-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocs-search-tool.ts
More file actions
138 lines (118 loc) · 3.9 KB
/
docs-search-tool.ts
File metadata and controls
138 lines (118 loc) · 3.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { Metadata, McpRequestContext, asTextContentResult } from './types';
import { getLogger } from './logger';
import type { LocalDocsSearch } from './local-docs-search';
export const metadata: Metadata = {
resource: 'all',
operation: 'read',
tags: [],
httpMethod: 'get',
};
export const tool: Tool = {
name: 'search_docs',
description:
'Search SDK documentation to find methods, parameters, and usage examples for interacting with the API. Use this before writing code when you need to discover the right approach.',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The query to search for.',
},
language: {
type: 'string',
description: 'The language for the SDK to search for.',
enum: ['http', 'python', 'go', 'typescript', 'javascript', 'terraform', 'ruby', 'java', 'kotlin'],
},
detail: {
type: 'string',
description: 'The amount of detail to return.',
enum: ['default', 'verbose'],
},
},
required: ['query', 'language'],
},
annotations: {
readOnlyHint: true,
},
};
const docsSearchURL =
process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/cas-parser/docs/search';
let _localSearch: LocalDocsSearch | undefined;
export function setLocalSearch(search: LocalDocsSearch): void {
_localSearch = search;
}
async function searchLocal(args: Record<string, unknown>): Promise<unknown> {
if (!_localSearch) {
throw new Error('Local search not initialized');
}
const query = (args['query'] as string) ?? '';
const language = (args['language'] as string) ?? 'typescript';
const detail = (args['detail'] as string) ?? 'default';
return _localSearch.search({
query,
language,
detail,
maxResults: 5,
}).results;
}
async function searchRemote(args: Record<string, unknown>, reqContext: McpRequestContext): Promise<unknown> {
const body = args as any;
const query = new URLSearchParams(body).toString();
const startTime = Date.now();
const result = await fetch(`${docsSearchURL}?${query}`, {
headers: {
...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }),
...(reqContext.mcpSessionId && { 'x-stainless-mcp-session-id': reqContext.mcpSessionId }),
...(reqContext.mcpClientInfo && {
'x-stainless-mcp-client-info': JSON.stringify(reqContext.mcpClientInfo),
}),
},
});
const logger = getLogger();
if (!result.ok) {
const errorText = await result.text();
logger.warn(
{
durationMs: Date.now() - startTime,
query: body.query,
status: result.status,
statusText: result.statusText,
errorText,
},
'Got error response from docs search tool',
);
if (result.status === 404 && !reqContext.stainlessApiKey) {
throw new Error(
'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.',
);
}
throw new Error(
`${result.status}: ${result.statusText} when using doc search tool. Details: ${errorText}`,
);
}
const resultBody = await result.json();
logger.info(
{
durationMs: Date.now() - startTime,
query: body.query,
},
'Got docs search result',
);
return resultBody;
}
export const handler = async ({
reqContext,
args,
}: {
reqContext: McpRequestContext;
args: Record<string, unknown> | undefined;
}) => {
const body = args ?? {};
if (_localSearch) {
return asTextContentResult(await searchLocal(body));
}
return asTextContentResult(await searchRemote(body, reqContext));
};
export default { metadata, tool, handler };