From 210816fd5f8c6973a7cebd1af1efdd09cb754417 Mon Sep 17 00:00:00 2001 From: Shreehari-Acharya Date: Thu, 22 Jan 2026 10:02:42 +0530 Subject: [PATCH 01/13] move api function to services/api.js --- packages/mcp/lib/factory.js | 17 ----------------- packages/mcp/src/services/api.js | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 packages/mcp/src/services/api.js diff --git a/packages/mcp/lib/factory.js b/packages/mcp/lib/factory.js index b357141..a23462b 100644 --- a/packages/mcp/lib/factory.js +++ b/packages/mcp/lib/factory.js @@ -17,24 +17,7 @@ import { getAuthToken, getBaseUrl } from "./auth.js"; // API LAYER // ============================================================================ -/** - * Makes authenticated API requests to the Composter backend - */ -async function api(path, options = {}) { - const token = getAuthToken(); - const baseUrl = getBaseUrl(); - - const response = await fetch(`${baseUrl}${path}`, { - ...options, - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${token}`, - ...options.headers, - }, - }); - return response; -} // ============================================================================ // DATA FETCHERS diff --git a/packages/mcp/src/services/api.js b/packages/mcp/src/services/api.js new file mode 100644 index 0000000..0e95d30 --- /dev/null +++ b/packages/mcp/src/services/api.js @@ -0,0 +1,26 @@ +import { getAuthToken, getBaseUrl } from "./auth.js"; + +/** + * Makes authenticated API requests to the Composter backend + * @param {string} path - The API endpoint path + * @param {Object} options - The fetch options + * @returns {Response} The API response + */ +async function api(path, options = {}) { + const token = getAuthToken(); + const baseUrl = getBaseUrl(); + + const response = await fetch(`${baseUrl}${path}`, { + ...options, + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}`, + ...options.headers, + }, + }); + + return response; +} + +export default api; + \ No newline at end of file From 0ad89aee6cf30be7e4a767c8184ae959628aa630 Mon Sep 17 00:00:00 2001 From: Shreehari-Acharya Date: Thu, 22 Jan 2026 10:10:48 +0530 Subject: [PATCH 02/13] move catalog functions to a seperate file src/services/catalog.js --- packages/mcp/lib/factory.js | 44 ---------------------- packages/mcp/src/services/catalog.js | 55 ++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 44 deletions(-) create mode 100644 packages/mcp/src/services/catalog.js diff --git a/packages/mcp/lib/factory.js b/packages/mcp/lib/factory.js index a23462b..30ce541 100644 --- a/packages/mcp/lib/factory.js +++ b/packages/mcp/lib/factory.js @@ -23,51 +23,7 @@ import { getAuthToken, getBaseUrl } from "./auth.js"; // DATA FETCHERS // ============================================================================ -async function getAllCategories() { - const res = await api("/categories"); - if (!res.ok) throw new Error(await getErrorMessage(res)); - const data = await res.json(); - return data.categories || []; -} - -async function getAllComponents() { - const res = await api("/components/list"); - if (!res.ok) throw new Error(await getErrorMessage(res)); - const data = await res.json(); - return data.components || []; -} - -async function getComponentsByCategory(categoryName) { - const res = await api(`/components/list-by-category?category=${encodeURIComponent(categoryName)}`); - if (res.status === 404) return null; // Category not found - if (!res.ok) throw new Error(await getErrorMessage(res)); - const data = await res.json(); - return data.components || []; -} - -async function getComponent(category, title) { - const res = await api(`/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`); - if (res.status === 404) return null; - if (!res.ok) throw new Error(await getErrorMessage(res)); - const data = await res.json(); - return data.component; -} -async function searchComponents(query) { - const res = await api(`/components/search?q=${encodeURIComponent(query)}`); - if (!res.ok) throw new Error(await getErrorMessage(res)); - const data = await res.json(); - return data.components || []; -} - -async function getErrorMessage(res) { - try { - const data = await res.json(); - return data.message || data.error || res.statusText; - } catch { - return res.statusText; - } -} // ============================================================================ // FORMATTERS - Beautiful output for chat interfaces diff --git a/packages/mcp/src/services/catalog.js b/packages/mcp/src/services/catalog.js new file mode 100644 index 0000000..079dfad --- /dev/null +++ b/packages/mcp/src/services/catalog.js @@ -0,0 +1,55 @@ +import api from "./api.js"; + +async function getAllCategories() { + const res = api("/categories"); + if (!res.ok) throw new Error(getErrorMessage(res)); + const data = await res.json(); + return data.categories || []; +} + +async function getAllComponents() { + const res = api("/components/list"); + if (!res.ok) throw new Error(getErrorMessage(res)); + const data = await res.json(); + return data.components || []; +} + +async function getComponentsByCategory(categoryName) { + const res = api(`/components/list-by-category?category=${encodeURIComponent(categoryName)}`); + if (res.status === 404) return null; // Category not found + if (!res.ok) throw new Error(getErrorMessage(res)); + const data = await res.json(); + return data.components || []; +} + +async function getComponent(category, title) { + const res = api(`/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`); + if (res.status === 404) return null; + if (!res.ok) throw new Error(getErrorMessage(res)); + const data = await res.json(); + return data.component; +} + +async function searchComponents(query) { + const res = api(`/components/search?q=${encodeURIComponent(query)}`); + if (!res.ok) throw new Error(getErrorMessage(res)); + const data = await res.json(); + return data.components || []; +} + +async function getErrorMessage(res) { + try { + const data = await res.json(); + return data.message || data.error || res.statusText; + } catch { + return res.statusText; + } +} + +export { + getAllCategories, + getAllComponents, + getComponentsByCategory, + getComponent, + searchComponents, +}; From 0eb0d9c0ab3bd96c180c06619ad216daa2cbca11 Mon Sep 17 00:00:00 2001 From: Shreehari-Acharya Date: Thu, 22 Jan 2026 10:16:52 +0530 Subject: [PATCH 03/13] move markdown templates to utils (src/utils/formatting.js) --- packages/mcp/lib/factory.js | 185 -------------------------- packages/mcp/src/utils/formatting.js | 192 +++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 185 deletions(-) create mode 100644 packages/mcp/src/utils/formatting.js diff --git a/packages/mcp/lib/factory.js b/packages/mcp/lib/factory.js index 30ce541..af222ec 100644 --- a/packages/mcp/lib/factory.js +++ b/packages/mcp/lib/factory.js @@ -29,196 +29,11 @@ import { getAuthToken, getBaseUrl } from "./auth.js"; // FORMATTERS - Beautiful output for chat interfaces // ============================================================================ -function formatCategoriesList(categories) { - if (!categories.length) { - return `๐Ÿ“ **No categories found** - -Your vault is empty! Get started by creating a category: - -\`\`\`bash -composter mkcat buttons -\`\`\` - -Then push your first component: - -\`\`\`bash -composter push buttons "MyButton" ./src/components/Button.jsx -\`\`\``; - } - - const list = categories.map(c => ` โ€ข **${c.name}**`).join("\n"); - return `๐Ÿ“ **Your Categories** (${categories.length}) - -${list} - -๐Ÿ’ก *Ask me to "show components in [category]" to explore further*`; -} - -function formatComponentsList(components, categoryName = null) { - if (!components.length) { - const context = categoryName ? ` in "${categoryName}"` : ""; - return `๐Ÿ“ฆ **No components found${context}** - -Push components using the CLI: - -\`\`\`bash -composter push ${categoryName || "category"} "ComponentName" ./path/to/component.jsx -\`\`\``; - } - - const header = categoryName - ? `๐Ÿ“ฆ **Components in "${categoryName}"** (${components.length})` - : `๐Ÿ“ฆ **All Components** (${components.length})`; - - const list = components.map(c => { - const category = c.category?.name || "uncategorized"; - const date = new Date(c.createdAt).toLocaleDateString(); - const deps = getDepsCount(c); - const depsLabel = deps > 0 ? ` ยท ${deps} deps` : ""; - - return categoryName - ? ` โ€ข **${c.title}** โ€” ${date}${depsLabel}` - : ` โ€ข **${c.title}** *(${category})* โ€” ${date}${depsLabel}`; - }).join("\n"); - - return `${header} - -${list} - -๐Ÿ’ก *Ask me to "read [component] from [category]" to see the code*`; -} - -function formatComponentDetail(component, categoryName) { - if (!component) { - return `โŒ **Component not found** - -Try searching: *"find [keyword]"*`; - } - - // Parse multi-file or single-file code - let codeBlocks = ""; - try { - const files = JSON.parse(component.code); - codeBlocks = Object.entries(files) - .map(([filePath, content]) => { - const lang = getLanguageFromPath(filePath); - return `### ๐Ÿ“„ \`${filePath}\` - -\`\`\`${lang} -${content} -\`\`\``; - }) - .join("\n\n"); - } catch { - // Single file component - codeBlocks = `\`\`\`tsx -${component.code} -\`\`\``; - } - - // Format dependencies - let depsSection = ""; - if (component.dependencies && Object.keys(component.dependencies).length > 0) { - const deps = Object.entries(component.dependencies) - .map(([pkg, ver]) => ` โ€ข \`${pkg}\`: ${ver}`) - .join("\n"); - - const installCmd = Object.keys(component.dependencies).join(" "); - depsSection = ` ---- - -### ๐Ÿ“ฆ Dependencies - -${deps} - -**Install command:** -\`\`\`bash -npm install ${installCmd} -\`\`\` -`; - } - - const createdDate = new Date(component.createdAt).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric" - }); - - return `# ${component.title} - -| Property | Value | -|----------|-------| -| **Category** | ${categoryName} | -| **Created** | ${createdDate} | - ---- - -## Source Code - -${codeBlocks} -${depsSection} ---- - -๐Ÿ’ก *Pull this component:* \`composter pull ${categoryName} "${component.title}" ./components/\``; -} - -function formatSearchResults(components, query) { - if (!components.length) { - return `๐Ÿ” **No results for "${query}"** - -Try: - โ€ข Different keywords - โ€ข *"list categories"* to see what's available - โ€ข *"show all components"* to browse everything`; - } - - const results = components.slice(0, 10).map(c => { - const category = c.category?.name || "uncategorized"; - return ` โ€ข **${c.title}** in *${category}*`; - }).join("\n"); - - const moreNote = components.length > 10 - ? `\n\n*...and ${components.length - 10} more results*` - : ""; - - return `๐Ÿ” **Search results for "${query}"** (${components.length}) - -${results}${moreNote} - -๐Ÿ’ก *Ask me to "read [component] from [category]" to see the full code*`; -} // ============================================================================ // HELPERS // ============================================================================ -function getDepsCount(component) { - if (!component.dependencies) return 0; - try { - const deps = typeof component.dependencies === "string" - ? JSON.parse(component.dependencies) - : component.dependencies; - return Object.keys(deps).length; - } catch { - return 0; - } -} - -function getLanguageFromPath(filePath) { - const ext = filePath.split(".").pop()?.toLowerCase(); - const langMap = { - tsx: "tsx", - ts: "typescript", - jsx: "jsx", - js: "javascript", - css: "css", - scss: "scss", - json: "json", - md: "markdown" - }; - return langMap[ext] || "tsx"; -} - function normalizeText(text) { return text.toLowerCase().trim().replace(/\s+/g, " "); } diff --git a/packages/mcp/src/utils/formatting.js b/packages/mcp/src/utils/formatting.js new file mode 100644 index 0000000..0415ad1 --- /dev/null +++ b/packages/mcp/src/utils/formatting.js @@ -0,0 +1,192 @@ +function getDepsCount(component) { + if (!component.dependencies) return 0; + try { + const deps = typeof component.dependencies === "string" + ? JSON.parse(component.dependencies) + : component.dependencies; + return Object.keys(deps).length; + } catch { + return 0; + } +} + +function getLanguageFromPath(filePath) { + const ext = filePath.split(".").pop()?.toLowerCase(); + const langMap = { + tsx: "tsx", + ts: "typescript", + jsx: "jsx", + js: "javascript", + css: "css", + scss: "scss", + json: "json", + md: "markdown" + }; + return langMap[ext] || "tsx"; +} + +function formatCategoriesList(categories) { + if (!categories.length) { + return `๐Ÿ“ **No categories found** + +Your vault is empty! Get started by creating a category: + +\`\`\`bash +composter mkcat buttons +\`\`\` + +Then push your first component: + +\`\`\`bash +composter push buttons "MyButton" ./src/components/Button.jsx +\`\`\``; + } + + const list = categories.map(c => ` โ€ข **${c.name}**`).join("\n"); + return `๐Ÿ“ **Your Categories** (${categories.length}) + +${list} + +๐Ÿ’ก *Ask me to "show components in [category]" to explore further*`; +} + +function formatComponentsList(components, categoryName = null) { + if (!components.length) { + const context = categoryName ? ` in "${categoryName}"` : ""; + return `๐Ÿ“ฆ **No components found${context}** + +Push components using the CLI: + +\`\`\`bash +composter push ${categoryName || "category"} "ComponentName" ./path/to/component.jsx +\`\`\``; + } + + const header = categoryName + ? `๐Ÿ“ฆ **Components in "${categoryName}"** (${components.length})` + : `๐Ÿ“ฆ **All Components** (${components.length})`; + + const list = components.map(c => { + const category = c.category?.name || "uncategorized"; + const date = new Date(c.createdAt).toLocaleDateString(); + const deps = getDepsCount(c); + const depsLabel = deps > 0 ? ` ยท ${deps} deps` : ""; + + return categoryName + ? ` โ€ข **${c.title}** โ€” ${date}${depsLabel}` + : ` โ€ข **${c.title}** *(${category})* โ€” ${date}${depsLabel}`; + }).join("\n"); + + return `${header} + +${list} + +๐Ÿ’ก *Ask me to "read [component] from [category]" to see the code*`; +} + +function formatComponentDetail(component, categoryName) { + if (!component) { + return `โŒ **Component not found** + +Try searching: *"find [keyword]"*`; + } + + // Parse multi-file or single-file code + let codeBlocks = ""; + try { + const files = JSON.parse(component.code); + codeBlocks = Object.entries(files) + .map(([filePath, content]) => { + const lang = getLanguageFromPath(filePath); + return `### ๐Ÿ“„ \`${filePath}\` + +\`\`\`${lang} +${content} +\`\`\``; + }) + .join("\n\n"); + } catch { + // Single file component + codeBlocks = `\`\`\`tsx +${component.code} +\`\`\``; + } + + // Format dependencies + let depsSection = ""; + if (component.dependencies && Object.keys(component.dependencies).length > 0) { + const deps = Object.entries(component.dependencies) + .map(([pkg, ver]) => ` โ€ข \`${pkg}\`: ${ver}`) + .join("\n"); + + const installCmd = Object.keys(component.dependencies).join(" "); + depsSection = ` +--- + +### ๐Ÿ“ฆ Dependencies + +${deps} + +**Install command:** +\`\`\`bash +npm install ${installCmd} +\`\`\` +`; + } + + const createdDate = new Date(component.createdAt).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric" + }); + + return `# ${component.title} + +| Property | Value | +|----------|-------| +| **Category** | ${categoryName} | +| **Created** | ${createdDate} | + +--- + +## Source Code + +${codeBlocks} +${depsSection} +--- + +๐Ÿ’ก *Pull this component:* \`composter pull ${categoryName} "${component.title}" ./components/\``; +} + +function formatSearchResults(components, query) { + if (!components.length) { + return `๐Ÿ” **No results for "${query}"** + +Try: + โ€ข Different keywords + โ€ข *"list categories"* to see what's available + โ€ข *"show all components"* to browse everything`; + } + + const results = components.slice(0, 10).map(c => { + const category = c.category?.name || "uncategorized"; + return ` โ€ข **${c.title}** in *${category}*`; + }).join("\n"); + + const moreNote = components.length > 10 + ? `\n\n*...and ${components.length - 10} more results*` + : ""; + + return `๐Ÿ” **Search results for "${query}"** (${components.length}) + +${results}${moreNote} + +๐Ÿ’ก *Ask me to "read [component] from [category]" to see the full code*`; +} + +export { + formatCategoriesList, + formatComponentsList, + formatComponentDetail, + formatSearchResults, +} \ No newline at end of file From 34bedef0b5f48d05133c4ec3074bd0726ac54143 Mon Sep 17 00:00:00 2001 From: Shreehari-Acharya Date: Thu, 22 Jan 2026 10:29:19 +0530 Subject: [PATCH 04/13] decouple processNaturalQuery and its related regex patterns - new file : src/utils/nlp.js & src/utils/regexPatterns.js --- packages/mcp/lib/factory.js | 246 ------------------------ packages/mcp/src/utils/nlp.js | 214 +++++++++++++++++++++ packages/mcp/src/utils/regexPatterns.js | 39 ++++ 3 files changed, 253 insertions(+), 246 deletions(-) create mode 100644 packages/mcp/src/utils/nlp.js create mode 100644 packages/mcp/src/utils/regexPatterns.js diff --git a/packages/mcp/lib/factory.js b/packages/mcp/lib/factory.js index af222ec..73444e3 100644 --- a/packages/mcp/lib/factory.js +++ b/packages/mcp/lib/factory.js @@ -92,253 +92,7 @@ async function fuzzyFindCategory(searchName) { // NATURAL LANGUAGE PROCESSOR // ============================================================================ -/** - * Intelligent query parser that understands natural language requests - * and routes them to the appropriate handler - */ -async function processNaturalQuery(query) { - const q = normalizeText(query); - - // ------------------------------------------------------------------------- - // PATTERN: List all categories - // "list categories", "show my categories", "what categories do i have" - // ------------------------------------------------------------------------- - if ( - /\b(list|show|get|what|my)\b.*\bcategor(y|ies)\b/.test(q) || - /\bcategor(y|ies)\b.*\b(list|show|have)\b/.test(q) || - q === "categories" - ) { - const categories = await getAllCategories(); - return formatCategoriesList(categories); - } - - // ------------------------------------------------------------------------- - // PATTERN: List all components - // "show all components", "list everything", "what components do i have" - // ------------------------------------------------------------------------- - if ( - /\b(all|every)\b.*\bcomponent/.test(q) || - /\bcomponent.*\b(all|every|have)\b/.test(q) || - /\blist\s+(everything|all)\b/.test(q) || - /\bshow\s+(everything|all)\b/.test(q) || - q === "components" || - q === "all" - ) { - const components = await getAllComponents(); - return formatComponentsList(components); - } - - // ------------------------------------------------------------------------- - // PATTERN: List components in a specific category - // "show components in buttons", "what's in ui", "list items in forms" - // ------------------------------------------------------------------------- - const categoryListPatterns = [ - /(?:show|list|get|what'?s?)\s+(?:components?|items?)?\s*(?:in|from|under)\s+['"]?([a-z0-9_-]+)['"]?/i, - /(?:in|from)\s+['"]?([a-z0-9_-]+)['"]?\s+(?:category|folder)/i, - /['"]?([a-z0-9_-]+)['"]?\s+(?:components?|category)/i, - ]; - - for (const pattern of categoryListPatterns) { - const match = q.match(pattern); - if (match) { - const categoryInput = match[1]; - const categoryName = await fuzzyFindCategory(categoryInput); - - if (!categoryName) { - const categories = await getAllCategories(); - const suggestions = categories.slice(0, 5).map(c => `"${c.name}"`).join(", "); - return `โŒ **Category "${categoryInput}" not found** - -Available categories: ${suggestions || "none"} - -๐Ÿ’ก *Try "list categories" to see all available categories*`; - } - - const components = await getComponentsByCategory(categoryName); - if (components === null) { - return `โŒ **Category "${categoryName}" not found**`; - } - return formatComponentsList(components, categoryName); - } - } - - // ------------------------------------------------------------------------- - // PATTERN: Read/show a specific component - // "read Button from ui", "show me the Card component", "get LoginForm from auth" - // ------------------------------------------------------------------------- - const readPatterns = [ - // "read X from Y", "get X from Y", "show X from Y" - /(?:read|show|get|open|fetch|view|display)\s+['"]?(.+?)['"]?\s+(?:from|in)\s+['"]?([a-z0-9_-]+)['"]?/i, - // "X component from Y" - /['"]?(.+?)['"]?\s+(?:component\s+)?(?:from|in)\s+['"]?([a-z0-9_-]+)['"]?/i, - ]; - - for (const pattern of readPatterns) { - const match = query.match(pattern); - if (match) { - const titleInput = match[1].trim().replace(/^(the|a|my)\s+/i, "").replace(/\s+component$/i, ""); - const categoryInput = match[2].trim(); - - const categoryName = await fuzzyFindCategory(categoryInput); - if (!categoryName) { - return `โŒ **Category "${categoryInput}" not found** - -๐Ÿ’ก *Try "list categories" to see available categories*`; - } - // Try exact match first - let component = await getComponent(categoryName, titleInput); - - // If not found, try fuzzy match within the category - if (!component) { - const categoryComponents = await getComponentsByCategory(categoryName); - if (categoryComponents) { - const normalized = normalizeText(titleInput); - const fuzzyMatch = categoryComponents.find(c => - normalizeText(c.title).includes(normalized) || - normalized.includes(normalizeText(c.title)) - ); - if (fuzzyMatch) { - component = await getComponent(categoryName, fuzzyMatch.title); - } - } - } - - if (!component) { - const categoryComponents = await getComponentsByCategory(categoryName); - const suggestions = categoryComponents?.slice(0, 5).map(c => `"${c.title}"`).join(", "); - return `โŒ **Component "${titleInput}" not found in "${categoryName}"** - -Available in ${categoryName}: ${suggestions || "no components"} - -๐Ÿ’ก *Try "show components in ${categoryName}" to see all*`; - } - - return formatComponentDetail(component, categoryName); - } - } - - // ------------------------------------------------------------------------- - // PATTERN: Read component (without category specified) - // "show me the Button component", "get Card", "read LoginForm" - // ------------------------------------------------------------------------- - const simpleReadPatterns = [ - /(?:read|show|get|open|fetch|view|display)\s+(?:me\s+)?(?:the\s+)?['"]?(.+?)['"]?(?:\s+component)?$/i, - /^['"]?(.+?)['"]?\s+(?:code|source|component)$/i, - ]; - - for (const pattern of simpleReadPatterns) { - const match = query.match(pattern); - if (match) { - const titleInput = match[1].trim(); - - // Skip if it looks like a different command - if (/^(all|my|the|in|from|categories?|components?)$/i.test(titleInput)) continue; - - const foundComponent = await fuzzyFindComponent(titleInput); - - if (foundComponent) { - const categoryName = foundComponent.category?.name; - const component = await getComponent(categoryName, foundComponent.title); - return formatComponentDetail(component, categoryName); - } - - // Not found - search and suggest - const searchResults = await searchComponents(titleInput); - if (searchResults.length > 0) { - const suggestions = searchResults.slice(0, 5).map(c => - ` โ€ข **${c.title}** in *${c.category?.name}*` - ).join("\n"); - return `โ“ **Did you mean one of these?** - -${suggestions} - -๐Ÿ’ก *Be more specific: "read [component] from [category]"*`; - } - - return `โŒ **Component "${titleInput}" not found** - -๐Ÿ’ก *Try "list all components" or "search [keyword]"*`; - } - } - - // ------------------------------------------------------------------------- - // PATTERN: Search - // "find buttons", "search for cards", "look up forms" - // ------------------------------------------------------------------------- - const searchPatterns = [ - /(?:search|find|look\s*up|look\s*for)\s+(?:for\s+)?['"]?(.+?)['"]?$/i, - /^['"]?(.+?)['"]?\s+(?:search|find)$/i, - ]; - - for (const pattern of searchPatterns) { - const match = query.match(pattern); - if (match) { - const searchQuery = match[1].trim(); - const results = await searchComponents(searchQuery); - return formatSearchResults(results, searchQuery); - } - } - - // ------------------------------------------------------------------------- - // PATTERN: Help - // "help", "what can you do", "how do i use this" - // ------------------------------------------------------------------------- - if (/\b(help|usage|how|what can)\b/.test(q)) { - return `# ๐Ÿงฉ Composter - Your Component Vault - -I can help you manage your React component library. Here's what you can ask: - -## ๐Ÿ“ Categories - โ€ข *"list categories"* โ€” See all your categories - โ€ข *"show components in [category]"* โ€” Browse a category - -## ๐Ÿ“ฆ Components - โ€ข *"show all components"* โ€” List everything - โ€ข *"read [component] from [category]"* โ€” Get full source code - โ€ข *"find [keyword]"* โ€” Search your vault - -## ๐Ÿ’ก Examples - โ€ข "What categories do I have?" - โ€ข "Show me components in ui" - โ€ข "Read Button from buttons" - โ€ข "Find form components" - ---- - -**CLI Commands:** -\`\`\`bash -composter login # Authenticate -composter ls # List categories -composter push ui "Card" ./Card.jsx -composter pull ui "Card" ./components/ -\`\`\``; - } - - // ------------------------------------------------------------------------- - // FALLBACK: Treat as search query - // ------------------------------------------------------------------------- - const results = await searchComponents(query); - if (results.length > 0) { - return formatSearchResults(results, query); - } - - // Nothing found - provide guidance - const categories = await getAllCategories(); - const categoryList = categories.slice(0, 5).map(c => `"${c.name}"`).join(", "); - - return `๐Ÿค” **I'm not sure what you're looking for** - -I couldn't find anything matching "${query}". - -**Try asking:** - โ€ข "list categories" - โ€ข "show all components" - โ€ข "find [keyword]" -${categoryList ? `\n**Your categories:** ${categoryList}` : ""} - -๐Ÿ’ก *Type "help" for more guidance*`; -} // ============================================================================ // MCP SERVER FACTORY diff --git a/packages/mcp/src/utils/nlp.js b/packages/mcp/src/utils/nlp.js new file mode 100644 index 0000000..236694c --- /dev/null +++ b/packages/mcp/src/utils/nlp.js @@ -0,0 +1,214 @@ + + + + +/** + * Intelligent query parser that understands natural language requests + * and routes them to the appropriate handler + */ +async function processNaturalQuery(query) { + const q = normalizeText(query); + + // ------------------------------------------------------------------------- + // PATTERN: List all categories + // "list categories", "show my categories", "what categories do i have" + // ------------------------------------------------------------------------- + if ( + /\b(list|show|get|what|my)\b.*\bcategor(y|ies)\b/.test(q) || + /\bcategor(y|ies)\b.*\b(list|show|have)\b/.test(q) || + q === "categories" + ) { + const categories = await getAllCategories(); + return formatCategoriesList(categories); + } + + // ------------------------------------------------------------------------- + // PATTERN: List all components + // "show all components", "list everything", "what components do i have" + // ------------------------------------------------------------------------- + if ( + /\b(all|every)\b.*\bcomponent/.test(q) || + /\bcomponent.*\b(all|every|have)\b/.test(q) || + /\blist\s+(everything|all)\b/.test(q) || + /\bshow\s+(everything|all)\b/.test(q) || + q === "components" || + q === "all" + ) { + const components = await getAllComponents(); + return formatComponentsList(components); + } + + for (const pattern of categoryListPatterns) { + const match = q.match(pattern); + if (match) { + const categoryInput = match[1]; + const categoryName = await fuzzyFindCategory(categoryInput); + + if (!categoryName) { + const categories = await getAllCategories(); + const suggestions = categories.slice(0, 5).map(c => `"${c.name}"`).join(", "); + return `โŒ **Category "${categoryInput}" not found** + +Available categories: ${suggestions || "none"} + +๐Ÿ’ก *Try "list categories" to see all available categories*`; + } + + const components = await getComponentsByCategory(categoryName); + if (components === null) { + return `โŒ **Category "${categoryName}" not found**`; + } + return formatComponentsList(components, categoryName); + } + } + + + + for (const pattern of readPatterns) { + const match = query.match(pattern); + if (match) { + const titleInput = match[1].trim().replace(/^(the|a|my)\s+/i, "").replace(/\s+component$/i, ""); + const categoryInput = match[2].trim(); + + const categoryName = await fuzzyFindCategory(categoryInput); + if (!categoryName) { + return `โŒ **Category "${categoryInput}" not found** + +๐Ÿ’ก *Try "list categories" to see available categories*`; + } + + // Try exact match first + let component = await getComponent(categoryName, titleInput); + + // If not found, try fuzzy match within the category + if (!component) { + const categoryComponents = await getComponentsByCategory(categoryName); + if (categoryComponents) { + const normalized = normalizeText(titleInput); + const fuzzyMatch = categoryComponents.find(c => + normalizeText(c.title).includes(normalized) || + normalized.includes(normalizeText(c.title)) + ); + if (fuzzyMatch) { + component = await getComponent(categoryName, fuzzyMatch.title); + } + } + } + + if (!component) { + const categoryComponents = await getComponentsByCategory(categoryName); + const suggestions = categoryComponents?.slice(0, 5).map(c => `"${c.title}"`).join(", "); + return `โŒ **Component "${titleInput}" not found in "${categoryName}"** + +Available in ${categoryName}: ${suggestions || "no components"} + +๐Ÿ’ก *Try "show components in ${categoryName}" to see all*`; + } + + return formatComponentDetail(component, categoryName); + } + } + + for (const pattern of simpleReadPatterns) { + const match = query.match(pattern); + if (match) { + const titleInput = match[1].trim(); + + // Skip if it looks like a different command + if (/^(all|my|the|in|from|categories?|components?)$/i.test(titleInput)) continue; + + const foundComponent = await fuzzyFindComponent(titleInput); + + if (foundComponent) { + const categoryName = foundComponent.category?.name; + const component = await getComponent(categoryName, foundComponent.title); + return formatComponentDetail(component, categoryName); + } + + // Not found - search and suggest + const searchResults = await searchComponents(titleInput); + if (searchResults.length > 0) { + const suggestions = searchResults.slice(0, 5).map(c => + ` โ€ข **${c.title}** in *${c.category?.name}*` + ).join("\n"); + return `โ“ **Did you mean one of these?** + +${suggestions} + +๐Ÿ’ก *Be more specific: "read [component] from [category]"*`; + } + + return `โŒ **Component "${titleInput}" not found** + +๐Ÿ’ก *Try "list all components" or "search [keyword]"*`; + } + } + + for (const pattern of searchPatterns) { + const match = query.match(pattern); + if (match) { + const searchQuery = match[1].trim(); + const results = await searchComponents(searchQuery); + return formatSearchResults(results, searchQuery); + } + } + + // ------------------------------------------------------------------------- + // PATTERN: Help + // "help", "what can you do", "how do i use this" + // ------------------------------------------------------------------------- + if (/\b(help|usage|how|what can)\b/.test(q)) { + return `# ๐Ÿงฉ Composter - Your Component Vault + +I can help you manage your React component library. Here's what you can ask: + +## ๐Ÿ“ Categories + โ€ข *"list categories"* โ€” See all your categories + โ€ข *"show components in [category]"* โ€” Browse a category + +## ๐Ÿ“ฆ Components + โ€ข *"show all components"* โ€” List everything + โ€ข *"read [component] from [category]"* โ€” Get full source code + โ€ข *"find [keyword]"* โ€” Search your vault + +## ๐Ÿ’ก Examples + โ€ข "What categories do I have?" + โ€ข "Show me components in ui" + โ€ข "Read Button from buttons" + โ€ข "Find form components" + +--- + +**CLI Commands:** +\`\`\`bash +composter login # Authenticate +composter ls # List categories +composter push ui "Card" ./Card.jsx +composter pull ui "Card" ./components/ +\`\`\``; + } + + // ------------------------------------------------------------------------- + // FALLBACK: Treat as search query + // ------------------------------------------------------------------------- + const results = await searchComponents(query); + if (results.length > 0) { + return formatSearchResults(results, query); + } + + // Nothing found - provide guidance + const categories = await getAllCategories(); + const categoryList = categories.slice(0, 5).map(c => `"${c.name}"`).join(", "); + + return `๐Ÿค” **I'm not sure what you're looking for** + +I couldn't find anything matching "${query}". + +**Try asking:** + โ€ข "list categories" + โ€ข "show all components" + โ€ข "find [keyword]" +${categoryList ? `\n**Your categories:** ${categoryList}` : ""} + +๐Ÿ’ก *Type "help" for more guidance*`; +} \ No newline at end of file diff --git a/packages/mcp/src/utils/regexPatterns.js b/packages/mcp/src/utils/regexPatterns.js new file mode 100644 index 0000000..6244bf8 --- /dev/null +++ b/packages/mcp/src/utils/regexPatterns.js @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------------- +// PATTERN: List components in a specific category +// "show components in buttons", "what's in ui", "list items in forms" +// ------------------------------------------------------------------------- +export const categoryListPatterns = [ + /(?:show|list|get|what'?s?)\s+(?:components?|items?)?\s*(?:in|from|under)\s+['"]?([a-z0-9_-]+)['"]?/i, + /(?:in|from)\s+['"]?([a-z0-9_-]+)['"]?\s+(?:category|folder)/i, + /['"]?([a-z0-9_-]+)['"]?\s+(?:components?|category)/i, +]; + +// ------------------------------------------------------------------------- +// PATTERN: Read/show a specific component +// "read Button from ui", "show me the Card component", "get LoginForm from auth" +// ------------------------------------------------------------------------- +export const readPatterns = [ + // "read X from Y", "get X from Y", "show X from Y" + /(?:read|show|get|open|fetch|view|display)\s+['"]?(.+?)['"]?\s+(?:from|in)\s+['"]?([a-z0-9_-]+)['"]?/i, + // "X component from Y" + /['"]?(.+?)['"]?\s+(?:component\s+)?(?:from|in)\s+['"]?([a-z0-9_-]+)['"]?/i, +]; + + +// ------------------------------------------------------------------------- +// PATTERN: Read component (without category specified) +// "show me the Button component", "get Card", "read LoginForm" +// ------------------------------------------------------------------------- +export const simpleReadPatterns = [ + /(?:read|show|get|open|fetch|view|display)\s+(?:me\s+)?(?:the\s+)?['"]?(.+?)['"]?(?:\s+component)?$/i, + /^['"]?(.+?)['"]?\s+(?:code|source|component)$/i, +]; + +// ------------------------------------------------------------------------- +// PATTERN: Search +// "find buttons", "search for cards", "look up forms" +// ------------------------------------------------------------------------- +export const searchPatterns = [ + /(?:search|find|look\s*up|look\s*for)\s+(?:for\s+)?['"]?(.+?)['"]?$/i, + /^['"]?(.+?)['"]?\s+(?:search|find)$/i, +]; \ No newline at end of file From 295ea05ee7776f46fc3c92490d28d342f0c297d4 Mon Sep 17 00:00:00 2001 From: Shreehari-Acharya Date: Thu, 22 Jan 2026 10:33:18 +0530 Subject: [PATCH 05/13] move fuzzy logic functions to a seperate file - New file : src/utils/fuzzy.js --- packages/mcp/lib/factory.js | 53 --------------------------------- packages/mcp/src/utils/fuzzy.js | 53 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 53 deletions(-) create mode 100644 packages/mcp/src/utils/fuzzy.js diff --git a/packages/mcp/lib/factory.js b/packages/mcp/lib/factory.js index 73444e3..8fd641e 100644 --- a/packages/mcp/lib/factory.js +++ b/packages/mcp/lib/factory.js @@ -34,59 +34,6 @@ import { getAuthToken, getBaseUrl } from "./auth.js"; // HELPERS // ============================================================================ -function normalizeText(text) { - return text.toLowerCase().trim().replace(/\s+/g, " "); -} - -/** - * Fuzzy match a component by title across all components - */ -async function fuzzyFindComponent(searchTitle) { - const allComponents = await getAllComponents(); - const normalized = normalizeText(searchTitle); - - // Exact match first - let match = allComponents.find(c => - normalizeText(c.title) === normalized - ); - if (match) return match; - - // Partial match - match = allComponents.find(c => - normalizeText(c.title).includes(normalized) || - normalized.includes(normalizeText(c.title)) - ); - if (match) return match; - - // Word-based match - const searchWords = normalized.split(/\s+/); - match = allComponents.find(c => { - const titleNorm = normalizeText(c.title); - return searchWords.every(word => titleNorm.includes(word)); - }); - - return match; -} - -/** - * Fuzzy match a category by name - */ -async function fuzzyFindCategory(searchName) { - const categories = await getAllCategories(); - const normalized = normalizeText(searchName); - - // Exact match - let match = categories.find(c => normalizeText(c.name) === normalized); - if (match) return match.name; - - // Partial match - match = categories.find(c => - normalizeText(c.name).includes(normalized) || - normalized.includes(normalizeText(c.name)) - ); - - return match?.name || null; -} // ============================================================================ // NATURAL LANGUAGE PROCESSOR diff --git a/packages/mcp/src/utils/fuzzy.js b/packages/mcp/src/utils/fuzzy.js new file mode 100644 index 0000000..00745f6 --- /dev/null +++ b/packages/mcp/src/utils/fuzzy.js @@ -0,0 +1,53 @@ +function normalizeText(text) { + return text.toLowerCase().trim().replace(/\s+/g, " "); +} + +/** + * Fuzzy match a component by title across all components + */ +export async function fuzzyFindComponent(searchTitle) { + const allComponents = await getAllComponents(); + const normalized = normalizeText(searchTitle); + + // Exact match first + let match = allComponents.find(c => + normalizeText(c.title) === normalized + ); + if (match) return match; + + // Partial match + match = allComponents.find(c => + normalizeText(c.title).includes(normalized) || + normalized.includes(normalizeText(c.title)) + ); + if (match) return match; + + // Word-based match + const searchWords = normalized.split(/\s+/); + match = allComponents.find(c => { + const titleNorm = normalizeText(c.title); + return searchWords.every(word => titleNorm.includes(word)); + }); + + return match; +} + +/** + * Fuzzy match a category by name + */ +export async function fuzzyFindCategory(searchName) { + const categories = await getAllCategories(); + const normalized = normalizeText(searchName); + + // Exact match + let match = categories.find(c => normalizeText(c.name) === normalized); + if (match) return match.name; + + // Partial match + match = categories.find(c => + normalizeText(c.name).includes(normalized) || + normalized.includes(normalizeText(c.name)) + ); + + return match?.name || null; +} From f672c556587edafe48972201fce7e1ee6dcfff1c Mon Sep 17 00:00:00 2001 From: Shreehari-Acharya Date: Thu, 22 Jan 2026 13:19:32 +0530 Subject: [PATCH 06/13] replace server.tool with server.registerTool and fix exports - update zod to latest - add a jsconfig.json file in packages/mcp --- package-lock.json | 17 +-- packages/mcp/jsconfig.json | 19 +++ packages/mcp/lib/factory.js | 158 ++++++++++++------------ packages/mcp/package.json | 2 +- packages/mcp/src/services/api.js | 4 +- packages/mcp/src/services/catalog.js | 20 +-- packages/mcp/src/utils/fuzzy.js | 4 +- packages/mcp/src/utils/nlp.js | 29 ++++- packages/mcp/src/utils/regexPatterns.js | 17 ++- 9 files changed, 152 insertions(+), 118 deletions(-) create mode 100644 packages/mcp/jsconfig.json diff --git a/package-lock.json b/package-lock.json index a36534d..17f5a83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18365,9 +18365,9 @@ } }, "node_modules/zod": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", - "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -18822,7 +18822,7 @@ "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.23.0", - "zod": "^3.23.0" + "zod": "^4.3.5" }, "bin": { "composter-mcp": "bin/composter-mcp.js" @@ -18833,15 +18833,6 @@ "engines": { "node": ">=18.0.0" } - }, - "packages/mcp/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/packages/mcp/jsconfig.json b/packages/mcp/jsconfig.json new file mode 100644 index 0000000..740f888 --- /dev/null +++ b/packages/mcp/jsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "ESNext", + "target": "ES6", + "checkJs": true, + "jsx": "preserve", + "moduleResolution": "node", + "baseUrl": "./", + "skipLibCheck": true, + "noLib": false + }, + "exclude": [ + "node_modules", + "**/node_modules/*", + "dist", + "build", + ".git" + ] +} \ No newline at end of file diff --git a/packages/mcp/lib/factory.js b/packages/mcp/lib/factory.js index 8fd641e..4c7b4be 100644 --- a/packages/mcp/lib/factory.js +++ b/packages/mcp/lib/factory.js @@ -11,39 +11,20 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; -import { getAuthToken, getBaseUrl } from "./auth.js"; - -// ============================================================================ -// API LAYER -// ============================================================================ - - - -// ============================================================================ -// DATA FETCHERS -// ============================================================================ - - - -// ============================================================================ -// FORMATTERS - Beautiful output for chat interfaces -// ============================================================================ - - -// ============================================================================ -// HELPERS -// ============================================================================ - - -// ============================================================================ -// NATURAL LANGUAGE PROCESSOR -// ============================================================================ - - - -// ============================================================================ -// MCP SERVER FACTORY -// ============================================================================ +import { + getAllCategories, + getComponentsByCategory, + getComponent, + searchComponents, +} from "../src/services/catalog"; +import { processNaturalQuery } from "../src/utils/nlp"; +import { + formatCategoriesList, + formatComponentDetail, + formatComponentsList, + formatSearchResults, +} from "../src/utils/formatting"; +import { fuzzyFindCategory, normalizeText } from "../src/utils/fuzzy"; /** * Creates and configures the Composter MCP server with all tools @@ -55,26 +36,27 @@ export function createMcpServer() { }); // =========================================================================== - // TOOL: ask_composter (Primary Natural Language Interface) + // TOOL: ask_composter // =========================================================================== - server.tool( + server.registerTool( "ask_composter", - `Ask in plain English to list categories, show components in a category, search components, or read a component (e.g., 'list categories', 'show components in ui', 'read Simple Card from ui', 'find button components').`, { - query: z.string().describe( - "Natural language query - e.g., 'list categories', 'show components in buttons', 'read Card from ui', 'find forms'" - ), + title: "Ask Composter", + description: "Ask in plain English to list categories, show components in a category, search components, or read a component (e.g., 'list categories', 'show components in ui', 'read Simple Card from ui', 'find button components').", + inputSchema: { + query: z.string().describe("Natural language query - e.g., 'list categories', 'show components in buttons', 'read Card from ui', 'find forms'") + } }, async ({ query }) => { try { const result = await processNaturalQuery(query.trim()); return { content: [{ type: "text", text: result }] }; } catch (err) { - return { - content: [{ - type: "text", - text: `โŒ **Error:** ${err.message}\n\n๐Ÿ’ก *Make sure you're logged in: \`composter login\`*` - }] + return { + content: [{ + type: "text", + text: `โŒ **Error:** ${err.message}\n\n๐Ÿ’ก *Make sure you're logged in: \`composter login\`*` + }] }; } } @@ -83,17 +65,20 @@ export function createMcpServer() { // =========================================================================== // TOOL: search_components // =========================================================================== - server.tool( + server.registerTool( "search_components", - "Search vault components by name or topic. Triggers on queries like 'find button components', 'search cards', 'look up forms'. Returns matches with IDs and categories.", { - query: z.string().describe("Search term for component title or category name"), + title: "Search Components", + description: "Search vault components by name or topic. Triggers on queries like 'find button components', 'search cards', 'look up forms'. Returns matches with IDs and categories.", + inputSchema: { + query: z.string().describe("Search term for component title or category name") + } }, async ({ query }) => { try { const results = await searchComponents(query.trim()); - return { - content: [{ type: "text", text: formatSearchResults(results, query.trim()) }] + return { + content: [{ type: "text", text: formatSearchResults(results, query.trim()) }] }; } catch (err) { return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; @@ -104,10 +89,13 @@ export function createMcpServer() { // =========================================================================== // TOOL: list_categories // =========================================================================== - server.tool( + server.registerTool( "list_categories", - "List all categories in the vault. Trigger when user asks 'what categories do I have', 'show my categories', 'list vault categories'.", - {}, + { + title: "List Categories", + description: "List all categories in the vault. Trigger when user asks 'what categories do I have', 'show my categories', 'list vault categories'.", + inputSchema: {} + }, async () => { try { const categories = await getAllCategories(); @@ -121,30 +109,33 @@ export function createMcpServer() { // =========================================================================== // TOOL: list_components // =========================================================================== - server.tool( + server.registerTool( "list_components", - "List components inside a given category. Trigger on requests like 'show components in ui', 'what's in forms', 'list items in buttons'.", { - category: z.string().describe("The category name to list components from"), + title: "List Components", + description: "List components inside a given category. Trigger on requests like 'show components in ui', 'what's in forms', 'list items in buttons'.", + inputSchema: { + category: z.string().describe("The category name to list components from") + } }, async ({ category }) => { try { const categoryName = await fuzzyFindCategory(category.trim()); - + if (!categoryName) { const categories = await getAllCategories(); const suggestions = categories.slice(0, 5).map(c => `"${c.name}"`).join(", "); - return { - content: [{ - type: "text", - text: `โŒ **Category "${category}" not found**\n\nAvailable: ${suggestions || "none"}` - }] + return { + content: [{ + type: "text", + text: `โŒ **Category "${category}" not found**\n\nAvailable: ${suggestions || "none"}` + }] }; } const components = await getComponentsByCategory(categoryName); - return { - content: [{ type: "text", text: formatComponentsList(components, categoryName) }] + return { + content: [{ type: "text", text: formatComponentsList(components, categoryName) }] }; } catch (err) { return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; @@ -155,35 +146,38 @@ export function createMcpServer() { // =========================================================================== // TOOL: read_component // =========================================================================== - server.tool( + server.registerTool( "read_component", - "Read a component's full source. Trigger on 'read/open/show/get from ' or similar. Returns code, category, dependencies, and creation date.", { - category: z.string().describe("The category name the component belongs to"), - title: z.string().describe("The title/name of the component to read"), + title: "Read Component", + description: "Read a component's full source. Trigger on 'read/open/show/get from ' or similar. Returns code, category, dependencies, and creation date.", + inputSchema: { + category: z.string().describe("The category name the component belongs to"), + title: z.string().describe("The title/name of the component to read") + } }, async ({ category, title }) => { try { const categoryName = await fuzzyFindCategory(category.trim()); - + if (!categoryName) { - return { - content: [{ - type: "text", - text: `โŒ **Category "${category}" not found**\n\n๐Ÿ’ก *Try "list categories" to see available options*` - }] + return { + content: [{ + type: "text", + text: `โŒ **Category "${category}" not found**\n\n๐Ÿ’ก *Try "list categories" to see available options*` + }] }; } // Try exact match let component = await getComponent(categoryName, title.trim()); - + // Try fuzzy match if exact fails if (!component) { const categoryComponents = await getComponentsByCategory(categoryName); if (categoryComponents) { const normalized = normalizeText(title); - const fuzzyMatch = categoryComponents.find(c => + const fuzzyMatch = categoryComponents.find(c => normalizeText(c.title).includes(normalized) || normalized.includes(normalizeText(c.title)) ); @@ -196,16 +190,16 @@ export function createMcpServer() { if (!component) { const categoryComponents = await getComponentsByCategory(categoryName); const suggestions = categoryComponents?.slice(0, 5).map(c => `"${c.title}"`).join(", "); - return { - content: [{ - type: "text", - text: `โŒ **Component "${title}" not found in "${categoryName}"**\n\nAvailable: ${suggestions || "none"}` - }] + return { + content: [{ + type: "text", + text: `โŒ **Component "${title}" not found in "${categoryName}"**\n\nAvailable: ${suggestions || "none"}` + }] }; } - return { - content: [{ type: "text", text: formatComponentDetail(component, categoryName) }] + return { + content: [{ type: "text", text: formatComponentDetail(component, categoryName) }] }; } catch (err) { return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; diff --git a/packages/mcp/package.json b/packages/mcp/package.json index b25e19c..f33a724 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -38,7 +38,7 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.23.0", - "zod": "^3.23.0" + "zod": "^4.3.5" }, "devDependencies": { "@modelcontextprotocol/inspector": "^0.18.0" diff --git a/packages/mcp/src/services/api.js b/packages/mcp/src/services/api.js index 0e95d30..3499ace 100644 --- a/packages/mcp/src/services/api.js +++ b/packages/mcp/src/services/api.js @@ -1,10 +1,10 @@ -import { getAuthToken, getBaseUrl } from "./auth.js"; +import { getAuthToken, getBaseUrl } from "../../lib/auth"; /** * Makes authenticated API requests to the Composter backend * @param {string} path - The API endpoint path * @param {Object} options - The fetch options - * @returns {Response} The API response + * @returns response - The fetch response */ async function api(path, options = {}) { const token = getAuthToken(); diff --git a/packages/mcp/src/services/catalog.js b/packages/mcp/src/services/catalog.js index 079dfad..d27d39b 100644 --- a/packages/mcp/src/services/catalog.js +++ b/packages/mcp/src/services/catalog.js @@ -1,38 +1,38 @@ import api from "./api.js"; async function getAllCategories() { - const res = api("/categories"); - if (!res.ok) throw new Error(getErrorMessage(res)); + const res = await api("/categories"); + if (!res.ok) throw new Error(await getErrorMessage(res)); const data = await res.json(); return data.categories || []; } async function getAllComponents() { - const res = api("/components/list"); - if (!res.ok) throw new Error(getErrorMessage(res)); + const res = await api("/components/list"); + if (!res.ok) throw new Error(await getErrorMessage(res)); const data = await res.json(); return data.components || []; } async function getComponentsByCategory(categoryName) { - const res = api(`/components/list-by-category?category=${encodeURIComponent(categoryName)}`); + const res = await api(`/components/list-by-category?category=${encodeURIComponent(categoryName)}`); if (res.status === 404) return null; // Category not found - if (!res.ok) throw new Error(getErrorMessage(res)); + if (!res.ok) throw new Error(await getErrorMessage(res)); const data = await res.json(); return data.components || []; } async function getComponent(category, title) { - const res = api(`/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`); + const res = await api(`/components?category=${encodeURIComponent(category)}&title=${encodeURIComponent(title)}`); if (res.status === 404) return null; - if (!res.ok) throw new Error(getErrorMessage(res)); + if (!res.ok) throw new Error(await getErrorMessage(res)); const data = await res.json(); return data.component; } async function searchComponents(query) { - const res = api(`/components/search?q=${encodeURIComponent(query)}`); - if (!res.ok) throw new Error(getErrorMessage(res)); + const res = await api(`/components/search?q=${encodeURIComponent(query)}`); + if (!res.ok) throw new Error(await getErrorMessage(res)); const data = await res.json(); return data.components || []; } diff --git a/packages/mcp/src/utils/fuzzy.js b/packages/mcp/src/utils/fuzzy.js index 00745f6..150f6d5 100644 --- a/packages/mcp/src/utils/fuzzy.js +++ b/packages/mcp/src/utils/fuzzy.js @@ -1,4 +1,6 @@ -function normalizeText(text) { +import { getAllComponents, getAllCategories } from "../services/catalog" + +export function normalizeText(text) { return text.toLowerCase().trim().replace(/\s+/g, " "); } diff --git a/packages/mcp/src/utils/nlp.js b/packages/mcp/src/utils/nlp.js index 236694c..f33f17d 100644 --- a/packages/mcp/src/utils/nlp.js +++ b/packages/mcp/src/utils/nlp.js @@ -1,12 +1,33 @@ - - - +import { + normalizeText, + fuzzyFindCategory, + fuzzyFindComponent +} from "./fuzzy"; +import { + getAllCategories, + getAllComponents, + getComponentsByCategory, + getComponent, + searchComponents, +} from "../services/catalog"; +import { + formatCategoriesList, + formatComponentsList, + formatComponentDetail, + formatSearchResults, +} from "./formatting" +import { + categoryListPatterns, + readPatterns, + simpleReadPatterns, + searchPatterns, +} from "./regexPatterns" /** * Intelligent query parser that understands natural language requests * and routes them to the appropriate handler */ -async function processNaturalQuery(query) { +export async function processNaturalQuery(query) { const q = normalizeText(query); // ------------------------------------------------------------------------- diff --git a/packages/mcp/src/utils/regexPatterns.js b/packages/mcp/src/utils/regexPatterns.js index 6244bf8..f54d28b 100644 --- a/packages/mcp/src/utils/regexPatterns.js +++ b/packages/mcp/src/utils/regexPatterns.js @@ -2,7 +2,7 @@ // PATTERN: List components in a specific category // "show components in buttons", "what's in ui", "list items in forms" // ------------------------------------------------------------------------- -export const categoryListPatterns = [ +const categoryListPatterns = [ /(?:show|list|get|what'?s?)\s+(?:components?|items?)?\s*(?:in|from|under)\s+['"]?([a-z0-9_-]+)['"]?/i, /(?:in|from)\s+['"]?([a-z0-9_-]+)['"]?\s+(?:category|folder)/i, /['"]?([a-z0-9_-]+)['"]?\s+(?:components?|category)/i, @@ -12,7 +12,7 @@ export const categoryListPatterns = [ // PATTERN: Read/show a specific component // "read Button from ui", "show me the Card component", "get LoginForm from auth" // ------------------------------------------------------------------------- -export const readPatterns = [ +const readPatterns = [ // "read X from Y", "get X from Y", "show X from Y" /(?:read|show|get|open|fetch|view|display)\s+['"]?(.+?)['"]?\s+(?:from|in)\s+['"]?([a-z0-9_-]+)['"]?/i, // "X component from Y" @@ -24,7 +24,7 @@ export const readPatterns = [ // PATTERN: Read component (without category specified) // "show me the Button component", "get Card", "read LoginForm" // ------------------------------------------------------------------------- -export const simpleReadPatterns = [ +const simpleReadPatterns = [ /(?:read|show|get|open|fetch|view|display)\s+(?:me\s+)?(?:the\s+)?['"]?(.+?)['"]?(?:\s+component)?$/i, /^['"]?(.+?)['"]?\s+(?:code|source|component)$/i, ]; @@ -33,7 +33,14 @@ export const simpleReadPatterns = [ // PATTERN: Search // "find buttons", "search for cards", "look up forms" // ------------------------------------------------------------------------- -export const searchPatterns = [ +const searchPatterns = [ /(?:search|find|look\s*up|look\s*for)\s+(?:for\s+)?['"]?(.+?)['"]?$/i, /^['"]?(.+?)['"]?\s+(?:search|find)$/i, -]; \ No newline at end of file +]; + +export { + categoryListPatterns, + readPatterns, + simpleReadPatterns, + searchPatterns, +} \ No newline at end of file From ea1a11d8468e6e4411bfbe15bc01f79e843b6701 Mon Sep 17 00:00:00 2001 From: Shreehari-Acharya Date: Thu, 22 Jan 2026 13:36:01 +0530 Subject: [PATCH 07/13] modularise the tool layer of the McpServer --- packages/mcp/lib/factory.js | 189 +-------------------------- packages/mcp/src/tools/components.js | 113 ++++++++++++++++ packages/mcp/src/tools/general.js | 50 +++++++ packages/mcp/src/tools/index.js | 12 ++ 4 files changed, 178 insertions(+), 186 deletions(-) create mode 100644 packages/mcp/src/tools/components.js create mode 100644 packages/mcp/src/tools/general.js create mode 100644 packages/mcp/src/tools/index.js diff --git a/packages/mcp/lib/factory.js b/packages/mcp/lib/factory.js index 4c7b4be..b2e5cfd 100644 --- a/packages/mcp/lib/factory.js +++ b/packages/mcp/lib/factory.js @@ -10,21 +10,7 @@ */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { z } from "zod"; -import { - getAllCategories, - getComponentsByCategory, - getComponent, - searchComponents, -} from "../src/services/catalog"; -import { processNaturalQuery } from "../src/utils/nlp"; -import { - formatCategoriesList, - formatComponentDetail, - formatComponentsList, - formatSearchResults, -} from "../src/utils/formatting"; -import { fuzzyFindCategory, normalizeText } from "../src/utils/fuzzy"; +import { registerTools } from "src/tools"; /** * Creates and configures the Composter MCP server with all tools @@ -35,177 +21,8 @@ export function createMcpServer() { version: "2.0.0", }); - // =========================================================================== - // TOOL: ask_composter - // =========================================================================== - server.registerTool( - "ask_composter", - { - title: "Ask Composter", - description: "Ask in plain English to list categories, show components in a category, search components, or read a component (e.g., 'list categories', 'show components in ui', 'read Simple Card from ui', 'find button components').", - inputSchema: { - query: z.string().describe("Natural language query - e.g., 'list categories', 'show components in buttons', 'read Card from ui', 'find forms'") - } - }, - async ({ query }) => { - try { - const result = await processNaturalQuery(query.trim()); - return { content: [{ type: "text", text: result }] }; - } catch (err) { - return { - content: [{ - type: "text", - text: `โŒ **Error:** ${err.message}\n\n๐Ÿ’ก *Make sure you're logged in: \`composter login\`*` - }] - }; - } - } - ); - - // =========================================================================== - // TOOL: search_components - // =========================================================================== - server.registerTool( - "search_components", - { - title: "Search Components", - description: "Search vault components by name or topic. Triggers on queries like 'find button components', 'search cards', 'look up forms'. Returns matches with IDs and categories.", - inputSchema: { - query: z.string().describe("Search term for component title or category name") - } - }, - async ({ query }) => { - try { - const results = await searchComponents(query.trim()); - return { - content: [{ type: "text", text: formatSearchResults(results, query.trim()) }] - }; - } catch (err) { - return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; - } - } - ); - - // =========================================================================== - // TOOL: list_categories - // =========================================================================== - server.registerTool( - "list_categories", - { - title: "List Categories", - description: "List all categories in the vault. Trigger when user asks 'what categories do I have', 'show my categories', 'list vault categories'.", - inputSchema: {} - }, - async () => { - try { - const categories = await getAllCategories(); - return { content: [{ type: "text", text: formatCategoriesList(categories) }] }; - } catch (err) { - return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; - } - } - ); - - // =========================================================================== - // TOOL: list_components - // =========================================================================== - server.registerTool( - "list_components", - { - title: "List Components", - description: "List components inside a given category. Trigger on requests like 'show components in ui', 'what's in forms', 'list items in buttons'.", - inputSchema: { - category: z.string().describe("The category name to list components from") - } - }, - async ({ category }) => { - try { - const categoryName = await fuzzyFindCategory(category.trim()); - - if (!categoryName) { - const categories = await getAllCategories(); - const suggestions = categories.slice(0, 5).map(c => `"${c.name}"`).join(", "); - return { - content: [{ - type: "text", - text: `โŒ **Category "${category}" not found**\n\nAvailable: ${suggestions || "none"}` - }] - }; - } - - const components = await getComponentsByCategory(categoryName); - return { - content: [{ type: "text", text: formatComponentsList(components, categoryName) }] - }; - } catch (err) { - return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; - } - } - ); - - // =========================================================================== - // TOOL: read_component - // =========================================================================== - server.registerTool( - "read_component", - { - title: "Read Component", - description: "Read a component's full source. Trigger on 'read/open/show/get from ' or similar. Returns code, category, dependencies, and creation date.", - inputSchema: { - category: z.string().describe("The category name the component belongs to"), - title: z.string().describe("The title/name of the component to read") - } - }, - async ({ category, title }) => { - try { - const categoryName = await fuzzyFindCategory(category.trim()); - - if (!categoryName) { - return { - content: [{ - type: "text", - text: `โŒ **Category "${category}" not found**\n\n๐Ÿ’ก *Try "list categories" to see available options*` - }] - }; - } - - // Try exact match - let component = await getComponent(categoryName, title.trim()); - - // Try fuzzy match if exact fails - if (!component) { - const categoryComponents = await getComponentsByCategory(categoryName); - if (categoryComponents) { - const normalized = normalizeText(title); - const fuzzyMatch = categoryComponents.find(c => - normalizeText(c.title).includes(normalized) || - normalized.includes(normalizeText(c.title)) - ); - if (fuzzyMatch) { - component = await getComponent(categoryName, fuzzyMatch.title); - } - } - } - - if (!component) { - const categoryComponents = await getComponentsByCategory(categoryName); - const suggestions = categoryComponents?.slice(0, 5).map(c => `"${c.title}"`).join(", "); - return { - content: [{ - type: "text", - text: `โŒ **Component "${title}" not found in "${categoryName}"**\n\nAvailable: ${suggestions || "none"}` - }] - }; - } - - return { - content: [{ type: "text", text: formatComponentDetail(component, categoryName) }] - }; - } catch (err) { - return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; - } - } - ); + // Register all tools + registerTools(server); return server; } diff --git a/packages/mcp/src/tools/components.js b/packages/mcp/src/tools/components.js new file mode 100644 index 0000000..7dc179b --- /dev/null +++ b/packages/mcp/src/tools/components.js @@ -0,0 +1,113 @@ +import { z } from "zod"; +import { + getComponentsByCategory, + getComponent, + searchComponents, + getAllCategories, +} from "src/services/catalog"; +import { + formatComponentDetail, + formatSearchResults, + formatComponentsList, +} from "src/utils/formatting"; +import { fuzzyFindCategory } from "src/utils/fuzzy"; + +/** + * Register component-related tools + */ +export function registerComponentTools(server) { + server.registerTool( + "search_components", + { + title: "Search Components", + description: "Search vault components by name or topic.", + inputSchema: z.object({ + query: z.string().describe("Search term for component or category") + }) + }, + async ({ query }) => { + try { + const results = await searchComponents(query.trim()); + return { + content: [{ type: "text", text: formatSearchResults(results, query.trim()) }] + }; + } catch (err) { + return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; + } + } + ); + + server.registerTool( + "read_component", + { + title: "Read Component", + description: "Read a component's full source code and metadata.", + inputSchema: z.object({ + category: z.string().describe("The category name"), + title: z.string().describe("The title/name of the component") + }) + }, + async ({ category, title }) => { + try { + const categoryName = await fuzzyFindCategory(category.trim()); + if (!categoryName) { + return { content: [{ type: "text", text: `โŒ Category "${category}" not found.` }] }; + } + + let component = await getComponent(categoryName, title.trim()); + + // Fuzzy logic remains same + if (!component) { + const categoryComponents = await getComponentsByCategory(categoryName); + const normalized = title.toLowerCase(); + const fuzzyMatch = categoryComponents?.find(c => + c.title.toLowerCase().includes(normalized) + ); + if (fuzzyMatch) component = await getComponent(categoryName, fuzzyMatch.title); + } + + if (!component) { + return { content: [{ type: "text", text: `โŒ Component "${title}" not found.` }] }; + } + + return { content: [{ type: "text", text: formatComponentDetail(component, categoryName) }] }; + } catch (err) { + return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; + } + } + ); + + server.registerTool( + "list_components", + { + title: "List Components", + description: "List components inside a given category. Trigger on requests like 'show components in ui', 'what's in forms', 'list items in buttons'.", + inputSchema: { + category: z.string().describe("The category name to list components from") + } + }, + async ({ category }) => { + try { + const categoryName = await fuzzyFindCategory(category.trim()); + + if (!categoryName) { + const categories = await getAllCategories(); + const suggestions = categories.slice(0, 5).map(c => `"${c.name}"`).join(", "); + return { + content: [{ + type: "text", + text: `โŒ **Category "${category}" not found**\n\nAvailable: ${suggestions || "none"}` + }] + }; + } + + const components = await getComponentsByCategory(categoryName); + return { + content: [{ type: "text", text: formatComponentsList(components, categoryName) }] + }; + } catch (err) { + return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; + } + } + ); +} \ No newline at end of file diff --git a/packages/mcp/src/tools/general.js b/packages/mcp/src/tools/general.js new file mode 100644 index 0000000..9fa8e8d --- /dev/null +++ b/packages/mcp/src/tools/general.js @@ -0,0 +1,50 @@ +import { z } from "zod"; +import { processNaturalQuery } from "src/utils/nlp"; +import { getAllCategories } from "src/services/catalog"; +import { formatCategoriesList } from "src/utils/formatting"; + +/** + * Register general tools related to vault management + */ +export function registerGeneralTools(server) { + server.registerTool( + "ask_composter", + { + title: "Ask Composter", + description: "Ask in plain English to manage vault components.", + inputSchema: z.object({ + query: z.string().describe("Natural language query") + }) + }, + async ({ query }) => { + try { + const result = await processNaturalQuery(query.trim()); + return { content: [{ type: "text", text: result }] }; + } catch (err) { + return { + content: [{ + type: "text", + text: `โŒ **Error:** ${err.message}\n\n๐Ÿ’ก *Make sure you're logged in: \`composter login\`*` + }] + }; + } + } + ); + + server.registerTool( + "list_categories", + { + title: "List Categories", + description: "List all categories in the vault.", + inputSchema: z.object({}) + }, + async () => { + try { + const categories = await getAllCategories(); + return { content: [{ type: "text", text: formatCategoriesList(categories) }] }; + } catch (err) { + return { content: [{ type: "text", text: `โŒ **Error:** ${err.message}` }] }; + } + } + ); +} \ No newline at end of file diff --git a/packages/mcp/src/tools/index.js b/packages/mcp/src/tools/index.js new file mode 100644 index 0000000..d6e255b --- /dev/null +++ b/packages/mcp/src/tools/index.js @@ -0,0 +1,12 @@ +import { registerGeneralTools } from "./general.js"; +import { registerComponentTools } from "./components.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; + +/** + * Registers all tools to the provided MCP server instance + * @param {McpServer} server - The Model Context Protocol server instance + */ +export function registerTools(server) { + registerGeneralTools(server); + registerComponentTools(server); +} \ No newline at end of file From 600dc6642919137a78f6e97c3f94fbc549452500 Mon Sep 17 00:00:00 2001 From: Shreehari Acharya Date: Thu, 22 Jan 2026 13:54:43 +0530 Subject: [PATCH 08/13] Update packages/mcp/src/tools/components.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/mcp/src/tools/components.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mcp/src/tools/components.js b/packages/mcp/src/tools/components.js index 7dc179b..f7c0877 100644 --- a/packages/mcp/src/tools/components.js +++ b/packages/mcp/src/tools/components.js @@ -82,9 +82,9 @@ export function registerComponentTools(server) { { title: "List Components", description: "List components inside a given category. Trigger on requests like 'show components in ui', 'what's in forms', 'list items in buttons'.", - inputSchema: { + inputSchema: z.object({ category: z.string().describe("The category name to list components from") - } + }) }, async ({ category }) => { try { From 005c387cb7bb5a12cb6d7c54ca5ebad58eceb980 Mon Sep 17 00:00:00 2001 From: Shreehari Acharya Date: Thu, 22 Jan 2026 13:55:15 +0530 Subject: [PATCH 09/13] Update packages/mcp/src/utils/fuzzy.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/mcp/src/utils/fuzzy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp/src/utils/fuzzy.js b/packages/mcp/src/utils/fuzzy.js index 150f6d5..0d6ccfa 100644 --- a/packages/mcp/src/utils/fuzzy.js +++ b/packages/mcp/src/utils/fuzzy.js @@ -1,4 +1,4 @@ -import { getAllComponents, getAllCategories } from "../services/catalog" +import { getAllComponents, getAllCategories } from "../services/catalog.js" export function normalizeText(text) { return text.toLowerCase().trim().replace(/\s+/g, " "); From fcd011fcf06f6cb589ac1de65bcfcdffb35c6dbe Mon Sep 17 00:00:00 2001 From: Shreehari Acharya Date: Thu, 22 Jan 2026 13:56:02 +0530 Subject: [PATCH 10/13] Update packages/mcp/src/utils/nlp.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/mcp/src/utils/nlp.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mcp/src/utils/nlp.js b/packages/mcp/src/utils/nlp.js index f33f17d..6d8b0fd 100644 --- a/packages/mcp/src/utils/nlp.js +++ b/packages/mcp/src/utils/nlp.js @@ -2,26 +2,26 @@ import { normalizeText, fuzzyFindCategory, fuzzyFindComponent -} from "./fuzzy"; +} from "./fuzzy.js"; import { getAllCategories, getAllComponents, getComponentsByCategory, getComponent, searchComponents, -} from "../services/catalog"; +} from "../services/catalog.js"; import { formatCategoriesList, formatComponentsList, formatComponentDetail, formatSearchResults, -} from "./formatting" +} from "./formatting.js" import { categoryListPatterns, readPatterns, simpleReadPatterns, searchPatterns, -} from "./regexPatterns" +} from "./regexPatterns.js" /** * Intelligent query parser that understands natural language requests From cdaad8e5ccf017dfeebad60e2caac265bf2ff8e3 Mon Sep 17 00:00:00 2001 From: Shreehari-Acharya Date: Thu, 22 Jan 2026 14:05:06 +0530 Subject: [PATCH 11/13] add copilot suggestions (relative path and .js extension) --- packages/mcp/lib/factory.js | 2 +- packages/mcp/src/tools/components.js | 6 +++--- packages/mcp/src/tools/general.js | 6 +++--- packages/mcp/src/tools/index.js | 3 +-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/mcp/lib/factory.js b/packages/mcp/lib/factory.js index b2e5cfd..e78754a 100644 --- a/packages/mcp/lib/factory.js +++ b/packages/mcp/lib/factory.js @@ -10,7 +10,7 @@ */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerTools } from "src/tools"; +import { registerTools } from "../src/tools/index.js"; /** * Creates and configures the Composter MCP server with all tools diff --git a/packages/mcp/src/tools/components.js b/packages/mcp/src/tools/components.js index f7c0877..e745f8c 100644 --- a/packages/mcp/src/tools/components.js +++ b/packages/mcp/src/tools/components.js @@ -4,13 +4,13 @@ import { getComponent, searchComponents, getAllCategories, -} from "src/services/catalog"; +} from "../services/catalog.js"; import { formatComponentDetail, formatSearchResults, formatComponentsList, -} from "src/utils/formatting"; -import { fuzzyFindCategory } from "src/utils/fuzzy"; +} from "../utils/formatting.js"; +import { fuzzyFindCategory } from "../utils/fuzzy.js"; /** * Register component-related tools diff --git a/packages/mcp/src/tools/general.js b/packages/mcp/src/tools/general.js index 9fa8e8d..ec030fd 100644 --- a/packages/mcp/src/tools/general.js +++ b/packages/mcp/src/tools/general.js @@ -1,7 +1,7 @@ import { z } from "zod"; -import { processNaturalQuery } from "src/utils/nlp"; -import { getAllCategories } from "src/services/catalog"; -import { formatCategoriesList } from "src/utils/formatting"; +import { processNaturalQuery } from "../utils/nlp.js"; +import { getAllCategories } from "../services/catalog.js"; +import { formatCategoriesList } from "../utils/formatting.js"; /** * Register general tools related to vault management diff --git a/packages/mcp/src/tools/index.js b/packages/mcp/src/tools/index.js index d6e255b..cadd2f4 100644 --- a/packages/mcp/src/tools/index.js +++ b/packages/mcp/src/tools/index.js @@ -1,10 +1,9 @@ import { registerGeneralTools } from "./general.js"; import { registerComponentTools } from "./components.js"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; /** * Registers all tools to the provided MCP server instance - * @param {McpServer} server - The Model Context Protocol server instance + * @param {import("@modelcontextprotocol/sdk/server/mcp.js").McpServer} server - The Model Context Protocol server instance */ export function registerTools(server) { registerGeneralTools(server); From e0ce77027e8881e064e1aa5502f9cf3cc2e9da1e Mon Sep 17 00:00:00 2001 From: Shreehari Acharya Date: Thu, 22 Jan 2026 14:06:55 +0530 Subject: [PATCH 12/13] Update packages/mcp/jsconfig.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/mcp/jsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mcp/jsconfig.json b/packages/mcp/jsconfig.json index 740f888..e5fe0d4 100644 --- a/packages/mcp/jsconfig.json +++ b/packages/mcp/jsconfig.json @@ -5,7 +5,6 @@ "checkJs": true, "jsx": "preserve", "moduleResolution": "node", - "baseUrl": "./", "skipLibCheck": true, "noLib": false }, From 7a6d1890da10a2e58c9c913da2b766bef1616dad Mon Sep 17 00:00:00 2001 From: Shreehari-Acharya Date: Fri, 23 Jan 2026 19:05:35 +0530 Subject: [PATCH 13/13] fix backend api url and add .js extension to api.js imports --- packages/mcp/lib/auth.js | 2 +- packages/mcp/src/services/api.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mcp/lib/auth.js b/packages/mcp/lib/auth.js index 528a7aa..6955aca 100644 --- a/packages/mcp/lib/auth.js +++ b/packages/mcp/lib/auth.js @@ -16,7 +16,7 @@ export function getBaseUrl() { return "http://localhost:3000/api"; } // Default to production - return "https://composter.onrender.com/api"; + return "https://composter-api.vercel.app/api"; } // Load session from CLI's session file diff --git a/packages/mcp/src/services/api.js b/packages/mcp/src/services/api.js index 3499ace..a8609f6 100644 --- a/packages/mcp/src/services/api.js +++ b/packages/mcp/src/services/api.js @@ -1,4 +1,4 @@ -import { getAuthToken, getBaseUrl } from "../../lib/auth"; +import { getAuthToken, getBaseUrl } from "../../lib/auth.js"; /** * Makes authenticated API requests to the Composter backend