Skip to content

Commit c99b140

Browse files
chore(internal): support local docs search in MCP servers
1 parent c7dc1f3 commit c99b140

File tree

5 files changed

+696
-10
lines changed

5 files changed

+696
-10
lines changed

packages/mcp-server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"cors": "^2.8.5",
4242
"express": "^5.1.0",
4343
"fuse.js": "^7.1.0",
44+
"minisearch": "^7.2.0",
4445
"jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz",
4546
"pino": "^10.3.1",
4647
"pino-http": "^11.0.0",

packages/mcp-server/src/docs-search-tool.ts

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Tool } from '@modelcontextprotocol/sdk/types.js';
44
import { Metadata, McpRequestContext, asTextContentResult } from './types';
55
import { getLogger } from './logger';
6+
import type { LocalDocsSearch } from './local-docs-search';
67

78
export const metadata: Metadata = {
89
resource: 'all',
@@ -43,20 +44,49 @@ export const tool: Tool = {
4344
const docsSearchURL =
4445
process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/brand.dev/docs/search';
4546

46-
export const handler = async ({
47-
reqContext,
48-
args,
49-
}: {
50-
reqContext: McpRequestContext;
51-
args: Record<string, unknown> | undefined;
52-
}) => {
47+
let _localSearch: LocalDocsSearch | undefined;
48+
49+
export function setLocalSearch(search: LocalDocsSearch): void {
50+
_localSearch = search;
51+
}
52+
53+
const SUPPORTED_LANGUAGES = new Set(['http', 'typescript', 'javascript']);
54+
55+
async function searchLocal(args: Record<string, unknown>): Promise<unknown> {
56+
if (!_localSearch) {
57+
throw new Error('Local search not initialized');
58+
}
59+
60+
const query = (args['query'] as string) ?? '';
61+
const language = (args['language'] as string) ?? 'typescript';
62+
const detail = (args['detail'] as string) ?? 'verbose';
63+
64+
if (!SUPPORTED_LANGUAGES.has(language)) {
65+
throw new Error(
66+
`Local docs search only supports HTTP, TypeScript, and JavaScript. Got language="${language}". ` +
67+
`Use --docs-search-mode stainless-api for other languages, or set language to "http", "typescript", or "javascript".`,
68+
);
69+
}
70+
71+
return _localSearch.search({
72+
query,
73+
language,
74+
detail,
75+
maxResults: 10,
76+
}).results;
77+
}
78+
79+
async function searchRemote(
80+
args: Record<string, unknown>,
81+
stainlessApiKey: string | undefined,
82+
): Promise<unknown> {
5383
const body = args as any;
5484
const query = new URLSearchParams(body).toString();
5585

5686
const startTime = Date.now();
5787
const result = await fetch(`${docsSearchURL}?${query}`, {
5888
headers: {
59-
...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }),
89+
...(stainlessApiKey && { Authorization: stainlessApiKey }),
6090
},
6191
});
6292

@@ -75,7 +105,7 @@ export const handler = async ({
75105
'Got error response from docs search tool',
76106
);
77107

78-
if (result.status === 404 && !reqContext.stainlessApiKey) {
108+
if (result.status === 404 && !stainlessApiKey) {
79109
throw new Error(
80110
'Could not find docs for this project. You may need to provide a Stainless API key via the STAINLESS_API_KEY environment variable, the --stainless-api-key flag, or the x-stainless-api-key HTTP header.',
81111
);
@@ -94,7 +124,23 @@ export const handler = async ({
94124
},
95125
'Got docs search result',
96126
);
97-
return asTextContentResult(resultBody);
127+
return resultBody;
128+
}
129+
130+
export const handler = async ({
131+
reqContext,
132+
args,
133+
}: {
134+
reqContext: McpRequestContext;
135+
args: Record<string, unknown> | undefined;
136+
}) => {
137+
const body = args ?? {};
138+
139+
if (_localSearch) {
140+
return asTextContentResult(await searchLocal(body));
141+
}
142+
143+
return asTextContentResult(await searchRemote(body, reqContext.stainlessApiKey));
98144
};
99145

100146
export default { metadata, tool, handler };

0 commit comments

Comments
 (0)