-
Notifications
You must be signed in to change notification settings - Fork 0
release: 0.33.0 #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
release: 0.33.0 #84
Changes from all commits
c5c7f31
e7684c4
2750b02
f8b4ea0
7aded92
c49250c
1d19a8e
6a453f9
b91b593
af6d83b
604dcdd
1c23c70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| { | ||
| ".": "0.32.1" | ||
| ".": "0.33.0" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| configured_endpoints: 23 | ||
| openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ca07e6605f61ae00e12be55df648b38e467a31d505fdeec7879c8a9ea9e1b390.yml | ||
| openapi_spec_hash: 25915d4fcda54adbd8a7f106d8af2d65 | ||
| openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-d6e895ab5ce17b403a1981c9f3e3e1a357d2016683627cbbc725c10f6aa2e13a.yml | ||
| openapi_spec_hash: 36fc6b210e87fbd995fd578adcbe6626 | ||
| config_hash: fd3005a8f140e5baadd3d25b3c9cd79f |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. | ||
|
|
||
| import { Metadata, McpRequestContext, asTextContentResult } from './types'; | ||
| import { Tool } from '@modelcontextprotocol/sdk/types.js'; | ||
| import { Metadata, McpRequestContext, asTextContentResult } from './types'; | ||
| import { getLogger } from './logger'; | ||
|
|
||
| export const metadata: Metadata = { | ||
| resource: 'all', | ||
|
|
@@ -12,7 +13,8 @@ export const metadata: Metadata = { | |
|
|
||
| export const tool: Tool = { | ||
| name: 'search_docs', | ||
| description: 'Search for documentation for how to use the client to interact with the API.', | ||
| 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: { | ||
|
|
@@ -50,19 +52,49 @@ export const handler = async ({ | |
| }) => { | ||
| 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 }), | ||
| }, | ||
| }); | ||
|
|
||
| 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: ${await result.text()}`, | ||
| `${result.status}: ${result.statusText} when using doc search tool. Details: ${errorText}`, | ||
| ); | ||
| } | ||
|
|
||
| return asTextContentResult(await result.json()); | ||
| const resultBody = await result.json(); | ||
| logger.info( | ||
| { | ||
| durationMs: Date.now() - startTime, | ||
| query: body.query, | ||
|
Comment on lines
+70
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correctness: 🚫 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| }, | ||
| 'Got docs search result', | ||
| ); | ||
| return asTextContentResult(resultBody); | ||
| }; | ||
|
|
||
| export default { metadata, tool, handler }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,10 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; | |
| import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; | ||
| import { ClientOptions } from 'hyperspell'; | ||
| import express from 'express'; | ||
| import morgan from 'morgan'; | ||
| import morganBody from 'morgan-body'; | ||
| import pino from 'pino'; | ||
| import pinoHttp from 'pino-http'; | ||
| import { getStainlessApiKey, parseClientAuthHeaders } from './auth'; | ||
| import { getLogger } from './logger'; | ||
| import { McpOptions } from './options'; | ||
| import { initMcpServer, newMcpServer } from './server'; | ||
|
|
||
|
Comment on lines
4
to
13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correctness: The introduction of |
||
|
|
@@ -70,29 +71,60 @@ const del = async (req: express.Request, res: express.Response) => { | |
| }); | ||
| }; | ||
|
|
||
| const redactHeaders = (headers: Record<string, any>) => { | ||
| const hiddenHeaders = /auth|cookie|key|token/i; | ||
| const filtered = { ...headers }; | ||
| Object.keys(filtered).forEach((key) => { | ||
| if (hiddenHeaders.test(key)) { | ||
| filtered[key] = '[REDACTED]'; | ||
| } | ||
| }); | ||
| return filtered; | ||
| }; | ||
|
|
||
| export const streamableHTTPApp = ({ | ||
| clientOptions = {}, | ||
| mcpOptions, | ||
| debug, | ||
| }: { | ||
| clientOptions?: ClientOptions; | ||
| mcpOptions: McpOptions; | ||
| debug: boolean; | ||
| }): express.Express => { | ||
| const app = express(); | ||
| app.set('query parser', 'extended'); | ||
| app.use(express.json()); | ||
|
|
||
| if (debug) { | ||
| morganBody(app, { | ||
| logAllReqHeader: true, | ||
| logAllResHeader: true, | ||
| logRequestBody: true, | ||
| logResponseBody: true, | ||
| }); | ||
| } else { | ||
| app.use(morgan('combined')); | ||
| } | ||
| app.use( | ||
| pinoHttp({ | ||
| logger: getLogger(), | ||
| customLogLevel: (req, res) => { | ||
| if (res.statusCode >= 500) { | ||
| return 'error'; | ||
| } else if (res.statusCode >= 400) { | ||
| return 'warn'; | ||
| } | ||
| return 'info'; | ||
| }, | ||
| customSuccessMessage: function (req, res) { | ||
| return `Request ${req.method} to ${req.url} completed with status ${res.statusCode}`; | ||
| }, | ||
| customErrorMessage: function (req, res, err) { | ||
| return `Request ${req.method} to ${req.url} errored with status ${res.statusCode}`; | ||
| }, | ||
| serializers: { | ||
| req: pino.stdSerializers.wrapRequestSerializer((req) => { | ||
| return { | ||
| ...req, | ||
| headers: redactHeaders(req.raw.headers), | ||
| }; | ||
| }), | ||
| res: pino.stdSerializers.wrapResponseSerializer((res) => { | ||
| return { | ||
| ...res, | ||
| headers: redactHeaders(res.headers), | ||
| }; | ||
|
Comment on lines
+115
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correctness: 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| }), | ||
| }, | ||
| }), | ||
| ); | ||
|
|
||
| app.get('/health', async (req: express.Request, res: express.Response) => { | ||
| res.status(200).send('OK'); | ||
|
|
@@ -106,22 +138,22 @@ export const streamableHTTPApp = ({ | |
|
|
||
| export const launchStreamableHTTPServer = async ({ | ||
| mcpOptions, | ||
| debug, | ||
| port, | ||
| }: { | ||
| mcpOptions: McpOptions; | ||
| debug: boolean; | ||
| port: number | string | undefined; | ||
| }) => { | ||
| const app = streamableHTTPApp({ mcpOptions, debug }); | ||
| const app = streamableHTTPApp({ mcpOptions }); | ||
| const server = app.listen(port); | ||
| const address = server.address(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correctness: Using 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
|
|
||
| const logger = getLogger(); | ||
|
|
||
| if (typeof address === 'string') { | ||
| console.error(`MCP Server running on streamable HTTP at ${address}`); | ||
| logger.info(`MCP Server running on streamable HTTP at ${address}`); | ||
| } else if (address !== null) { | ||
| console.error(`MCP Server running on streamable HTTP on port ${address.port}`); | ||
| logger.info(`MCP Server running on streamable HTTP on port ${address.port}`); | ||
| } else { | ||
| console.error(`MCP Server running on streamable HTTP on port ${port}`); | ||
| logger.info(`MCP Server running on streamable HTTP on port ${port}`); | ||
| } | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correctness: Calling
getLogger()introduces a reliability regression. SincegetLogger()throws an error if the logger is not configured, this handler will now crash in environments where logging is not initialized, whereas it previously functioned correctly. Guard the logger call or provide a fallback to ensure the tool remains functional regardless of the logging state.🤖 AI Agent Prompt for Cursor/Windsurf