From b17280c5b1b10bfb21dc70e69012f67923b9e3c3 Mon Sep 17 00:00:00 2001 From: arre_ankit Date: Thu, 3 Apr 2025 04:45:08 +0530 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=93=A6=20NEW:=20Docs=20MCP=20Server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/package.json | 2 + packages/cli/src/docs-mcp-server/docs.ts | 46 ++++++ packages/cli/src/docs-mcp-server/index.ts | 177 ++++++++++++++++++++++ packages/cli/src/index.ts | 5 + packages/cli/src/utils/cli.ts | 5 +- 5 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 packages/cli/src/docs-mcp-server/docs.ts create mode 100644 packages/cli/src/docs-mcp-server/index.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index b241a8a..c91811d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -51,6 +51,7 @@ "@clack/prompts": "^0.7.0", "@hono/node-server": "^1.13.1", "@hono/zod-openapi": "^0.16.0", + "@modelcontextprotocol/sdk": "^1.8.0", "@sindresorhus/slugify": "^2.2.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", @@ -71,6 +72,7 @@ "get-package-json-file": "^2.0.0", "hono": "^4.5.11", "js-tiktoken": "^1.0.14", + "jsdom": "^24.1.0", "log-symbols": "^7.0.0", "lowdb": "^7.0.1", "meow": "^13.2.0", diff --git a/packages/cli/src/docs-mcp-server/docs.ts b/packages/cli/src/docs-mcp-server/docs.ts new file mode 100644 index 0000000..ce15d32 --- /dev/null +++ b/packages/cli/src/docs-mcp-server/docs.ts @@ -0,0 +1,46 @@ +import { JSDOM } from 'jsdom'; +// Fetches a list of all the docs on the langbase website +export async function fetchDocsList() { + try { + const response = await fetch('https://langbase.com/docs/llms.txt'); + + if (!response.ok) { + throw new Error('Failed to fetch docs'); + } + + const text = await response.text(); + return text; + + } catch (error) { + throw new Error('Failed to fetch docs ' + JSON.stringify(error)); + } +} + + +// Helper function to fetch and convert a blog post to markdown +export async function fetchDocsPost(url: string): Promise { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error('Failed to fetch blog post'); + } + + const html = await response.text(); + + const dom = new JSDOM(html); + const document = dom.window.document; + // Remove Next.js initialization code + const scripts = document.querySelectorAll('script'); + scripts.forEach(script => script.remove()); + + // Get the main content + const content = document.body.textContent?.trim() || ''; + if (!content) { + throw new Error('No content found in docs'); + } + + return content; + } catch (error) { + throw new Error(`Failed to fetch docs: ${error instanceof Error ? error.message : 'Unknown error'}`); + } + } \ No newline at end of file diff --git a/packages/cli/src/docs-mcp-server/index.ts b/packages/cli/src/docs-mcp-server/index.ts new file mode 100644 index 0000000..a651658 --- /dev/null +++ b/packages/cli/src/docs-mcp-server/index.ts @@ -0,0 +1,177 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import { fetchDocsList, fetchDocsPost } from "./docs" + + +export async function docsMcpServer() { + // Create server instance with resource support + const server = new McpServer({ + name: "langbase-docs-server", + version: "1.0.0" + }); + + + // SDK-specific tool + server.tool( + "docs-route-finder", + "Searches through all available documentation routes and returns relevant paths based on the user's query. This tool helps navigate the documentation by finding the most appropriate sections that match the search criteria.", + { + query: z.string().describe(`A refined search term extracted from the user's question. + For example, if user asks 'How do I create a pipe?', the query would be 'SDK Pipe'. + This should be the specific concept or topic to search for in the documentation. + Treat keyword add as create if user ask for Eg. 'How do I add memory to pipe?' the query should be 'create memory'`), + }, + async ({ query }) => { + const docs = await fetchDocsList() + // search through the docs and return the most relevent path based on the query + // Split docs into lines and create an array of documentation entries + const docLines = docs.split('\n').filter(line => line.trim()); + + // Create a simple scoring system for relevance + const getRelevanceScore = (line: string, searchQuery: string) => { + const lowerLine = line.toLowerCase(); + const lowerQuery = searchQuery.toLowerCase(); + // Higher score for exact matches + if (lowerLine.includes(lowerQuery)) { + return 3; + } + + // Score based on word matches + const queryWords = lowerQuery.split(' '); + return queryWords.reduce((score, word) => { + return score + (lowerLine.includes(word) ? 1 : 0); + }, 0); + }; + + // Score and sort the documentation entries + const scoredDocs = docLines + .map(line => ({ + line, + score: getRelevanceScore(line, query) + })) + .sort((a, b) => b.score - a.score) + .filter(doc => doc.score > 0) + .slice(0, 3); // Get top 3 most relevant results + + + if (scoredDocs.length === 0) { + return { + content: [{ + type: "text", + text: "No relevant documentation found for the query: " + query + }] + }; + } + + // Extract URLs and create formatted response + const results = scoredDocs.map(doc => doc.line).join('\n'); + + return { + content: [ + { + type: "text", + text: results, + }, + ], + }; + } + ); + + + server.tool( + "sdk-documentation-fetcher", + "Fetches detailed SDK documentation, specializing in implementation guides for core features like pipes, memory, and tools. This is the primary source for the latest SDK documentation and should be consulted first for questions about creating or implementing SDK components. Use this tool for detailed step-by-step instructions on building pipes, configuring memory systems, and developing custom tools.", + { + url: z.string().describe("URL of a specific SDK page to fetch. Format: /sdk/..."), + }, + async ({ url }) => { + const content = await fetchDocsPost(`https://langbase.com/docs${url}`); + return { + content: [ + { + type: "text", + text: content, + }, + ], + }; + } + ); + + server.tool( + "examples-tool", + "Fetches code examples and sample implementations from the documentation. Use this tool when users specifically request examples, sample code, or implementation demonstrations. This tool provides practical code snippets and complete working examples that demonstrate how to implement various features.", + { + url: z.string().describe("URL of a specific examples page to fetch. Format: /examples/..."), + }, + async ({ url }) => { + const content = await fetchDocsPost(`https://langbase.com/docs${url}`); + return { + content: [ + { + type: "text", + text: content, + }, + ], + }; + } + ); + + server.tool( + "guide-tool", + "Fetches detailed guides and tutorials from the documentation. Use this tool when users explicitly request guides, tutorials, or how-to content. This tool provides step-by-step instructions and practical examples for implementing various features.", + { + url: z.string().describe("URL of a specific guide page to fetch. Format: /guides/..."), + }, + async ({ url }) => { + const content = await fetchDocsPost(`https://langbase.com/docs${url}`); + return { + content: [ + { + type: "text", + text: content, + }, + ], + }; + } + ); + + server.tool( + "api-reference-tool", + "Fetches API reference documentation. Use this tool ONLY when the user explicitly asks about API endpoints, REST API calls, or programmatically creating/updating/deleting resources (like pipes, memory, etc.) through the API interface. For general SDK implementation questions, use the sdk-documentation-fetcher instead.", + { + url: z.string().describe("URL of a specific api-reference page to fetch. Format: /api-reference/..."), + }, + async ({ url }) => { + const content = await fetchDocsPost(`https://langbase.com/docs${url}`); + return { + content: [ + { + type: "text", + text: content, + }, + ], + }; + } + ); + + + async function main() { + const transport = new StdioServerTransport(); + + try { + await server.connect(transport); + console.error("Langbase service MCP Server running on stdio"); + } catch (error) { + console.error("Error connecting to transport:", error); + process.exit(1); + } + } + + main().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); + }); + +} + diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index bacf0da..5e27458 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -2,6 +2,7 @@ import { auth } from './auth'; import { build } from './build'; import { deploy } from './deploy'; +import { docsMcpServer } from './docs-mcp-server'; import cli from './utils/cli'; import debugMode from './utils/debug-mode'; import cliInit from './utils/init'; @@ -42,4 +43,8 @@ const flag = (flg: string): boolean => Boolean(flags[flg]); await deploy({ isDev, agent, filePath, apiKey }); } + + if (command('docs-mcp-server')) { + await docsMcpServer(); + } })(); diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts index c35d172..d58001c 100644 --- a/packages/cli/src/utils/cli.ts +++ b/packages/cli/src/utils/cli.ts @@ -33,11 +33,11 @@ const flags = { const commands = { auth: { desc: `Authenticate with Langbase` }, deploy: { desc: `Deploy a script to Langbase` }, - help: { desc: `Print help info` } + help: { desc: `Print help info` }, }; const helpText = meowHelp({ - name: `baseai`, + name: `langbase`, flags, commands, desc: false, @@ -53,4 +53,5 @@ const options = { flags }; + export default meow(helpText, options); From 112dc18b1a8d0213df321dfacf07577c4d6b3357 Mon Sep 17 00:00:00 2001 From: arre_ankit Date: Thu, 3 Apr 2025 04:59:05 +0530 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20README.md=20updat?= =?UTF-8?q?ed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/README.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 7d30af0..b05e305 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,7 +1,35 @@ -## Langbase CLI +# Langbase CLI Langbase CLI is a command line interface for Langbase. It allows you to interact with Langbase from the command line. -## Documentation +# Documentation Please follow the [Langbase documentation](https://langbase.com/docs) for the latest information. + +## Installation + +```bash +npm install -g @langbase/cli +``` + +# Usage + +### DOCS MCP Server + +Integrate the Langbase Docs MCP server into Cursor IDE. + +#### Cursor + +``` +{ + "mcpServers": { + "Langbase": { + "command": "npx", + "args": ["@langbase/cli","docs-mcp-server"] + } + } +} +``` + + + From b8c7181427df82f953f665c783c428f9ec7db33f Mon Sep 17 00:00:00 2001 From: arre_ankit Date: Fri, 4 Apr 2025 20:25:15 +0530 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20formating?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/README.md | 2 +- packages/cli/src/docs-mcp-server/docs.ts | 76 ++++--- packages/cli/src/docs-mcp-server/index.ts | 255 ++++++++++------------ packages/cli/src/index.ts | 5 +- 4 files changed, 162 insertions(+), 176 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index b05e305..8616ab8 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -9,7 +9,7 @@ Please follow the [Langbase documentation](https://langbase.com/docs) for the la ## Installation ```bash -npm install -g @langbase/cli +npx @langbase/cli ``` # Usage diff --git a/packages/cli/src/docs-mcp-server/docs.ts b/packages/cli/src/docs-mcp-server/docs.ts index ce15d32..52aab33 100644 --- a/packages/cli/src/docs-mcp-server/docs.ts +++ b/packages/cli/src/docs-mcp-server/docs.ts @@ -1,46 +1,56 @@ import { JSDOM } from 'jsdom'; -// Fetches a list of all the docs on the langbase website + +/** + * Fetches a list of all the docs on the langbase website + * @returns {Promise} A promise that resolves to a string of all the docs on the langbase website + */ + export async function fetchDocsList() { - try { - const response = await fetch('https://langbase.com/docs/llms.txt'); + try { + const response = await fetch('https://langbase.com/docs/llms.txt'); - if (!response.ok) { - throw new Error('Failed to fetch docs'); - } + if (!response.ok) { + throw new Error('Failed to fetch docs'); + } - const text = await response.text(); - return text; + const text = await response.text(); + return text; - } catch (error) { - throw new Error('Failed to fetch docs ' + JSON.stringify(error)); - } + } catch (error) { + throw new Error('Failed to fetch docs ' + JSON.stringify(error)); + } } +/** + * Fetches and converts a blog post to markdown + * @param {string} url - The URL of the blog post to fetch + * @returns {Promise} A promise that resolves to a string of the blog post in markdown format + */ -// Helper function to fetch and convert a blog post to markdown export async function fetchDocsPost(url: string): Promise { - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error('Failed to fetch blog post'); - } + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error('Failed to fetch blog post'); + } - const html = await response.text(); + const html = await response.text(); - const dom = new JSDOM(html); - const document = dom.window.document; - // Remove Next.js initialization code - const scripts = document.querySelectorAll('script'); - scripts.forEach(script => script.remove()); - - // Get the main content - const content = document.body.textContent?.trim() || ''; - if (!content) { - throw new Error('No content found in docs'); - } - - return content; + const dom = new JSDOM(html); + const document = dom.window.document; + // Remove Next.js initialization code + const scripts = document.querySelectorAll('script'); + scripts.forEach(script => script.remove()); + + // Get the main content + const content = document.body.textContent?.trim() || ''; + if (!content) { + throw new Error('No content found in docs'); + } + + return content; + } catch (error) { - throw new Error(`Failed to fetch docs: ${error instanceof Error ? error.message : 'Unknown error'}`); + throw new Error(`Failed to fetch docs: ${error instanceof Error ? error.message : 'Unknown error'}`); } - } \ No newline at end of file +} \ No newline at end of file diff --git a/packages/cli/src/docs-mcp-server/index.ts b/packages/cli/src/docs-mcp-server/index.ts index a651658..97eaea7 100644 --- a/packages/cli/src/docs-mcp-server/index.ts +++ b/packages/cli/src/docs-mcp-server/index.ts @@ -5,173 +5,146 @@ import { fetchDocsList, fetchDocsPost } from "./docs" export async function docsMcpServer() { - // Create server instance with resource support const server = new McpServer({ - name: "langbase-docs-server", - version: "1.0.0" + name: "langbase-docs-server", + version: "1.0.0" }); - - // SDK-specific tool server.tool( - "docs-route-finder", - "Searches through all available documentation routes and returns relevant paths based on the user's query. This tool helps navigate the documentation by finding the most appropriate sections that match the search criteria.", - { - query: z.string().describe(`A refined search term extracted from the user's question. - For example, if user asks 'How do I create a pipe?', the query would be 'SDK Pipe'. - This should be the specific concept or topic to search for in the documentation. - Treat keyword add as create if user ask for Eg. 'How do I add memory to pipe?' the query should be 'create memory'`), - }, - async ({ query }) => { - const docs = await fetchDocsList() - // search through the docs and return the most relevent path based on the query - // Split docs into lines and create an array of documentation entries - const docLines = docs.split('\n').filter(line => line.trim()); + "docs-route-finder", + "Searches through all available documentation routes and returns relevant paths based on the user's query. This tool helps navigate the documentation by finding the most appropriate sections that match the search criteria.", + { query: z.string().describe(`A refined search term extracted from the user's question. + For example, if user asks 'How do I create a pipe?', the query would be 'SDK Pipe'. + This should be the specific concept or topic to search for in the documentation. + Treat keyword add as create if user ask for Eg. 'How do I add memory to pipe?' the query should be 'create memory'`) + }, + async ({ query }) => { + const docs = await fetchDocsList() + // search through the docs and return the most relevent path based on the query + const docLines = docs.split('\n').filter(line => line.trim()); - // Create a simple scoring system for relevance - const getRelevanceScore = (line: string, searchQuery: string) => { - const lowerLine = line.toLowerCase(); - const lowerQuery = searchQuery.toLowerCase(); - // Higher score for exact matches - if (lowerLine.includes(lowerQuery)) { - return 3; - } - // Score based on word matches - const queryWords = lowerQuery.split(' '); - return queryWords.reduce((score, word) => { - return score + (lowerLine.includes(word) ? 1 : 0); - }, 0); - }; - - // Score and sort the documentation entries - const scoredDocs = docLines - .map(line => ({ - line, - score: getRelevanceScore(line, query) - })) - .sort((a, b) => b.score - a.score) - .filter(doc => doc.score > 0) - .slice(0, 3); // Get top 3 most relevant results - + const getRelevanceScore = (line: string, searchQuery: string) => { + const lowerLine = line.toLowerCase(); + const lowerQuery = searchQuery.toLowerCase(); + // Higher score for exact matches + if (lowerLine.includes(lowerQuery)) { + return 3; + } + + // Score based on word matches + const queryWords = lowerQuery.split(' '); + return queryWords.reduce((score, word) => { + return score + (lowerLine.includes(word) ? 1 : 0); + }, 0); + }; + + // Score and sort the documentation entries + const scoredDocs = docLines + .map(line => ({ + line, + score: getRelevanceScore(line, query) + })) + .sort((a, b) => b.score - a.score) + .filter(doc => doc.score > 0) + .slice(0, 3); // Get top 3 most relevant results + - if (scoredDocs.length === 0) { - return { - content: [{ - type: "text", - text: "No relevant documentation found for the query: " + query - }] - }; + if (scoredDocs.length === 0) { + return { + content: [{ + type: "text", + text: "No relevant documentation found for the query: " + query + }] + }; + } + + const results = scoredDocs.map(doc => doc.line).join('\n'); + + return { + content: [{ + type: "text", + text: results + }] + }; } - - // Extract URLs and create formatted response - const results = scoredDocs.map(doc => doc.line).join('\n'); - - return { - content: [ - { - type: "text", - text: results, - }, - ], - }; - } ); - server.tool( - "sdk-documentation-fetcher", - "Fetches detailed SDK documentation, specializing in implementation guides for core features like pipes, memory, and tools. This is the primary source for the latest SDK documentation and should be consulted first for questions about creating or implementing SDK components. Use this tool for detailed step-by-step instructions on building pipes, configuring memory systems, and developing custom tools.", - { - url: z.string().describe("URL of a specific SDK page to fetch. Format: /sdk/..."), - }, - async ({ url }) => { - const content = await fetchDocsPost(`https://langbase.com/docs${url}`); - return { - content: [ - { - type: "text", - text: content, - }, - ], - }; - } + "sdk-documentation-fetcher", + "Fetches detailed SDK documentation, specializing in implementation guides for core features like pipes, memory, and tools. This is the primary source for the latest SDK documentation and should be consulted first for questions about creating or implementing SDK components. Use this tool for detailed step-by-step instructions on building pipes, configuring memory systems, and developing custom tools.", + { url: z.string().describe("URL of a specific SDK page to fetch. Format: /sdk/...") }, + async ({ url }) => { + const content = await fetchDocsPost(`https://langbase.com/docs${url}`); + return { + content: [{ + type: "text", + text: content + }] + }; + } ); server.tool( - "examples-tool", - "Fetches code examples and sample implementations from the documentation. Use this tool when users specifically request examples, sample code, or implementation demonstrations. This tool provides practical code snippets and complete working examples that demonstrate how to implement various features.", - { - url: z.string().describe("URL of a specific examples page to fetch. Format: /examples/..."), - }, - async ({ url }) => { - const content = await fetchDocsPost(`https://langbase.com/docs${url}`); - return { - content: [ - { - type: "text", - text: content, - }, - ], - }; - } + "examples-tool", + "Fetches code examples and sample implementations from the documentation. Use this tool when users specifically request examples, sample code, or implementation demonstrations. This tool provides practical code snippets and complete working examples that demonstrate how to implement various features.", + { url: z.string().describe("URL of a specific examples page to fetch. Format: /examples/...") }, + async ({ url }) => { + const content = await fetchDocsPost(`https://langbase.com/docs${url}`); + return { + content: [{ + type: "text", + text: content + }] + }; + } ); server.tool( - "guide-tool", - "Fetches detailed guides and tutorials from the documentation. Use this tool when users explicitly request guides, tutorials, or how-to content. This tool provides step-by-step instructions and practical examples for implementing various features.", - { - url: z.string().describe("URL of a specific guide page to fetch. Format: /guides/..."), - }, - async ({ url }) => { - const content = await fetchDocsPost(`https://langbase.com/docs${url}`); - return { - content: [ - { - type: "text", - text: content, - }, - ], - }; - } + "guide-tool", + "Fetches detailed guides and tutorials from the documentation. Use this tool when users explicitly request guides, tutorials, or how-to content. This tool provides step-by-step instructions and practical examples for implementing various features.", + { url: z.string().describe("URL of a specific guide page to fetch. Format: /guides/...") }, + async ({ url }) => { + const content = await fetchDocsPost(`https://langbase.com/docs${url}`); + return { + content: [{ + type: "text", + text: content + }] + }; + } ); server.tool( - "api-reference-tool", - "Fetches API reference documentation. Use this tool ONLY when the user explicitly asks about API endpoints, REST API calls, or programmatically creating/updating/deleting resources (like pipes, memory, etc.) through the API interface. For general SDK implementation questions, use the sdk-documentation-fetcher instead.", - { - url: z.string().describe("URL of a specific api-reference page to fetch. Format: /api-reference/..."), - }, - async ({ url }) => { - const content = await fetchDocsPost(`https://langbase.com/docs${url}`); - return { - content: [ - { - type: "text", - text: content, - }, - ], - }; - } + "api-reference-tool", + "Fetches API reference documentation. Use this tool ONLY when the user explicitly asks about API endpoints, REST API calls, or programmatically creating/updating/deleting resources (like pipes, memory, etc.) through the API interface. For general SDK implementation questions, use the sdk-documentation-fetcher instead.", + { url: z.string().describe("URL of a specific api-reference page to fetch. Format: /api-reference/...") }, + async ({ url }) => { + const content = await fetchDocsPost(`https://langbase.com/docs${url}`); + return { + content: [{ + type: "text", + text: content, + }] + }; + } ); - async function main() { - const transport = new StdioServerTransport(); - - try { - await server.connect(transport); - console.error("Langbase service MCP Server running on stdio"); - } catch (error) { - console.error("Error connecting to transport:", error); - process.exit(1); - } + const transport = new StdioServerTransport(); + + try { + await server.connect(transport); + console.error("Langbase service MCP Server running on stdio"); + } catch (error) { + console.error("Error connecting to transport:", error); + process.exit(1); + } } main().catch((error) => { - console.error("Fatal error in main():", error); - process.exit(1); + console.error("Fatal error in main():", error); + process.exit(1); }); - } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 5e27458..aef60f3 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -17,7 +17,10 @@ const command = (cmd: string): boolean => input.includes(cmd); const flag = (flg: string): boolean => Boolean(flags[flg]); (async () => { - await cliInit({ clear }); + // Skip welcome message for docs-mcp-server command + if (!command('docs-mcp-server')) { + await cliInit({ clear }); + } if (debug) debugMode(cli); if (command('help')) { From ac35659acdb0d54724a0df4539c82c428983dbb6 Mon Sep 17 00:00:00 2001 From: msaaddev Date: Mon, 7 Apr 2025 13:09:43 +0200 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Review=20by=20SI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/README.md | 3 - packages/cli/package.json | 1 + packages/cli/src/docs-mcp-server/docs.ts | 58 +-- packages/cli/src/docs-mcp-server/index.ts | 333 ++++++++++------- pnpm-lock.yaml | 435 ++++++++++++++++++++++ 5 files changed, 664 insertions(+), 166 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 8616ab8..6e9b2ba 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -30,6 +30,3 @@ Integrate the Langbase Docs MCP server into Cursor IDE. } } ``` - - - diff --git a/packages/cli/package.json b/packages/cli/package.json index c91811d..e6f10fa 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -92,6 +92,7 @@ "devDependencies": { "@langbase/eslint-config": "workspace:*", "@langbase/tsconfig": "workspace:*", + "@types/jsdom": "^21.1.7", "@types/node": "^22.6.1", "tsup": "^8.3.0", "tsx": "^4.19.1", diff --git a/packages/cli/src/docs-mcp-server/docs.ts b/packages/cli/src/docs-mcp-server/docs.ts index 52aab33..e7166b4 100644 --- a/packages/cli/src/docs-mcp-server/docs.ts +++ b/packages/cli/src/docs-mcp-server/docs.ts @@ -2,55 +2,57 @@ import { JSDOM } from 'jsdom'; /** * Fetches a list of all the docs on the langbase website + * + * * @returns {Promise} A promise that resolves to a string of all the docs on the langbase website */ - export async function fetchDocsList() { try { - const response = await fetch('https://langbase.com/docs/llms.txt'); - - if (!response.ok) { - throw new Error('Failed to fetch docs'); - } - - const text = await response.text(); - return text; - - } catch (error) { - throw new Error('Failed to fetch docs ' + JSON.stringify(error)); - } + const response = await fetch('https://langbase.com/docs/llms.txt'); + if (!response.ok) { + throw new Error('Failed to fetch docs'); + } + + const text = await response.text(); + return text; + } catch (error) { + throw new Error('Failed to fetch docs ' + JSON.stringify(error)); + } } /** * Fetches and converts a blog post to markdown + * + * * @param {string} url - The URL of the blog post to fetch * @returns {Promise} A promise that resolves to a string of the blog post in markdown format */ - export async function fetchDocsPost(url: string): Promise { try { - const response = await fetch(url); - if (!response.ok) { - throw new Error('Failed to fetch blog post'); - } - - const html = await response.text(); - + const response = await fetch(url); + if (!response.ok) { + throw new Error('Failed to fetch blog post'); + } + + const html = await response.text(); + const dom = new JSDOM(html); const document = dom.window.document; + // Remove Next.js initialization code const scripts = document.querySelectorAll('script'); scripts.forEach(script => script.remove()); - + // Get the main content const content = document.body.textContent?.trim() || ''; if (!content) { throw new Error('No content found in docs'); } - - return content; - } catch (error) { - throw new Error(`Failed to fetch docs: ${error instanceof Error ? error.message : 'Unknown error'}`); - } -} \ No newline at end of file + return content; + } catch (error) { + throw new Error( + `Failed to fetch docs: ${error instanceof Error ? error.message : 'Something went wrong. Please try again.'}` + ); + } +} diff --git a/packages/cli/src/docs-mcp-server/index.ts b/packages/cli/src/docs-mcp-server/index.ts index 97eaea7..6e599fa 100644 --- a/packages/cli/src/docs-mcp-server/index.ts +++ b/packages/cli/src/docs-mcp-server/index.ts @@ -1,150 +1,213 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; -import { fetchDocsList, fetchDocsPost } from "./docs" - +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { z } from 'zod'; +import { fetchDocsList, fetchDocsPost } from './docs'; +/** + * Starts the Documentation Message Control Protocol (MCP) server for Langbase. + * + * This function initializes and runs an MCP server that provides tools for + * fetching and searching Langbase documentation. The server includes several tools: + * + * - docs-route-finder: Searches through documentation routes based on user query + * - sdk-documentation-fetcher: Fetches detailed SDK documentation + * - examples-tool: Fetches code examples and sample implementations + * - guide-tool: Fetches detailed guides and tutorials + * - api-reference-tool: Fetches API reference documentation + * + * The server communicates using a standard I/O transport mechanism. + * + * @returns {Promise} A promise that resolves when the server completes execution + * + * @throws Will throw an error if connecting to the transport fails + * @throws Will throw an error if there is a fatal error in the main execution + */ export async function docsMcpServer() { - const server = new McpServer({ - name: "langbase-docs-server", - version: "1.0.0" - }); + const server = new McpServer({ + name: 'langbase-docs-server', + version: '0.1.0' + }); - server.tool( - "docs-route-finder", - "Searches through all available documentation routes and returns relevant paths based on the user's query. This tool helps navigate the documentation by finding the most appropriate sections that match the search criteria.", - { query: z.string().describe(`A refined search term extracted from the user's question. - For example, if user asks 'How do I create a pipe?', the query would be 'SDK Pipe'. + server.tool( + 'docs-route-finder', + "Searches through all available documentation routes and returns relevant paths based on the user's query. This tool helps navigate the documentation by finding the most appropriate sections that match the search criteria.", + { + query: z.string() + .describe(`A refined search term extracted from the user's question. + For example, if user asks 'How do I create a pipe?', the query would be 'SDK Pipe'. This should be the specific concept or topic to search for in the documentation. Treat keyword add as create if user ask for Eg. 'How do I add memory to pipe?' the query should be 'create memory'`) - }, - async ({ query }) => { - const docs = await fetchDocsList() - // search through the docs and return the most relevent path based on the query - const docLines = docs.split('\n').filter(line => line.trim()); + }, + async ({ query }) => { + const docs = await fetchDocsList(); + // search through the docs and return the most relevent path based on the query + const docLines = docs.split('\n').filter(line => line.trim()); - - const getRelevanceScore = (line: string, searchQuery: string) => { - const lowerLine = line.toLowerCase(); - const lowerQuery = searchQuery.toLowerCase(); - // Higher score for exact matches - if (lowerLine.includes(lowerQuery)) { - return 3; - } - - // Score based on word matches - const queryWords = lowerQuery.split(' '); - return queryWords.reduce((score, word) => { - return score + (lowerLine.includes(word) ? 1 : 0); - }, 0); - }; - - // Score and sort the documentation entries - const scoredDocs = docLines - .map(line => ({ - line, - score: getRelevanceScore(line, query) - })) - .sort((a, b) => b.score - a.score) - .filter(doc => doc.score > 0) - .slice(0, 3); // Get top 3 most relevant results - + const getRelevanceScore = (line: string, searchQuery: string) => { + const lowerLine = line.toLowerCase(); + const lowerQuery = searchQuery.toLowerCase(); + // Higher score for exact matches + if (lowerLine.includes(lowerQuery)) { + return 3; + } - if (scoredDocs.length === 0) { - return { - content: [{ - type: "text", - text: "No relevant documentation found for the query: " + query - }] - }; - } - - const results = scoredDocs.map(doc => doc.line).join('\n'); - - return { - content: [{ - type: "text", - text: results - }] - }; - } - ); + // Score based on word matches + const queryWords = lowerQuery.split(' '); + return queryWords.reduce((score, word) => { + return score + (lowerLine.includes(word) ? 1 : 0); + }, 0); + }; - server.tool( - "sdk-documentation-fetcher", - "Fetches detailed SDK documentation, specializing in implementation guides for core features like pipes, memory, and tools. This is the primary source for the latest SDK documentation and should be consulted first for questions about creating or implementing SDK components. Use this tool for detailed step-by-step instructions on building pipes, configuring memory systems, and developing custom tools.", - { url: z.string().describe("URL of a specific SDK page to fetch. Format: /sdk/...") }, - async ({ url }) => { - const content = await fetchDocsPost(`https://langbase.com/docs${url}`); - return { - content: [{ - type: "text", - text: content - }] - }; - } - ); + // Score and sort the documentation entries + const scoredDocs = docLines + .map(line => ({ + line, + score: getRelevanceScore(line, query) + })) + .sort((a, b) => b.score - a.score) + .filter(doc => doc.score > 0) + .slice(0, 3); // Get top 3 most relevant results - server.tool( - "examples-tool", - "Fetches code examples and sample implementations from the documentation. Use this tool when users specifically request examples, sample code, or implementation demonstrations. This tool provides practical code snippets and complete working examples that demonstrate how to implement various features.", - { url: z.string().describe("URL of a specific examples page to fetch. Format: /examples/...") }, - async ({ url }) => { - const content = await fetchDocsPost(`https://langbase.com/docs${url}`); - return { - content: [{ - type: "text", - text: content - }] - }; - } - ); + if (scoredDocs.length === 0) { + return { + content: [ + { + type: 'text', + text: + 'No relevant documentation found for the query: ' + + query + } + ] + }; + } - server.tool( - "guide-tool", - "Fetches detailed guides and tutorials from the documentation. Use this tool when users explicitly request guides, tutorials, or how-to content. This tool provides step-by-step instructions and practical examples for implementing various features.", - { url: z.string().describe("URL of a specific guide page to fetch. Format: /guides/...") }, - async ({ url }) => { - const content = await fetchDocsPost(`https://langbase.com/docs${url}`); - return { - content: [{ - type: "text", - text: content - }] - }; - } - ); + const results = scoredDocs.map(doc => doc.line).join('\n'); - server.tool( - "api-reference-tool", - "Fetches API reference documentation. Use this tool ONLY when the user explicitly asks about API endpoints, REST API calls, or programmatically creating/updating/deleting resources (like pipes, memory, etc.) through the API interface. For general SDK implementation questions, use the sdk-documentation-fetcher instead.", - { url: z.string().describe("URL of a specific api-reference page to fetch. Format: /api-reference/...") }, - async ({ url }) => { - const content = await fetchDocsPost(`https://langbase.com/docs${url}`); - return { - content: [{ - type: "text", - text: content, - }] - }; - } - ); + return { + content: [ + { + type: 'text', + text: results + } + ] + }; + } + ); - async function main() { - const transport = new StdioServerTransport(); + server.tool( + 'sdk-documentation-fetcher', + 'Fetches detailed SDK documentation, specializing in implementation guides for core features like pipes, memory, and tools. This is the primary source for the latest SDK documentation and should be consulted first for questions about creating or implementing SDK components. Use this tool for detailed step-by-step instructions on building pipes, configuring memory systems, and developing custom tools.', + { + url: z + .string() + .describe( + 'URL of a specific SDK page to fetch. Format: /sdk/...' + ) + }, + async ({ url }) => { + const content = await fetchDocsPost( + `https://langbase.com/docs${url}` + ); + return { + content: [ + { + type: 'text', + text: content + } + ] + }; + } + ); - try { - await server.connect(transport); - console.error("Langbase service MCP Server running on stdio"); - } catch (error) { - console.error("Error connecting to transport:", error); - process.exit(1); - } - } + server.tool( + 'examples-tool', + 'Fetches code examples and sample implementations from the documentation. Use this tool when users specifically request examples, sample code, or implementation demonstrations. This tool provides practical code snippets and complete working examples that demonstrate how to implement various features.', + { + url: z + .string() + .describe( + 'URL of a specific examples page to fetch. Format: /examples/...' + ) + }, + async ({ url }) => { + const content = await fetchDocsPost( + `https://langbase.com/docs${url}` + ); + return { + content: [ + { + type: 'text', + text: content + } + ] + }; + } + ); - main().catch((error) => { - console.error("Fatal error in main():", error); - process.exit(1); - }); -} + server.tool( + 'guide-tool', + 'Fetches detailed guides and tutorials from the documentation. Use this tool when users explicitly request guides, tutorials, or how-to content. This tool provides step-by-step instructions and practical examples for implementing various features.', + { + url: z + .string() + .describe( + 'URL of a specific guide page to fetch. Format: /guides/...' + ) + }, + async ({ url }) => { + const content = await fetchDocsPost( + `https://langbase.com/docs${url}` + ); + return { + content: [ + { + type: 'text', + text: content + } + ] + }; + } + ); + + server.tool( + 'api-reference-tool', + 'Fetches API reference documentation. Use this tool ONLY when the user explicitly asks about API endpoints, REST API calls, or programmatically creating/updating/deleting resources (like pipes, memory, etc.) through the API interface. For general SDK implementation questions, use the sdk-documentation-fetcher instead.', + { + url: z + .string() + .describe( + 'URL of a specific api-reference page to fetch. Format: /api-reference/...' + ) + }, + async ({ url }) => { + const content = await fetchDocsPost( + `https://langbase.com/docs${url}` + ); + return { + content: [ + { + type: 'text', + text: content + } + ] + }; + } + ); + async function main() { + const transport = new StdioServerTransport(); + + try { + await server.connect(transport); + console.error('Langbase service MCP Server running on stdio'); + } catch (error) { + console.error('Error connecting to transport:', error); + process.exit(1); + } + } + + main().catch(error => { + console.error('Something went wrong:', error); + process.exit(1); + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f54b925..2596ba5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -141,6 +141,9 @@ importers: '@hono/zod-openapi': specifier: ^0.16.0 version: 0.16.4(hono@4.7.2)(zod@3.24.1) + '@modelcontextprotocol/sdk': + specifier: ^1.8.0 + version: 1.8.0 '@sindresorhus/slugify': specifier: ^2.2.1 version: 2.2.1 @@ -201,6 +204,9 @@ importers: js-tiktoken: specifier: ^1.0.14 version: 1.0.19 + jsdom: + specifier: ^24.1.0 + version: 24.1.3(canvas@2.11.2) log-symbols: specifier: ^7.0.0 version: 7.0.0 @@ -253,6 +259,9 @@ importers: '@langbase/tsconfig': specifier: workspace:* version: link:../../tools/tsconfig + '@types/jsdom': + specifier: ^21.1.7 + version: 21.1.7 '@types/node': specifier: ^22.6.1 version: 22.13.5 @@ -1104,6 +1113,10 @@ packages: '@microsoft/tsdoc@0.14.2': resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} + '@modelcontextprotocol/sdk@1.8.0': + resolution: {integrity: sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==} + engines: {node: '>=18'} + '@next/env@14.2.5': resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} @@ -1388,6 +1401,9 @@ packages: '@types/gensync@1.0.4': resolution: {integrity: sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==} + '@types/jsdom@21.1.7': + resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1426,6 +1442,9 @@ packages: '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@typescript-eslint/eslint-plugin@6.21.0': resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -1700,6 +1719,10 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1892,6 +1915,10 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1928,6 +1955,10 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -2122,9 +2153,29 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + cosmiconfig-typescript-loader@5.1.0: resolution: {integrity: sha512-7PtBB+6FdsOvZyJtlF3hEPpACq7RQX6BVGsgC7/lfVXnKMvNCu/XY3ykreqG5w/rBNdu2z8LCIKoF3kpHHdHlA==} engines: {node: '>=v16'} @@ -2244,6 +2295,10 @@ packages: delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2304,6 +2359,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.97: resolution: {integrity: sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==} @@ -2316,6 +2374,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + enhanced-resolve@5.18.1: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} @@ -2392,6 +2454,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -2609,6 +2674,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -2620,6 +2689,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.1: + resolution: {integrity: sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.6: + resolution: {integrity: sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==} + engines: {node: '>=18.0.0'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -2628,6 +2705,16 @@ packages: resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} engines: {node: ^18.19.0 || >=20.5.0} + express-rate-limit@7.5.0: + resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} + engines: {node: '>= 16'} + peerDependencies: + express: ^4.11 || 5 || ^5.0.0-beta.1 + + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} @@ -2678,6 +2765,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -2716,10 +2807,18 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + frac@1.1.2: resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} engines: {node: '>=0.8'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -2904,6 +3003,10 @@ packages: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2974,6 +3077,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -3081,6 +3188,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -3376,10 +3486,18 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -3395,10 +3513,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} @@ -3488,6 +3614,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -3605,6 +3735,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3707,6 +3841,10 @@ packages: parse5@7.2.1: resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3730,6 +3868,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -3771,6 +3913,10 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pkce-challenge@4.1.0: + resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==} + engines: {node: '>=16.20.0'} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -3892,6 +4038,10 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} @@ -3904,6 +4054,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} @@ -3913,6 +4067,14 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -4032,6 +4194,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + rrweb-cssom@0.7.1: resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} @@ -4095,9 +4261,17 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -4113,6 +4287,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4218,6 +4395,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + std-env@3.8.0: resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} @@ -4428,6 +4609,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -4548,6 +4733,10 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -4600,6 +4789,10 @@ packages: unpdf@0.11.0: resolution: {integrity: sha512-SScdGQl6uTMt4+872d7YwQxMFnvxM6XtrfKLR0Q1DPTIGpsbbg7b11J73sz4s6R5mMID+KYtoMnjH+OQX4Iiyg==} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + update-browserslist-db@1.1.2: resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true @@ -4632,6 +4825,10 @@ packages: validate.io-function@1.0.2: resolution: {integrity: sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vite-node@1.6.0: resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} engines: {node: ^18.0.0 || >=20.0.0} @@ -4857,6 +5054,11 @@ packages: zod-error@1.5.0: resolution: {integrity: sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ==} + zod-to-json-schema@3.24.5: + resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + peerDependencies: + zod: ^3.24.1 + zod-validation-error@3.4.0: resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==} engines: {node: '>=18.0.0'} @@ -5541,6 +5743,21 @@ snapshots: '@microsoft/tsdoc@0.14.2': {} + '@modelcontextprotocol/sdk@1.8.0': + dependencies: + content-type: 1.0.5 + cors: 2.8.5 + cross-spawn: 7.0.6 + eventsource: 3.0.6 + express: 5.1.0 + express-rate-limit: 7.5.0(express@5.1.0) + pkce-challenge: 4.1.0 + raw-body: 3.0.0 + zod: 3.24.1 + zod-to-json-schema: 3.24.5(zod@3.24.1) + transitivePeerDependencies: + - supports-color + '@next/env@14.2.5': {} '@next/eslint-plugin-next@14.2.23': @@ -5759,6 +5976,12 @@ snapshots: '@types/gensync@1.0.4': {} + '@types/jsdom@21.1.7': + dependencies: + '@types/node': 20.17.17 + '@types/tough-cookie': 4.0.5 + parse5: 7.2.1 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -5797,6 +6020,8 @@ snapshots: '@types/semver@7.5.8': {} + '@types/tough-cookie@4.0.5': {} + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -6206,6 +6431,11 @@ snapshots: dependencies: event-target-shim: 5.0.1 + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: acorn: 8.14.0 @@ -6402,6 +6632,20 @@ snapshots: binary-extensions@2.3.0: {} + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.0 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.0 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -6439,6 +6683,8 @@ snapshots: dependencies: streamsearch: 1.1.0 + bytes@3.1.2: {} + cac@6.7.14: {} call-bind-apply-helpers@1.0.1: @@ -6672,8 +6918,23 @@ snapshots: console-control-strings@1.1.0: optional: true + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + convert-source-map@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cosmiconfig-typescript-loader@5.1.0(@types/node@22.13.5)(cosmiconfig@9.0.0(typescript@5.7.3))(typescript@5.7.3): dependencies: '@types/node': 22.13.5 @@ -6781,6 +7042,8 @@ snapshots: delegates@1.0.0: optional: true + depd@2.0.0: {} + dequal@2.0.3: {} detect-indent@6.1.0: {} @@ -6824,6 +7087,8 @@ snapshots: eastasianwidth@0.2.0: {} + ee-first@1.1.1: {} + electron-to-chromium@1.5.97: {} emoji-regex@10.4.0: {} @@ -6832,6 +7097,8 @@ snapshots: emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 @@ -7036,6 +7303,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@1.0.5: {} escape-string-regexp@4.0.0: {} @@ -7388,12 +7657,20 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + event-target-shim@5.0.1: {} eventemitter3@5.0.1: {} events@3.3.0: {} + eventsource-parser@3.0.1: {} + + eventsource@3.0.6: + dependencies: + eventsource-parser: 3.0.1 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -7421,6 +7698,42 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.1 + express-rate-limit@7.5.0(express@5.1.0): + dependencies: + express: 5.1.0 + + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.1 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extendable-error@0.1.7: {} external-editor@3.1.0: @@ -7470,6 +7783,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.0: + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -7514,8 +7838,12 @@ snapshots: dependencies: fetch-blob: 3.2.0 + forwarded@0.2.0: {} + frac@1.1.2: {} + fresh@2.0.0: {} + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -7719,6 +8047,14 @@ snapshots: dependencies: whatwg-encoding: 3.1.1 + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 @@ -7789,6 +8125,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + ipaddr.js@1.9.1: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -7889,6 +8227,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.3 @@ -8210,8 +8550,12 @@ snapshots: math-intrinsics@1.1.0: {} + media-typer@1.1.0: {} + meow@13.2.0: {} + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -8223,10 +8567,16 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + mimic-fn@4.0.0: {} mimic-function@5.0.1: {} @@ -8308,6 +8658,8 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + neo-async@2.6.2: {} next@14.2.5(@playwright/test@1.50.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -8439,6 +8791,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -8547,6 +8903,8 @@ snapshots: dependencies: entities: 4.5.0 + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -8562,6 +8920,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-to-regexp@8.2.0: {} + path-type@4.0.0: {} pathe@1.1.2: {} @@ -8584,6 +8944,8 @@ snapshots: pirates@4.0.6: {} + pkce-challenge@4.1.0: {} + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -8689,6 +9051,11 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + psl@1.15.0: dependencies: punycode: 2.3.1 @@ -8701,6 +9068,10 @@ snapshots: punycode@2.3.1: {} + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + querystringify@2.2.0: {} queue-microtask@1.2.3: {} @@ -8709,6 +9080,15 @@ snapshots: dependencies: safe-buffer: 5.2.1 + range-parser@1.2.1: {} + + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -8857,6 +9237,16 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.34.6 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.0 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + transitivePeerDependencies: + - supports-color + rrweb-cssom@0.7.1: {} rrweb-cssom@0.8.0: {} @@ -8921,10 +9311,35 @@ snapshots: semver@7.7.1: {} + send@1.2.0: + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + set-blocking@2.0.0: optional: true @@ -8950,6 +9365,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -9070,6 +9487,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.1: {} + std-env@3.8.0: {} steno@4.0.2: {} @@ -9307,6 +9726,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -9421,6 +9842,12 @@ snapshots: type-fest@0.8.1: {} + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.3 @@ -9484,6 +9911,8 @@ snapshots: - encoding - supports-color + unpipe@1.0.0: {} + update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: browserslist: 4.24.4 @@ -9514,6 +9943,8 @@ snapshots: validate.io-function@1.0.2: {} + vary@1.1.2: {} + vite-node@1.6.0(@types/node@20.17.17)(terser@5.38.1): dependencies: cac: 6.7.14 @@ -9828,6 +10259,10 @@ snapshots: dependencies: zod: 3.24.1 + zod-to-json-schema@3.24.5(zod@3.24.1): + dependencies: + zod: 3.24.1 + zod-validation-error@3.4.0(zod@3.24.1): dependencies: zod: 3.24.1 From a7953084d36da815054a76ba4fa2e1c2cc03b983 Mon Sep 17 00:00:00 2001 From: arre_ankit Date: Tue, 8 Apr 2025 00:38:23 +0530 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20utils=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/README.md | 52 ++++++- packages/cli/src/docs-mcp-server/index.ts | 172 ++++++++-------------- packages/cli/src/utils/cli.ts | 1 + packages/cli/src/utils/get-score.ts | 14 ++ packages/cli/src/utils/init.ts | 1 + 5 files changed, 124 insertions(+), 116 deletions(-) create mode 100644 packages/cli/src/utils/get-score.ts diff --git a/packages/cli/README.md b/packages/cli/README.md index 6e9b2ba..da1c0d9 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -6,7 +6,7 @@ Langbase CLI is a command line interface for Langbase. It allows you to interact Please follow the [Langbase documentation](https://langbase.com/docs) for the latest information. -## Installation +## Execute ```bash npx @langbase/cli @@ -16,17 +16,55 @@ npx @langbase/cli ### DOCS MCP Server -Integrate the Langbase Docs MCP server into Cursor IDE. +Integrate the Langbase Docs MCP server into your IDEs and Claude Desktop. #### Cursor +- Open Cursor settings +- Navigate to the MCP settings +- Click on the `+` button to add a new global MCP server +- Paste the following configuration in the `mcp.json` file: +```json +{ + "mcpServers": { + "Langbase": { + "command": "npx", + "args": ["@langbase/cli","docs-mcp-server"] + } + } +} +``` +#### Windsurf +- Navigate to Windsurf - Settings > Advanced Settings +- You will find the option to Add Server +- Click “Add custom server +” +- Paste the following configuration in the `mcp_config.json` file: +```json +{ + "mcpServers": { + "Langbase": { + "command": "npx", + "args": ["@langbase/cli", "docs-mcp-server"] + } + } +} ``` + +#### Claude Desktop +- Open Claude Desktop File Menu +- Navigate to the settings +- Go to Developer Tab +- Click on the Edit Config button +- Paste the following configuration in the `claude_desktop_config.json` file: +```json { - "mcpServers": { - "Langbase": { - "command": "npx", - "args": ["@langbase/cli","docs-mcp-server"] + "mcpServers": { + "Langbase": { + "command": "npx", + "args": ["@langbase/cli", "docs-mcp-server"] + } } - } } ``` + + diff --git a/packages/cli/src/docs-mcp-server/index.ts b/packages/cli/src/docs-mcp-server/index.ts index 6e599fa..764e84d 100644 --- a/packages/cli/src/docs-mcp-server/index.ts +++ b/packages/cli/src/docs-mcp-server/index.ts @@ -1,27 +1,10 @@ -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { z } from 'zod'; -import { fetchDocsList, fetchDocsPost } from './docs'; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import { fetchDocsList, fetchDocsPost } from "./docs" +import { getRelevanceScore } from "@/utils/get-score"; + -/** - * Starts the Documentation Message Control Protocol (MCP) server for Langbase. - * - * This function initializes and runs an MCP server that provides tools for - * fetching and searching Langbase documentation. The server includes several tools: - * - * - docs-route-finder: Searches through documentation routes based on user query - * - sdk-documentation-fetcher: Fetches detailed SDK documentation - * - examples-tool: Fetches code examples and sample implementations - * - guide-tool: Fetches detailed guides and tutorials - * - api-reference-tool: Fetches API reference documentation - * - * The server communicates using a standard I/O transport mechanism. - * - * @returns {Promise} A promise that resolves when the server completes execution - * - * @throws Will throw an error if connecting to the transport fails - * @throws Will throw an error if there is a fatal error in the main execution - */ export async function docsMcpServer() { const server = new McpServer({ name: 'langbase-docs-server', @@ -37,62 +20,44 @@ export async function docsMcpServer() { For example, if user asks 'How do I create a pipe?', the query would be 'SDK Pipe'. This should be the specific concept or topic to search for in the documentation. Treat keyword add as create if user ask for Eg. 'How do I add memory to pipe?' the query should be 'create memory'`) - }, - async ({ query }) => { - const docs = await fetchDocsList(); - // search through the docs and return the most relevent path based on the query - const docLines = docs.split('\n').filter(line => line.trim()); - - const getRelevanceScore = (line: string, searchQuery: string) => { - const lowerLine = line.toLowerCase(); - const lowerQuery = searchQuery.toLowerCase(); - // Higher score for exact matches - if (lowerLine.includes(lowerQuery)) { - return 3; - } - - // Score based on word matches - const queryWords = lowerQuery.split(' '); - return queryWords.reduce((score, word) => { - return score + (lowerLine.includes(word) ? 1 : 0); - }, 0); - }; + }, + async ({ query }) => { + const docs = await fetchDocsList() + // search through the docs and return the most relevent path based on the query + const docLines = docs.split('\n').filter(line => line.trim()); - // Score and sort the documentation entries - const scoredDocs = docLines - .map(line => ({ - line, - score: getRelevanceScore(line, query) - })) - .sort((a, b) => b.score - a.score) - .filter(doc => doc.score > 0) - .slice(0, 3); // Get top 3 most relevant results + + // Score and sort the documentation entries + const scoredDocs = docLines + .map(line => ({ + line, + score: getRelevanceScore(line, query) + })) + .sort((a, b) => b.score - a.score) + .filter(doc => doc.score > 0) + .slice(0, 3); // Get top 3 most relevant results + + const hasRelevantDocs = scoredDocs.length === 0; - if (scoredDocs.length === 0) { - return { - content: [ - { - type: 'text', - text: - 'No relevant documentation found for the query: ' + - query - } - ] - }; - } - - const results = scoredDocs.map(doc => doc.line).join('\n'); - - return { - content: [ - { - type: 'text', - text: results - } - ] - }; - } - ); + if (hasRelevantDocs) { + return { + content: [{ + type: "text", + text: "No relevant documentation found for the query: " + query + }] + }; + } + + const results = scoredDocs.map(doc => doc.line).join('\n'); + + return { + content: [{ + type: "text", + text: results + }] + }; + } + ); server.tool( 'sdk-documentation-fetcher', @@ -169,42 +134,31 @@ export async function docsMcpServer() { } ); - server.tool( - 'api-reference-tool', - 'Fetches API reference documentation. Use this tool ONLY when the user explicitly asks about API endpoints, REST API calls, or programmatically creating/updating/deleting resources (like pipes, memory, etc.) through the API interface. For general SDK implementation questions, use the sdk-documentation-fetcher instead.', - { - url: z - .string() - .describe( - 'URL of a specific api-reference page to fetch. Format: /api-reference/...' - ) - }, - async ({ url }) => { - const content = await fetchDocsPost( - `https://langbase.com/docs${url}` - ); - return { - content: [ - { - type: 'text', - text: content - } - ] - }; - } - ); + server.tool( + "api-reference-tool", + "Fetches API reference documentation. Use this tool ONLY when the user explicitly asks about API endpoints, REST API calls, or programmatically creating/updating/deleting resources (like pipes, memory, etc.) through the API interface. For general SDK implementation questions, use the sdk-documentation-fetcher instead.", + { url: z.string().describe("URL of a specific api-reference page to fetch. Format: /api-reference/...") }, + async ({ url }) => { + const content = await fetchDocsPost(`https://langbase.com/docs${url}`); + return { + content: [{ + type: "text", + text: content + }] + }; + } + ); async function main() { const transport = new StdioServerTransport(); - try { - await server.connect(transport); - console.error('Langbase service MCP Server running on stdio'); - } catch (error) { - console.error('Error connecting to transport:', error); - process.exit(1); - } - } + try { + await server.connect(transport); + } catch (error) { + console.error("Error connecting to transport:", error); + process.exit(1); + } + } main().catch(error => { console.error('Something went wrong:', error); diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts index d58001c..b5bcda3 100644 --- a/packages/cli/src/utils/cli.ts +++ b/packages/cli/src/utils/cli.ts @@ -1,4 +1,5 @@ import meowHelp from 'cli-meow-help'; +// @ts-ignore import meow from 'meow'; const flags = { diff --git a/packages/cli/src/utils/get-score.ts b/packages/cli/src/utils/get-score.ts new file mode 100644 index 0000000..e77bae9 --- /dev/null +++ b/packages/cli/src/utils/get-score.ts @@ -0,0 +1,14 @@ +export const getRelevanceScore = (line: string, searchQuery: string) => { + const lowerLine = line.toLowerCase(); + const lowerQuery = searchQuery.toLowerCase(); + // Higher score for exact matches + if (lowerLine.includes(lowerQuery)) { + return 3; + } + + // Score based on word matches + const queryWords = lowerQuery.split(' '); + return queryWords.reduce((score, word) => { + return score + (lowerLine.includes(word) ? 1 : 0); + }, 0); +}; \ No newline at end of file diff --git a/packages/cli/src/utils/init.ts b/packages/cli/src/utils/init.ts index 70e28ca..f540507 100644 --- a/packages/cli/src/utils/init.ts +++ b/packages/cli/src/utils/init.ts @@ -1,3 +1,4 @@ +// @ts-ignore import unhandled from 'cli-handle-unhandled'; import welcome from 'cli-welcome'; import path from 'path'; From 9225cc07ae79d531ec171fac8998a1dbd78c3f7c Mon Sep 17 00:00:00 2001 From: msaaddev Date: Wed, 9 Apr 2025 01:01:52 +0200 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Review=20by=20SI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/README.md | 10 +- packages/cli/src/docs-mcp-server/index.ts | 140 ++++++++++++---------- packages/cli/src/utils/cli.ts | 5 +- packages/cli/src/utils/get-score.ts | 37 ++++-- 4 files changed, 104 insertions(+), 88 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index da1c0d9..c56d947 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -6,12 +6,6 @@ Langbase CLI is a command line interface for Langbase. It allows you to interact Please follow the [Langbase documentation](https://langbase.com/docs) for the latest information. -## Execute - -```bash -npx @langbase/cli -``` - # Usage ### DOCS MCP Server @@ -31,7 +25,7 @@ Integrate the Langbase Docs MCP server into your IDEs and Claude Desktop. "args": ["@langbase/cli","docs-mcp-server"] } } -} +} ``` #### Windsurf @@ -66,5 +60,3 @@ Integrate the Langbase Docs MCP server into your IDEs and Claude Desktop. } } ``` - - diff --git a/packages/cli/src/docs-mcp-server/index.ts b/packages/cli/src/docs-mcp-server/index.ts index 764e84d..a7ce20d 100644 --- a/packages/cli/src/docs-mcp-server/index.ts +++ b/packages/cli/src/docs-mcp-server/index.ts @@ -1,9 +1,8 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; -import { fetchDocsList, fetchDocsPost } from "./docs" -import { getRelevanceScore } from "@/utils/get-score"; - +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { z } from 'zod'; +import { fetchDocsList, fetchDocsPost } from './docs'; +import { getRelevanceScore } from '@/utils/get-score'; export async function docsMcpServer() { const server = new McpServer({ @@ -20,44 +19,49 @@ export async function docsMcpServer() { For example, if user asks 'How do I create a pipe?', the query would be 'SDK Pipe'. This should be the specific concept or topic to search for in the documentation. Treat keyword add as create if user ask for Eg. 'How do I add memory to pipe?' the query should be 'create memory'`) - }, - async ({ query }) => { - const docs = await fetchDocsList() - // search through the docs and return the most relevent path based on the query - const docLines = docs.split('\n').filter(line => line.trim()); + }, + async ({ query }) => { + const docs = await fetchDocsList(); + // search through the docs and return the most relevent path based on the query + const docLines = docs.split('\n').filter(line => line.trim()); + + // Score and sort the documentation entries + const scoredDocs = docLines + .map(line => ({ + line, + score: getRelevanceScore(line, query) + })) + .sort((a, b) => b.score - a.score) + .filter(doc => doc.score > 0) + .slice(0, 3); // Get top 3 most relevant results + + const hasRelevantDocs = scoredDocs.length === 0; + + if (hasRelevantDocs) { + return { + content: [ + { + type: 'text', + text: + 'No relevant documentation found for the query: ' + + query + } + ] + }; + } - - // Score and sort the documentation entries - const scoredDocs = docLines - .map(line => ({ - line, - score: getRelevanceScore(line, query) - })) - .sort((a, b) => b.score - a.score) - .filter(doc => doc.score > 0) - .slice(0, 3); // Get top 3 most relevant results - - const hasRelevantDocs = scoredDocs.length === 0; + const results = scoredDocs.map(doc => doc.line).join('\n'); - if (hasRelevantDocs) { - return { - content: [{ - type: "text", - text: "No relevant documentation found for the query: " + query - }] - }; - } - - const results = scoredDocs.map(doc => doc.line).join('\n'); - - return { - content: [{ - type: "text", - text: results - }] - }; - } - ); + return { + content: [ + { + type: 'text', + text: results + } + ] + }; + } + ); server.tool( 'sdk-documentation-fetcher', @@ -134,31 +138,41 @@ export async function docsMcpServer() { } ); - server.tool( - "api-reference-tool", - "Fetches API reference documentation. Use this tool ONLY when the user explicitly asks about API endpoints, REST API calls, or programmatically creating/updating/deleting resources (like pipes, memory, etc.) through the API interface. For general SDK implementation questions, use the sdk-documentation-fetcher instead.", - { url: z.string().describe("URL of a specific api-reference page to fetch. Format: /api-reference/...") }, - async ({ url }) => { - const content = await fetchDocsPost(`https://langbase.com/docs${url}`); - return { - content: [{ - type: "text", - text: content - }] - }; - } - ); + server.tool( + 'api-reference-tool', + 'Fetches API reference documentation. Use this tool ONLY when the user explicitly asks about API endpoints, REST API calls, or programmatically creating/updating/deleting resources (like pipes, memory, etc.) through the API interface. For general SDK implementation questions, use the sdk-documentation-fetcher instead.', + { + url: z + .string() + .describe( + 'URL of a specific api-reference page to fetch. Format: /api-reference/...' + ) + }, + async ({ url }) => { + const content = await fetchDocsPost( + `https://langbase.com/docs${url}` + ); + return { + content: [ + { + type: 'text', + text: content + } + ] + }; + } + ); async function main() { const transport = new StdioServerTransport(); - try { - await server.connect(transport); - } catch (error) { - console.error("Error connecting to transport:", error); - process.exit(1); - } - } + try { + await server.connect(transport); + } catch (error) { + console.error('Error connecting to transport:', error); + process.exit(1); + } + } main().catch(error => { console.error('Something went wrong:', error); diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts index b5bcda3..eb0c482 100644 --- a/packages/cli/src/utils/cli.ts +++ b/packages/cli/src/utils/cli.ts @@ -34,11 +34,11 @@ const flags = { const commands = { auth: { desc: `Authenticate with Langbase` }, deploy: { desc: `Deploy a script to Langbase` }, - help: { desc: `Print help info` }, + help: { desc: `Print help info` } }; const helpText = meowHelp({ - name: `langbase`, + name: `@langbase/cli`, flags, commands, desc: false, @@ -54,5 +54,4 @@ const options = { flags }; - export default meow(helpText, options); diff --git a/packages/cli/src/utils/get-score.ts b/packages/cli/src/utils/get-score.ts index e77bae9..62e4e51 100644 --- a/packages/cli/src/utils/get-score.ts +++ b/packages/cli/src/utils/get-score.ts @@ -1,14 +1,25 @@ +/** + * Calculates a relevance score between a line of text and a search query. + * + * The scoring algorithm works as follows: + * - If the entire search query is found within the line, returns a score of 3 (highest relevance) + * - Otherwise, adds 1 point for each individual search query word found in the line + * + * @param line - The text line to check against the search query + * @param searchQuery - The search query to check against the line + * @returns A numerical score indicating relevance: 3 for exact matches, or the count of matching words + */ export const getRelevanceScore = (line: string, searchQuery: string) => { - const lowerLine = line.toLowerCase(); - const lowerQuery = searchQuery.toLowerCase(); - // Higher score for exact matches - if (lowerLine.includes(lowerQuery)) { - return 3; - } - - // Score based on word matches - const queryWords = lowerQuery.split(' '); - return queryWords.reduce((score, word) => { - return score + (lowerLine.includes(word) ? 1 : 0); - }, 0); -}; \ No newline at end of file + const lowerLine = line.toLowerCase(); + const lowerQuery = searchQuery.toLowerCase(); + // Higher score for exact matches + if (lowerLine.includes(lowerQuery)) { + return 3; + } + + // Score based on word matches + const queryWords = lowerQuery.split(' '); + return queryWords.reduce((score, word) => { + return score + (lowerLine.includes(word) ? 1 : 0); + }, 0); +}; From 4263cac7b991ff3ead62a3eafa9ae1f45d508d7a Mon Sep 17 00:00:00 2001 From: msaaddev Date: Wed, 9 Apr 2025 01:10:42 +0200 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index c56d947..caee3b7 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,14 +1,10 @@ # Langbase CLI -Langbase CLI is a command line interface for Langbase. It allows you to interact with Langbase from the command line. +The Langbase CLI is a command-line interface for Langbase. It provides a set of commands to interact with the Langbase SDK and perform various tasks related to AI development. -# Documentation +## Usage -Please follow the [Langbase documentation](https://langbase.com/docs) for the latest information. - -# Usage - -### DOCS MCP Server +### Langbase Docs MCP Server Integrate the Langbase Docs MCP server into your IDEs and Claude Desktop. @@ -60,3 +56,8 @@ Integrate the Langbase Docs MCP server into your IDEs and Claude Desktop. } } ``` + +## Next steps + +- Read the [Langbase SDK documentation](https://langbase.com/docs/sdk) for more details +- Join our [Discord](https://langbase.com/discord) community for feedback, requests, and support