From c5c7f31a366080de58b305429d0bcb84dec91493 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:28:28 +0000 Subject: [PATCH 01/12] feat(api): api update --- .stats.yml | 4 ++-- tests/api-resources/memories.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index bb13c77..624dbcd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -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-74ec424b735b297993534ab70f3a67ace300f8fdf420513f13567392510dd74f.yml +openapi_spec_hash: 86a1f32a14643bcee413662f7ae4b10f config_hash: fd3005a8f140e5baadd3d25b3c9cd79f diff --git a/tests/api-resources/memories.test.ts b/tests/api-resources/memories.test.ts index bd2d97b..390ba25 100644 --- a/tests/api-resources/memories.test.ts +++ b/tests/api-resources/memories.test.ts @@ -165,7 +165,7 @@ describe('resource memories', () => { google_calendar: { calendar_id: 'calendar_id', weight: 0 }, google_drive: { weight: 0 }, google_mail: { label_ids: ['string'], weight: 0 }, - max_results: 0, + max_results: 200, notion: { notion_page_ids: ['string'], weight: 0 }, reddit: { period: 'hour', From e7684c45c0ad25bf0015ccd24d98ea182f8d6055 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 06:39:22 +0000 Subject: [PATCH 02/12] chore(internal): codegen related update --- packages/mcp-server/package.json | 6 +- packages/mcp-server/src/code-tool.ts | 23 ++++++- packages/mcp-server/src/docs-search-tool.ts | 30 ++++++++- packages/mcp-server/src/http.ts | 74 +++++++++++++++------ packages/mcp-server/src/index.ts | 26 ++++---- packages/mcp-server/src/instructions.ts | 3 +- packages/mcp-server/src/logger.ts | 28 ++++++++ packages/mcp-server/src/options.ts | 11 +++ packages/mcp-server/src/stdio.ts | 3 +- 9 files changed, 161 insertions(+), 43 deletions(-) create mode 100644 packages/mcp-server/src/logger.ts diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index bf28a10..f1d66f3 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -39,8 +39,9 @@ "express": "^5.1.0", "fuse.js": "^7.1.0", "jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz", - "morgan": "^1.10.0", - "morgan-body": "^2.6.9", + "pino": "^10.3.1", + "pino-http": "^11.0.0", + "pino-pretty": "^13.1.3", "qs": "^6.14.1", "typescript": "5.8.3", "yargs": "^17.7.2", @@ -57,7 +58,6 @@ "@types/cors": "^2.8.19", "@types/express": "^5.0.3", "@types/jest": "^29.4.0", - "@types/morgan": "^1.9.10", "@types/qs": "^6.14.0", "@types/yargs": "^17.0.8", "@typescript-eslint/eslint-plugin": "8.31.1", diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 5ac53b5..1c37417 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -17,6 +17,7 @@ import { import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { readEnv, requireValue } from './util'; import { WorkerInput, WorkerOutput } from './code-tool-types'; +import { getLogger } from './logger'; import { SdkMethod } from './methods'; import { McpCodeExecutionMode } from './options'; import { ClientOptions } from 'hyperspell'; @@ -83,6 +84,8 @@ export function codeTool({ }, }; + const logger = getLogger(); + const handler = async ({ reqContext, args, @@ -107,11 +110,27 @@ export function codeTool({ } } + let result: ToolCallResult; + const startTime = Date.now(); + if (codeExecutionMode === 'local') { - return await localDenoHandler({ reqContext, args }); + logger.debug('Executing code in local Deno environment'); + result = await localDenoHandler({ reqContext, args }); } else { - return await remoteStainlessHandler({ reqContext, args }); + logger.debug('Executing code in remote Stainless environment'); + result = await remoteStainlessHandler({ reqContext, args }); } + + logger.info( + { + codeExecutionMode, + durationMs: Date.now() - startTime, + isError: result.isError, + contentRows: result.content?.length ?? 0, + }, + 'Got code tool execution result', + ); + return result; }; return { metadata, tool, handler }; diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index 6a519f9..ca98e6d 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -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', @@ -50,19 +51,42 @@ 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', + ); 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, + }, + 'Got docs search result', + ); + return asTextContentResult(resultBody); }; export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts index 9a53997..fc401f4 100644 --- a/packages/mcp-server/src/http.ts +++ b/packages/mcp-server/src/http.ts @@ -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'; @@ -70,29 +71,60 @@ const del = async (req: express.Request, res: express.Response) => { }); }; +const redactHeaders = (headers: Record) => { + 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), + }; + }), + }, + }), + ); 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(); + 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}`); } }; diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 654d25c..5bca4a6 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -5,15 +5,20 @@ import { McpOptions, parseCLIOptions } from './options'; import { launchStdioServer } from './stdio'; import { launchStreamableHTTPServer } from './http'; import type { McpTool } from './types'; +import { configureLogger, getLogger } from './logger'; async function main() { const options = parseOptionsOrError(); + configureLogger({ + level: options.debug ? 'debug' : 'info', + pretty: options.logFormat === 'pretty', + }); const selectedTools = await selectToolsOrError(options); - console.error( - `MCP Server starting with ${selectedTools.length} tools:`, - selectedTools.map((e) => e.tool.name), + getLogger().info( + { tools: selectedTools.map((e) => e.tool.name) }, + `MCP Server starting with ${selectedTools.length} tools`, ); switch (options.transport) { @@ -23,7 +28,6 @@ async function main() { case 'http': await launchStreamableHTTPServer({ mcpOptions: options, - debug: options.debug, port: options.socket ?? options.port, }); break; @@ -32,7 +36,8 @@ async function main() { if (require.main === module) { main().catch((error) => { - console.error('Fatal error in main():', error); + // Logger might not be initialized yet + console.error('Fatal error in main()', error); process.exit(1); }); } @@ -41,7 +46,8 @@ function parseOptionsOrError() { try { return parseCLIOptions(); } catch (error) { - console.error('Error parsing options:', error); + // Logger is initialized after options, so use console.error here + console.error('Error parsing options', error); process.exit(1); } } @@ -50,16 +56,12 @@ async function selectToolsOrError(options: McpOptions): Promise { try { const includedTools = selectTools(options); if (includedTools.length === 0) { - console.error('No tools match the provided filters.'); + getLogger().error('No tools match the provided filters'); process.exit(1); } return includedTools; } catch (error) { - if (error instanceof Error) { - console.error('Error filtering tools:', error.message); - } else { - console.error('Error filtering tools:', error); - } + getLogger().error({ error }, 'Error filtering tools'); process.exit(1); } } diff --git a/packages/mcp-server/src/instructions.ts b/packages/mcp-server/src/instructions.ts index 42ddc65..747d087 100644 --- a/packages/mcp-server/src/instructions.ts +++ b/packages/mcp-server/src/instructions.ts @@ -1,6 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { readEnv } from './util'; +import { getLogger } from './logger'; const INSTRUCTIONS_CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes @@ -50,7 +51,7 @@ async function fetchLatestInstructions(stainlessApiKey: string | undefined): Pro let instructions: string | undefined; if (!response.ok) { - console.warn( + getLogger().warn( 'Warning: failed to retrieve MCP server instructions. Proceeding with default instructions...', ); diff --git a/packages/mcp-server/src/logger.ts b/packages/mcp-server/src/logger.ts new file mode 100644 index 0000000..29dab11 --- /dev/null +++ b/packages/mcp-server/src/logger.ts @@ -0,0 +1,28 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { pino, type Level, type Logger } from 'pino'; +import pretty from 'pino-pretty'; + +let _logger: Logger | undefined; + +export function configureLogger({ level, pretty: usePretty }: { level: Level; pretty: boolean }): void { + _logger = pino( + { + level, + timestamp: pino.stdTimeFunctions.isoTime, + formatters: { + level(label) { + return { level: label }; + }, + }, + }, + usePretty ? pretty({ colorize: true, levelFirst: true, destination: 2 }) : process.stderr, + ); +} + +export function getLogger(): Logger { + if (!_logger) { + throw new Error('Logger has not been configured. Call configureLogger() before using the logger.'); + } + return _logger; +} diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index 069b881..b9e8e8a 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -8,6 +8,7 @@ import { readEnv } from './util'; export type CLIOptions = McpOptions & { debug: boolean; + logFormat: 'json' | 'pretty'; transport: 'stdio' | 'http'; port: number | undefined; socket: string | undefined; @@ -52,6 +53,11 @@ export function parseCLIOptions(): CLIOptions { "Where to run code execution in code tool; 'stainless-sandbox' will execute code in Stainless-hosted sandboxes whereas 'local' will execute code locally on the MCP server machine.", }) .option('debug', { type: 'boolean', description: 'Enable debug logging' }) + .option('log-format', { + type: 'string', + choices: ['json', 'pretty'], + description: 'Format for log output; defaults to json unless tty is detected', + }) .option('no-tools', { type: 'string', array: true, @@ -97,6 +103,10 @@ export function parseCLIOptions(): CLIOptions { const includeDocsTools = shouldIncludeToolType('docs'); const transport = argv.transport as 'stdio' | 'http'; + const logFormat = + argv.logFormat ? (argv.logFormat as 'json' | 'pretty') + : process.stderr.isTTY ? 'pretty' + : 'json'; return { ...(includeCodeTool !== undefined && { includeCodeTool }), @@ -108,6 +118,7 @@ export function parseCLIOptions(): CLIOptions { codeBlockedMethods: argv.codeBlockedMethods, codeExecutionMode: argv.codeExecutionMode as McpCodeExecutionMode, transport, + logFormat, port: argv.port, socket: argv.socket, }; diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts index ceccaed..e8bcbb1 100644 --- a/packages/mcp-server/src/stdio.ts +++ b/packages/mcp-server/src/stdio.ts @@ -1,6 +1,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { McpOptions } from './options'; import { initMcpServer, newMcpServer } from './server'; +import { getLogger } from './logger'; export const launchStdioServer = async (mcpOptions: McpOptions) => { const server = await newMcpServer(mcpOptions.stainlessApiKey); @@ -9,5 +10,5 @@ export const launchStdioServer = async (mcpOptions: McpOptions) => { const transport = new StdioServerTransport(); await server.connect(transport); - console.error('MCP Server running on stdio'); + getLogger().info('MCP Server running on stdio'); }; From 2750b02b4f1e091728ed4c9d202cd3387ed51294 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 07:03:18 +0000 Subject: [PATCH 03/12] chore(mcp-server): return access instructions for 404 without API key --- packages/mcp-server/src/code-tool.ts | 5 +++++ packages/mcp-server/src/docs-search-tool.ts | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 1c37417..44305f7 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -172,6 +172,11 @@ const remoteStainlessHandler = async ({ }); if (!res.ok) { + if (res.status === 404 && !reqContext.stainlessApiKey) { + throw new Error( + 'Could not access code tool 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( `${res.status}: ${ res.statusText diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index ca98e6d..c0c70bd 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -73,6 +73,13 @@ export const handler = async ({ }, '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}`, ); From f8b4ea07e91449a9262938777c8c32a3b30c9aba Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 05:45:51 +0000 Subject: [PATCH 04/12] chore(internal): use x-stainless-mcp-client-envs header for MCP remote code tool calls --- packages/mcp-server/src/code-tool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 44305f7..8243859 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -155,7 +155,7 @@ const remoteStainlessHandler = async ({ headers: { ...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }), 'Content-Type': 'application/json', - client_envs: JSON.stringify({ + 'x-stainless-mcp-client-envs': JSON.stringify({ HYPERSPELL_API_KEY: requireValue( readEnv('HYPERSPELL_API_KEY') ?? client.apiKey, 'set HYPERSPELL_API_KEY environment variable or provide apiKey client option', From 7aded9232a6d433b940fb5ae67a66adbce3c971a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 01:28:24 +0000 Subject: [PATCH 05/12] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 624dbcd..5193184 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 23 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-74ec424b735b297993534ab70f3a67ace300f8fdf420513f13567392510dd74f.yml -openapi_spec_hash: 86a1f32a14643bcee413662f7ae4b10f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-d6e895ab5ce17b403a1981c9f3e3e1a357d2016683627cbbc725c10f6aa2e13a.yml +openapi_spec_hash: 36fc6b210e87fbd995fd578adcbe6626 config_hash: fd3005a8f140e5baadd3d25b3c9cd79f From c49250ccd40e962f760fcec44f6ecd85856a1578 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 05:36:34 +0000 Subject: [PATCH 06/12] chore(internal): codegen related update --- src/client.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.ts b/src/client.ts index 867dd9c..09cbdfc 100644 --- a/src/client.ts +++ b/src/client.ts @@ -635,9 +635,9 @@ export class Hyperspell { } } - // If the API asks us to wait a certain amount of time (and it's a reasonable amount), - // just do what it says, but otherwise calculate a default - if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) { + // If the API asks us to wait a certain amount of time, just do what it + // says, but otherwise calculate a default + if (timeoutMillis === undefined) { const maxRetries = options.maxRetries ?? this.maxRetries; timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries); } From 1d19a8e8ca7be9782a217c28f12c0329931a725d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 05:51:51 +0000 Subject: [PATCH 07/12] chore(test): do not count install time for mock server timeout --- scripts/mock | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/mock b/scripts/mock index 0b28f6e..bcf3b39 100755 --- a/scripts/mock +++ b/scripts/mock @@ -21,11 +21,22 @@ echo "==> Starting mock server with URL ${URL}" # Run prism mock on the given spec if [ "$1" == "--daemon" ]; then + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism --version + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & - # Wait for server to come online + # Wait for server to come online (max 30s) echo -n "Waiting for server" + attempts=0 while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Prism server to start" + cat .prism.log + exit 1 + fi echo -n "." sleep 0.1 done From 6a453f9854dd43f6ee2d20674cd31e72743327f4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:04:36 +0000 Subject: [PATCH 08/12] chore(ci): skip uploading artifacts on stainless-internal branches --- .github/workflows/ci.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1cba302..bad4300 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,14 +55,18 @@ jobs: run: ./scripts/build - name: Get GitHub OIDC Token - if: github.repository == 'stainless-sdks/hyperspell-typescript' + if: |- + github.repository == 'stainless-sdks/hyperspell-typescript' && + !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc uses: actions/github-script@v8 with: script: core.setOutput('github_token', await core.getIDToken()); - name: Upload tarball - if: github.repository == 'stainless-sdks/hyperspell-typescript' + if: |- + github.repository == 'stainless-sdks/hyperspell-typescript' && + !startsWith(github.ref, 'refs/heads/stl/') env: URL: https://pkg.stainless.com/s AUTH: ${{ steps.github-oidc.outputs.github_token }} @@ -70,7 +74,9 @@ jobs: run: ./scripts/utils/upload-artifact.sh - name: Upload MCP Server tarball - if: github.repository == 'stainless-sdks/hyperspell-typescript' + if: |- + github.repository == 'stainless-sdks/hyperspell-typescript' && + !startsWith(github.ref, 'refs/heads/stl/') env: URL: https://pkg.stainless.com/s?subpackage=mcp-server AUTH: ${{ steps.github-oidc.outputs.github_token }} From b91b5935f49d8d5be04738bd2cda7dce878ea736 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:06:14 +0000 Subject: [PATCH 09/12] chore: update placeholder string --- tests/api-resources/memories.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api-resources/memories.test.ts b/tests/api-resources/memories.test.ts index 390ba25..972ccd0 100644 --- a/tests/api-resources/memories.test.ts +++ b/tests/api-resources/memories.test.ts @@ -206,7 +206,7 @@ describe('resource memories', () => { test('upload: only required params', async () => { const responsePromise = client.memories.upload({ - file: await toFile(Buffer.from('# my file contents'), 'README.md'), + file: await toFile(Buffer.from('Example data'), 'README.md'), }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); @@ -219,7 +219,7 @@ describe('resource memories', () => { test('upload: required and optional params', async () => { const response = await client.memories.upload({ - file: await toFile(Buffer.from('# my file contents'), 'README.md'), + file: await toFile(Buffer.from('Example data'), 'README.md'), collection: 'collection', metadata: 'metadata', }); From af6d83b7176a0e8672c1ee54a6db5a4bd48d4bd9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:10:37 +0000 Subject: [PATCH 10/12] fix(client): preserve URL params already embedded in path --- src/client.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index 09cbdfc..fead78a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -301,8 +301,9 @@ export class Hyperspell { : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); const defaultQuery = this.defaultQuery(); - if (!isEmptyObj(defaultQuery)) { - query = { ...defaultQuery, ...query }; + const pathQuery = Object.fromEntries(url.searchParams); + if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) { + query = { ...pathQuery, ...defaultQuery, ...query }; } if (typeof query === 'object' && query && !Array.isArray(query)) { From 604dcddc6a332f9990e06eacf0edcb64bb2fd171 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:17:37 +0000 Subject: [PATCH 11/12] chore(mcp-server): improve instructions --- packages/mcp-server/src/docs-search-tool.ts | 3 ++- packages/mcp-server/src/instructions.ts | 14 ++------------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index c0c70bd..1f3dbad 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -13,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: { diff --git a/packages/mcp-server/src/instructions.ts b/packages/mcp-server/src/instructions.ts index 747d087..4d6cd9c 100644 --- a/packages/mcp-server/src/instructions.ts +++ b/packages/mcp-server/src/instructions.ts @@ -55,21 +55,11 @@ async function fetchLatestInstructions(stainlessApiKey: string | undefined): Pro 'Warning: failed to retrieve MCP server instructions. Proceeding with default instructions...', ); - instructions = ` - This is the hyperspell MCP server. You will use Code Mode to help the user perform - actions. You can use search_docs tool to learn about how to take action with this server. Then, - you will write TypeScript code using the execute tool take action. It is CRITICAL that you be - thoughtful and deliberate when executing code. Always try to entirely solve the problem in code - block: it can be as long as you need to get the job done! - `; + instructions = + '\n This is the hyperspell MCP server.\n\n Available tools:\n - search_docs: Search SDK documentation to find the right methods and parameters.\n - execute: Run TypeScript code against a pre-authenticated SDK client. Define an async run(client) function.\n\n Workflow:\n - If unsure about the API, call search_docs first.\n - Write complete solutions in a single execute call when possible. For large datasets, use API filters to narrow results or paginate within a single execute block.\n - If execute returns an error, read the error and fix your code rather than retrying the same approach.\n - Variables do not persist between execute calls. Return or log all data you need.\n - Individual HTTP requests to the API have a 30-second timeout. If a request times out, try a smaller query or add filters.\n - Code execution has a total timeout of approximately 5 minutes. If your code times out, simplify it or break it into smaller steps.\n '; } instructions ??= ((await response.json()) as { instructions: string }).instructions; - instructions = ` - If needed, you can get the current time by executing Date.now(). - - ${instructions} - `; return instructions; } From 1c23c70afec8d7b99e512ca3a996c405e9e26dfc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:29:41 +0000 Subject: [PATCH 12/12] release: 0.33.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 25 +++++++++++++++++++++++++ package.json | 2 +- packages/mcp-server/manifest.json | 2 +- packages/mcp-server/package.json | 2 +- packages/mcp-server/src/server.ts | 2 +- src/version.ts | 2 +- 7 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2e04e49..53ecb86 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.32.1" + ".": "0.33.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index bb79c27..6f04d80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 0.33.0 (2026-03-08) + +Full Changelog: [v0.32.1...v0.33.0](https://github.com/hyperspell/node-sdk/compare/v0.32.1...v0.33.0) + +### Features + +* **api:** api update ([c5c7f31](https://github.com/hyperspell/node-sdk/commit/c5c7f31a366080de58b305429d0bcb84dec91493)) + + +### Bug Fixes + +* **client:** preserve URL params already embedded in path ([af6d83b](https://github.com/hyperspell/node-sdk/commit/af6d83b7176a0e8672c1ee54a6db5a4bd48d4bd9)) + + +### Chores + +* **ci:** skip uploading artifacts on stainless-internal branches ([6a453f9](https://github.com/hyperspell/node-sdk/commit/6a453f9854dd43f6ee2d20674cd31e72743327f4)) +* **internal:** codegen related update ([c49250c](https://github.com/hyperspell/node-sdk/commit/c49250ccd40e962f760fcec44f6ecd85856a1578)) +* **internal:** codegen related update ([e7684c4](https://github.com/hyperspell/node-sdk/commit/e7684c45c0ad25bf0015ccd24d98ea182f8d6055)) +* **internal:** use x-stainless-mcp-client-envs header for MCP remote code tool calls ([f8b4ea0](https://github.com/hyperspell/node-sdk/commit/f8b4ea07e91449a9262938777c8c32a3b30c9aba)) +* **mcp-server:** improve instructions ([604dcdd](https://github.com/hyperspell/node-sdk/commit/604dcddc6a332f9990e06eacf0edcb64bb2fd171)) +* **mcp-server:** return access instructions for 404 without API key ([2750b02](https://github.com/hyperspell/node-sdk/commit/2750b02b4f1e091728ed4c9d202cd3387ed51294)) +* **test:** do not count install time for mock server timeout ([1d19a8e](https://github.com/hyperspell/node-sdk/commit/1d19a8e8ca7be9782a217c28f12c0329931a725d)) +* update placeholder string ([b91b593](https://github.com/hyperspell/node-sdk/commit/b91b5935f49d8d5be04738bd2cda7dce878ea736)) + ## 0.32.1 (2026-03-02) Full Changelog: [v0.32.0...v0.32.1](https://github.com/hyperspell/node-sdk/compare/v0.32.0...v0.32.1) diff --git a/package.json b/package.json index 43a06f5..19f3a6a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hyperspell", - "version": "0.32.1", + "version": "0.33.0", "description": "The official TypeScript library for the Hyperspell API", "author": "Hyperspell ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json index 9c4e49e..246eade 100644 --- a/packages/mcp-server/manifest.json +++ b/packages/mcp-server/manifest.json @@ -1,7 +1,7 @@ { "dxt_version": "0.2", "name": "hyperspell-mcp", - "version": "0.32.1", + "version": "0.33.0", "description": "The official MCP Server for the Hyperspell API", "author": { "name": "Hyperspell", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index f1d66f3..639468c 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "hyperspell-mcp", - "version": "0.32.1", + "version": "0.33.0", "description": "The official MCP Server for the Hyperspell API", "author": "Hyperspell ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 95fc3b7..fe52085 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -21,7 +21,7 @@ export const newMcpServer = async (stainlessApiKey: string | undefined) => new McpServer( { name: 'hyperspell_api', - version: '0.32.1', + version: '0.33.0', }, { instructions: await getInstructions(stainlessApiKey), diff --git a/src/version.ts b/src/version.ts index ab5165f..dc173dd 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.32.1'; // x-release-please-version +export const VERSION = '0.33.0'; // x-release-please-version