@cjavdev/believe-mcp 0.20.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/code-tool.js +4 -4
  2. package/code-tool.js.map +1 -1
  3. package/code-tool.mjs +4 -4
  4. package/code-tool.mjs.map +1 -1
  5. package/docs-search-tool.d.mts +2 -0
  6. package/docs-search-tool.d.mts.map +1 -1
  7. package/docs-search-tool.d.ts +2 -0
  8. package/docs-search-tool.d.ts.map +1 -1
  9. package/docs-search-tool.js +32 -2
  10. package/docs-search-tool.js.map +1 -1
  11. package/docs-search-tool.mjs +31 -2
  12. package/docs-search-tool.mjs.map +1 -1
  13. package/http.d.mts.map +1 -1
  14. package/http.d.ts.map +1 -1
  15. package/http.js +17 -1
  16. package/http.js.map +1 -1
  17. package/http.mjs +17 -1
  18. package/http.mjs.map +1 -1
  19. package/instructions.d.mts +4 -1
  20. package/instructions.d.mts.map +1 -1
  21. package/instructions.d.ts +4 -1
  22. package/instructions.d.ts.map +1 -1
  23. package/instructions.js +23 -4
  24. package/instructions.js.map +1 -1
  25. package/instructions.mjs +20 -4
  26. package/instructions.mjs.map +1 -1
  27. package/local-docs-search.d.mts +28 -0
  28. package/local-docs-search.d.mts.map +1 -0
  29. package/local-docs-search.d.ts +28 -0
  30. package/local-docs-search.d.ts.map +1 -0
  31. package/local-docs-search.js +3932 -0
  32. package/local-docs-search.js.map +1 -0
  33. package/local-docs-search.mjs +3892 -0
  34. package/local-docs-search.mjs.map +1 -0
  35. package/options.d.mts +3 -0
  36. package/options.d.mts.map +1 -1
  37. package/options.d.ts +3 -0
  38. package/options.d.ts.map +1 -1
  39. package/options.js +19 -0
  40. package/options.js.map +1 -1
  41. package/options.mjs +19 -0
  42. package/options.mjs.map +1 -1
  43. package/package.json +13 -2
  44. package/server.d.mts +9 -1
  45. package/server.d.mts.map +1 -1
  46. package/server.d.ts +9 -1
  47. package/server.d.ts.map +1 -1
  48. package/server.js +12 -3
  49. package/server.js.map +1 -1
  50. package/server.mjs +12 -3
  51. package/server.mjs.map +1 -1
  52. package/src/code-tool.ts +7 -9
  53. package/src/docs-search-tool.ts +46 -8
  54. package/src/http.ts +18 -1
  55. package/src/instructions.ts +27 -4
  56. package/src/local-docs-search.ts +4636 -0
  57. package/src/options.ts +24 -0
  58. package/src/server.ts +21 -3
  59. package/src/stdio.ts +4 -1
  60. package/src/types.ts +2 -0
  61. package/stdio.d.mts.map +1 -1
  62. package/stdio.d.ts.map +1 -1
  63. package/stdio.js +4 -1
  64. package/stdio.js.map +1 -1
  65. package/stdio.mjs +4 -1
  66. package/stdio.mjs.map +1 -1
  67. package/types.d.mts +5 -0
  68. package/types.d.mts.map +1 -1
  69. package/types.d.ts +5 -0
  70. package/types.d.ts.map +1 -1
  71. package/types.js.map +1 -1
  72. package/types.mjs.map +1 -1
@@ -3,6 +3,7 @@
3
3
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
4
4
  import { Metadata, McpRequestContext, asTextContentResult } from './types';
5
5
  import { getLogger } from './logger';
6
+ import type { LocalDocsSearch } from './local-docs-search';
6
7
 
7
8
  export const metadata: Metadata = {
8
9
  resource: 'all',
@@ -43,13 +44,30 @@ export const tool: Tool = {
43
44
  const docsSearchURL =
44
45
  process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/believe/docs/search';
45
46
 
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
+ async function searchLocal(args: Record<string, unknown>): Promise<unknown> {
54
+ if (!_localSearch) {
55
+ throw new Error('Local search not initialized');
56
+ }
57
+
58
+ const query = (args['query'] as string) ?? '';
59
+ const language = (args['language'] as string) ?? 'typescript';
60
+ const detail = (args['detail'] as string) ?? 'default';
61
+
62
+ return _localSearch.search({
63
+ query,
64
+ language,
65
+ detail,
66
+ maxResults: 5,
67
+ }).results;
68
+ }
69
+
70
+ async function searchRemote(args: Record<string, unknown>, reqContext: McpRequestContext): Promise<unknown> {
53
71
  const body = args as any;
54
72
  const query = new URLSearchParams(body).toString();
55
73
 
@@ -57,6 +75,10 @@ export const handler = async ({
57
75
  const result = await fetch(`${docsSearchURL}?${query}`, {
58
76
  headers: {
59
77
  ...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }),
78
+ ...(reqContext.mcpSessionId && { 'x-stainless-mcp-session-id': reqContext.mcpSessionId }),
79
+ ...(reqContext.mcpClientInfo && {
80
+ 'x-stainless-mcp-client-info': JSON.stringify(reqContext.mcpClientInfo),
81
+ }),
60
82
  },
61
83
  });
62
84
 
@@ -94,7 +116,23 @@ export const handler = async ({
94
116
  },
95
117
  'Got docs search result',
96
118
  );
97
- return asTextContentResult(resultBody);
119
+ return resultBody;
120
+ }
121
+
122
+ export const handler = async ({
123
+ reqContext,
124
+ args,
125
+ }: {
126
+ reqContext: McpRequestContext;
127
+ args: Record<string, unknown> | undefined;
128
+ }) => {
129
+ const body = args ?? {};
130
+
131
+ if (_localSearch) {
132
+ return asTextContentResult(await searchLocal(body));
133
+ }
134
+
135
+ return asTextContentResult(await searchRemote(body, reqContext));
98
136
  };
99
137
 
100
138
  export default { metadata, tool, handler };
package/src/http.ts CHANGED
@@ -23,7 +23,8 @@ const newServer = async ({
23
23
  res: express.Response;
24
24
  }): Promise<McpServer | null> => {
25
25
  const stainlessApiKey = getStainlessApiKey(req, mcpOptions);
26
- const server = await newMcpServer(stainlessApiKey);
26
+ const customInstructionsPath = mcpOptions.customInstructionsPath;
27
+ const server = await newMcpServer({ stainlessApiKey, customInstructionsPath });
27
28
 
28
29
  const authOptions = parseClientAuthHeaders(req, false);
29
30
 
@@ -77,6 +78,11 @@ const newServer = async ({
77
78
  },
78
79
  stainlessApiKey: stainlessApiKey,
79
80
  upstreamClientEnvs,
81
+ mcpSessionId: (req as any).mcpSessionId,
82
+ mcpClientInfo:
83
+ typeof req.body?.params?.clientInfo?.name === 'string' ?
84
+ { name: req.body.params.clientInfo.name, version: String(req.body.params.clientInfo.version ?? '') }
85
+ : undefined,
80
86
  });
81
87
 
82
88
  return server;
@@ -134,6 +140,17 @@ export const streamableHTTPApp = ({
134
140
  const app = express();
135
141
  app.set('query parser', 'extended');
136
142
  app.use(express.json());
143
+ app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
144
+ const existing = req.headers['mcp-session-id'];
145
+ const sessionId = (Array.isArray(existing) ? existing[0] : existing) || crypto.randomUUID();
146
+ (req as any).mcpSessionId = sessionId;
147
+ const origWriteHead = res.writeHead.bind(res);
148
+ res.writeHead = function (statusCode: number, ...rest: any[]) {
149
+ res.setHeader('mcp-session-id', sessionId);
150
+ return origWriteHead(statusCode, ...rest);
151
+ } as typeof res.writeHead;
152
+ next();
153
+ });
137
154
  app.use(
138
155
  pinoHttp({
139
156
  logger: getLogger(),
@@ -1,5 +1,6 @@
1
1
  // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
+ import fs from 'fs/promises';
3
4
  import { readEnv } from './util';
4
5
  import { getLogger } from './logger';
5
6
 
@@ -12,9 +13,15 @@ interface InstructionsCacheEntry {
12
13
 
13
14
  const instructionsCache = new Map<string, InstructionsCacheEntry>();
14
15
 
15
- export async function getInstructions(stainlessApiKey: string | undefined): Promise<string> {
16
+ export async function getInstructions({
17
+ stainlessApiKey,
18
+ customInstructionsPath,
19
+ }: {
20
+ stainlessApiKey?: string | undefined;
21
+ customInstructionsPath?: string | undefined;
22
+ }): Promise<string> {
16
23
  const now = Date.now();
17
- const cacheKey = stainlessApiKey ?? '';
24
+ const cacheKey = customInstructionsPath ?? stainlessApiKey ?? '';
18
25
  const cached = instructionsCache.get(cacheKey);
19
26
 
20
27
  if (cached && now - cached.fetchedAt <= INSTRUCTIONS_CACHE_TTL_MS) {
@@ -28,12 +35,28 @@ export async function getInstructions(stainlessApiKey: string | undefined): Prom
28
35
  }
29
36
  }
30
37
 
31
- const fetchedInstructions = await fetchLatestInstructions(stainlessApiKey);
38
+ let fetchedInstructions: string;
39
+
40
+ if (customInstructionsPath) {
41
+ fetchedInstructions = await fetchLatestInstructionsFromFile(customInstructionsPath);
42
+ } else {
43
+ fetchedInstructions = await fetchLatestInstructionsFromApi(stainlessApiKey);
44
+ }
45
+
32
46
  instructionsCache.set(cacheKey, { fetchedInstructions, fetchedAt: now });
33
47
  return fetchedInstructions;
34
48
  }
35
49
 
36
- async function fetchLatestInstructions(stainlessApiKey: string | undefined): Promise<string> {
50
+ async function fetchLatestInstructionsFromFile(path: string): Promise<string> {
51
+ try {
52
+ return await fs.readFile(path, 'utf-8');
53
+ } catch (error) {
54
+ getLogger().error({ error, path }, 'Error fetching instructions from file');
55
+ throw error;
56
+ }
57
+ }
58
+
59
+ async function fetchLatestInstructionsFromApi(stainlessApiKey: string | undefined): Promise<string> {
37
60
  // Setting the stainless API key is optional, but may be required
38
61
  // to authenticate requests to the Stainless API.
39
62
  const response = await fetch(