Skip to content

Commit 8dd1154

Browse files
arre-ankitmsaaddev
andauthored
👌 IMPROVE: DOCS MCP server (#100)
* 👌 IMPROVE: DOCS MCP server * 👌 IMPROVE: Review by SI --------- Co-authored-by: msaaddev <[email protected]>
1 parent 32acf46 commit 8dd1154

File tree

4 files changed

+277
-40
lines changed

4 files changed

+277
-40
lines changed

packages/cli/src/docs-mcp-server/docs.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ export async function fetchDocsList() {
2020
}
2121
}
2222

23+
export async function fetchSdkDocsList() {
24+
try {
25+
const response = await fetch('https://langbase.com/docs/llms-sdk.txt');
26+
if (!response.ok) {
27+
throw new Error('Failed to fetch docs');
28+
}
29+
30+
const text = await response.text();
31+
return text;
32+
} catch (error) {
33+
throw new Error('Failed to fetch docs ' + JSON.stringify(error));
34+
}
35+
}
36+
2337
/**
2438
* Fetches and converts a blog post to markdown
2539
*
@@ -45,12 +59,14 @@ export async function fetchDocsPost(url: string): Promise<string> {
4559

4660
// Get the main content
4761
const content = document.body.textContent?.trim() || '';
62+
4863
if (!content) {
4964
throw new Error('No content found in docs');
5065
}
5166

5267
return content;
5368
} catch (error) {
69+
console.error('Error fetching docs:', error);
5470
throw new Error(
5571
`Failed to fetch docs: ${error instanceof Error ? error.message : 'Something went wrong. Please try again.'}`
5672
);

packages/cli/src/docs-mcp-server/index.ts

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
22
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
33
import { z } from 'zod';
4-
import { fetchDocsList, fetchDocsPost } from './docs';
5-
import { getRelevanceScore } from '@/utils/get-score';
4+
import { fetchDocsList, fetchDocsPost, fetchSdkDocsList } from './docs';
5+
import { findRelevantLink } from '@/utils/get-relevent-link';
66

77
export async function docsMcpServer() {
88
const server = new McpServer({
@@ -23,21 +23,46 @@ export async function docsMcpServer() {
2323
async ({ query }) => {
2424
const docs = await fetchDocsList();
2525
// search through the docs and return the most relevent path based on the query
26-
const docLines = docs.split('\n').filter(line => line.trim());
26+
const url = findRelevantLink(docs, query);
27+
if (!url) {
28+
return {
29+
content: [
30+
{
31+
type: 'text',
32+
text:
33+
'No relevant documentation found for the query: ' +
34+
query
35+
}
36+
]
37+
};
38+
}
2739

28-
// Score and sort the documentation entries
29-
const scoredDocs = docLines
30-
.map(line => ({
31-
line,
32-
score: getRelevanceScore(line, query)
33-
}))
34-
.sort((a, b) => b.score - a.score)
35-
.filter(doc => doc.score > 0)
36-
.slice(0, 3); // Get top 3 most relevant results
40+
return {
41+
content: [
42+
{
43+
type: 'text',
44+
text: `This is the most relevant documentation for the query: ${url}`
45+
}
46+
]
47+
};
48+
}
49+
);
3750

38-
const hasRelevantDocs = scoredDocs.length === 0;
51+
server.tool(
52+
'sdk-route-finder',
53+
"Searches through all available SDK 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.",
54+
{
55+
query: z.string()
56+
.describe(`A refined search term extracted from the user's question.
57+
For example, if user asks 'How do I create a pipe?', the query would be 'SDK Pipe'.
58+
This should be the specific concept or topic to search for in the documentation.
59+
Treat keyword add as create if user ask for Eg. 'How do I add memory to pipe?' the query should be 'create memory'`)
60+
},
61+
async ({ query }) => {
62+
const docs = await fetchSdkDocsList();
63+
const url = findRelevantLink(docs, query);
3964

40-
if (hasRelevantDocs) {
65+
if (!url) {
4166
return {
4267
content: [
4368
{
@@ -50,22 +75,20 @@ export async function docsMcpServer() {
5075
};
5176
}
5277

53-
const results = scoredDocs.map(doc => doc.line).join('\n');
54-
5578
return {
5679
content: [
5780
{
5881
type: 'text',
59-
text: results
82+
text: `This is the most relevant documentation for the query: ${url}`
6083
}
6184
]
6285
};
6386
}
6487
);
6588

6689
server.tool(
67-
'sdk-documentation-fetcher',
68-
'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.',
90+
'sdk-docs-tool',
91+
'Always First Use sdk-route-finder to find the most relevant documentation and then use this tool to fetch the detailed documentation.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.',
6992
{
7093
url: z
7194
.string()
@@ -90,7 +113,7 @@ export async function docsMcpServer() {
90113

91114
server.tool(
92115
'examples-tool',
93-
'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.',
116+
'Always first use docs-route-finder to find the most relevant documentation and then use this tool to fetch the detailed documentation. 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.',
94117
{
95118
url: z
96119
.string()
@@ -115,7 +138,7 @@ export async function docsMcpServer() {
115138

116139
server.tool(
117140
'guide-tool',
118-
'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.',
141+
'Always first use docs-route-finder to find the most relevant documentation and then use this tool to fetch the detailed documentation. 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.',
119142
{
120143
url: z
121144
.string()
@@ -140,7 +163,7 @@ export async function docsMcpServer() {
140163

141164
server.tool(
142165
'api-reference-tool',
143-
'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.',
166+
'Always first use docs-route-finder to find the most relevant documentation and then use this tool to fetch the detailed documentation. 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.',
144167
{
145168
url: z
146169
.string()
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {
2+
extractDocBlocks,
3+
extractDocMetadata,
4+
getRelevanceScore
5+
} from './get-score';
6+
7+
/**
8+
* Main search function to find relevant documentation based on a query
9+
* Returns the url of the most relevant documentation
10+
*
11+
* @param docs - The complete documentation text
12+
* @param query - The search query
13+
* @returns Array of top 5 relevant document objects
14+
*/
15+
export const findRelevantLink = (docs: string, query: string) => {
16+
// Get top 5 results
17+
const searchResults = searchDocs(docs, query, 5);
18+
const hasNoResults = searchResults.length === 0;
19+
if (hasNoResults) {
20+
return 'docs/sdk';
21+
}
22+
23+
const url = searchResults[0].url?.split('/docs/')[1];
24+
return url;
25+
};
26+
27+
/**
28+
* Searches through documentation blocks to find relevant matches for a query
29+
*
30+
* @param docs - The complete documentation text containing multiple doc blocks
31+
* @param query - The search query
32+
* @param maxResults - Maximum number of results to return
33+
* @returns Array of relevant docs with their scores, titles and URLs
34+
*/
35+
export const searchDocs = (docs: string, query: string, maxResults: number) => {
36+
// Extract all document blocks safely
37+
const docBlocks = extractDocBlocks(docs);
38+
// Score each document block
39+
const scoredDocs = docBlocks.map(docBlock => {
40+
// Extract metadata
41+
const metadata = extractDocMetadata(docBlock);
42+
43+
// Calculate scores for title and content separately
44+
let titleScore = 0;
45+
const isTitleNoEmpty = metadata.title != '';
46+
47+
if (isTitleNoEmpty) {
48+
titleScore = getRelevanceScore(metadata.title, query, true);
49+
} else {
50+
titleScore = 0;
51+
}
52+
53+
// Extract content from the doc block, handling potential code blocks with backticks
54+
const contentMatch = /<content>([\s\S]*?)<\/content>/i.exec(docBlock);
55+
let content = '';
56+
57+
if (contentMatch) {
58+
content = contentMatch[1].trim();
59+
} else {
60+
content = '';
61+
}
62+
63+
const contentScore = getRelevanceScore(content, query, false);
64+
// Combined score with title weighted more heavily
65+
const totalScore = titleScore + contentScore;
66+
67+
return {
68+
score: totalScore,
69+
title: metadata.title || 'Untitled Document',
70+
url: metadata.url || null
71+
};
72+
});
73+
74+
// Sort by score (descending) and filter out irrelevant results
75+
const filteredDocs = scoredDocs
76+
.filter(doc => doc.score > 0)
77+
.sort((a, b) => b.score - a.score)
78+
.slice(0, maxResults);
79+
return filteredDocs;
80+
};

0 commit comments

Comments
 (0)