diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1cba3029..bad43001 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 }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2e04e49c..53ecb865 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.32.1" + ".": "0.33.0" } diff --git a/.stats.yml b/.stats.yml index bb13c776..51931843 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-d6e895ab5ce17b403a1981c9f3e3e1a357d2016683627cbbc725c10f6aa2e13a.yml +openapi_spec_hash: 36fc6b210e87fbd995fd578adcbe6626 config_hash: fd3005a8f140e5baadd3d25b3c9cd79f diff --git a/CHANGELOG.md b/CHANGELOG.md index bb79c277..018324b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## 0.33.0 (2026-03-10) + +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:** update dependencies to address dependabot vulnerabilities ([8879f0c](https://github.com/hyperspell/node-sdk/commit/8879f0cbd229d4d09417b38c11a4439380af80ac)) +* **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 43a06f58..794e2cc4 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", @@ -53,6 +53,17 @@ "bin": { "hyperspell": "bin/cli" }, + "overrides": { + "minimatch": "^9.0.5" + }, + "pnpm": { + "overrides": { + "minimatch": "^9.0.5" + } + }, + "resolutions": { + "minimatch": "^9.0.5" + }, "exports": { ".": { "import": "./dist/index.mjs", diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json index 9c4e49e0..246eadec 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 bf28a103..35f96c1c 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", @@ -26,21 +26,25 @@ "format": "prettier --write --cache --cache-strategy metadata . !dist", "prepare": "npm run build", "tsn": "ts-node -r tsconfig-paths/register", - "lint": "eslint --ext ts,js .", - "fix": "eslint --fix --ext ts,js ." + "lint": "eslint .", + "fix": "eslint --fix ." }, "dependencies": { "hyperspell": "file:../../dist/", + "ajv": "^8.18.0", "@cloudflare/cabidela": "^0.2.4", - "@modelcontextprotocol/sdk": "^1.26.0", + "@hono/node-server": "^1.19.10", + "@modelcontextprotocol/sdk": "^1.27.1", + "hono": "^4.12.4", "@valtown/deno-http-worker": "^0.0.21", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "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,14 +61,13 @@ "@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", "@typescript-eslint/parser": "8.31.1", - "eslint": "^8.49.0", - "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-unused-imports": "^3.0.0", + "eslint": "^9.39.1", + "eslint-plugin-prettier": "^5.4.1", + "eslint-plugin-unused-imports": "^4.1.4", "jest": "^29.4.0", "prettier": "^3.0.0", "ts-jest": "^29.1.0", diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 5ac53b5d..82438591 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 }; @@ -136,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', @@ -153,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 6a519f9e..1f3dbad6 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', @@ -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, + }, + '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 9a539974..fc401f40 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 654d25cf..5bca4a60 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 42ddc65a..4d6cd9cf 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,25 +51,15 @@ 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...', ); - 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; } diff --git a/packages/mcp-server/src/logger.ts b/packages/mcp-server/src/logger.ts new file mode 100644 index 00000000..29dab11c --- /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 069b8811..b9e8e8a6 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/server.ts b/packages/mcp-server/src/server.ts index 95fc3b7f..fe520856 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/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts index ceccaed3..e8bcbb19 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'); }; diff --git a/scripts/mock b/scripts/mock index 0b28f6ea..bcf3b392 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 diff --git a/src/client.ts b/src/client.ts index 867dd9c6..fead78ac 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)) { @@ -635,9 +636,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); } diff --git a/src/version.ts b/src/version.ts index ab5165fb..dc173dd4 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 diff --git a/tests/api-resources/memories.test.ts b/tests/api-resources/memories.test.ts index bd2d97bb..972ccd0e 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', @@ -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', }); diff --git a/yarn.lock b/yarn.lock index fc9f262b..078f09a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1219,15 +1219,7 @@ baseline-browser-mapping@^2.9.0: resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz#3b6af0bc032445bca04de58caa9a87cfe921cbb3" integrity sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg== -brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: +brace-expansion@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== @@ -1395,11 +1387,6 @@ commander@^10.0.1: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" @@ -2600,26 +2587,12 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2, minimatch@^5.0.1, minimatch@^9.0.4, minimatch@^9.0.5: + version "9.0.9" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" + integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== dependencies: - brace-expansion "^2.0.1" + brace-expansion "^2.0.2" minimist@^1.2.6: version "1.2.6"