-
Notifications
You must be signed in to change notification settings - Fork 1
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
8879f0c
f00dec6
16f74a7
a36859a
7340a84
35c5ba8
632cb03
ab78440
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 | ||
| config_hash: fd3005a8f140e5baadd3d25b3c9cd79f | ||
| openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-e94268bb224f0aa46f151c81dba49c8def81c73b48da8a6f31b4f8a60aa5055c.yml | ||
| openapi_spec_hash: 2e2f8148f72a724fbafd05c51b7a62c9 | ||
| config_hash: b387daed43fa717ef3ac5811ae06307c |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <hello@hyperspell.com>", | ||
| "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", | ||
|
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: ESLint v9 uses a flat config system ( 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| "@typescript-eslint/parser": "8.31.1", | ||
|
Comment on lines
65
to
67
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: ESLint 9 introduced a breaking flat config format ( 🤖 AI Agent Prompt for Cursor/Windsurf
Comment on lines
65
to
67
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: ESLint v9 introduces a flat config system ( 🤖 AI Agent Prompt for Cursor/Windsurf
Comment on lines
65
to
67
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: ESLint v9 uses a flat config format ( 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| "eslint": "^8.49.0", | ||
| "eslint-plugin-prettier": "^5.0.1", | ||
| "eslint-plugin-unused-imports": "^3.0.0", | ||
| "eslint": "^9.39.1", | ||
|
Comment on lines
66
to
+68
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: ESLint v9 uses a flat config system ( 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| "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", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. | ||
|
|
||
| export const workerPath = require.resolve('./code-tool-worker.mjs'); | ||
| export function getWorkerPath(): string { | ||
| return require.resolve('./code-tool-worker.mjs'); | ||
| } | ||
|
Comment on lines
2
to
+5
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 exported symbol changed from a constant 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,5 @@ | ||
| // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. | ||
|
|
||
| import fs from 'node:fs'; | ||
| import path from 'node:path'; | ||
| import url from 'node:url'; | ||
| import { newDenoHTTPWorker } from '@valtown/deno-http-worker'; | ||
| import { workerPath } from './code-tool-paths.cjs'; | ||
| import { | ||
| ContentBlock, | ||
| McpRequestContext, | ||
|
|
@@ -17,6 +12,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'; | ||
|
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
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: Affected Locations:
🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| import { SdkMethod } from './methods'; | ||
| import { McpCodeExecutionMode } from './options'; | ||
| import { ClientOptions } from 'hyperspell'; | ||
|
|
@@ -83,6 +79,8 @@ export function codeTool({ | |
| }, | ||
| }; | ||
|
|
||
|
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: Affected Locations:
🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| const logger = getLogger(); | ||
|
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
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: Affected Locations:
🤖 AI Agent Prompt for Cursor/Windsurf
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: Affected Locations:
🤖 AI Agent Prompt for Cursor/Windsurf
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
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: Affected Locations:
🤖 AI Agent Prompt for Cursor/Windsurf
|
||
|
|
||
| const handler = async ({ | ||
| reqContext, | ||
| args, | ||
|
|
@@ -107,11 +105,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 }; | ||
|
|
@@ -130,19 +144,23 @@ const remoteStainlessHandler = async ({ | |
|
|
||
| const codeModeEndpoint = readEnv('CODE_MODE_ENDPOINT_URL') ?? 'https://api.stainless.com/api/ai/code-tool'; | ||
|
|
||
| const localClientEnvs = { | ||
| HYPERSPELL_API_KEY: requireValue( | ||
| readEnv('HYPERSPELL_API_KEY') ?? client.apiKey, | ||
| 'set HYPERSPELL_API_KEY environment variable or provide apiKey client option', | ||
| ), | ||
| HYPERSPELL_BASE_URL: readEnv('HYPERSPELL_BASE_URL') ?? client.baseURL ?? undefined, | ||
| }; | ||
| // Merge any upstream client envs from the request header, with upstream values taking precedence. | ||
| const mergedClientEnvs = { ...localClientEnvs, ...reqContext.upstreamClientEnvs }; | ||
|
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
|
||
|
|
||
| // Setting a Stainless API key authenticates requests to the code tool endpoint. | ||
| const res = await fetch(codeModeEndpoint, { | ||
| method: 'POST', | ||
| headers: { | ||
| ...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }), | ||
| 'Content-Type': 'application/json', | ||
| 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', | ||
| ), | ||
| HYPERSPELL_BASE_URL: readEnv('HYPERSPELL_BASE_URL') ?? client.baseURL ?? undefined, | ||
| }), | ||
| 'x-stainless-mcp-client-envs': JSON.stringify(mergedClientEnvs), | ||
| }, | ||
| body: JSON.stringify({ | ||
| project_name: 'hyperspell', | ||
|
|
@@ -153,6 +171,11 @@ const remoteStainlessHandler = async ({ | |
| }); | ||
|
|
||
| if (!res.ok) { | ||
| if (res.status === 404 && !reqContext.stainlessApiKey) { | ||
| throw new Error( | ||
|
Comment on lines
171
to
+175
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: A 404 response without a Stainless API key doesn't necessarily mean the project needs authentication — the endpoint or project itself might genuinely not exist, or the URL could be misconfigured. This misleads users into providing an API key when the real issue may be something else entirely (e.g., wrong 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| '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.', | ||
|
Comment on lines
171
to
+176
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 404 check assumes that a missing API key is the only reason for a 404, but the endpoint could return 404 for other reasons (e.g., the project 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| ); | ||
| } | ||
| throw new Error( | ||
| `${res.status}: ${ | ||
| res.statusText | ||
|
|
@@ -180,6 +203,13 @@ const localDenoHandler = async ({ | |
| reqContext: McpRequestContext; | ||
| args: unknown; | ||
| }): Promise<ToolCallResult> => { | ||
| const fs = await import('node:fs'); | ||
| const path = await import('node:path'); | ||
| const url = await import('node:url'); | ||
| const { newDenoHTTPWorker } = await import('@valtown/deno-http-worker'); | ||
| const { getWorkerPath } = await import('./code-tool-paths.cjs'); | ||
| const workerPath = getWorkerPath(); | ||
|
|
||
| const client = reqContext.client; | ||
| const baseURLHostname = new URL(client.baseURL).hostname; | ||
| const { code } = args as { code: string }; | ||
|
|
@@ -241,6 +271,9 @@ const localDenoHandler = async ({ | |
| printOutput: true, | ||
| spawnOptions: { | ||
| cwd: path.dirname(workerPath), | ||
| // Merge any upstream client envs into the Deno subprocess environment, | ||
| // with the upstream env vars taking precedence. | ||
| env: { ...process.env, ...reqContext.upstreamClientEnvs }, | ||
|
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: Spreading 🤖 AI Agent Prompt for Cursor/Windsurf
|
||
| }, | ||
| }); | ||
|
|
||
|
|
@@ -250,14 +283,18 @@ const localDenoHandler = async ({ | |
| reject(new Error(`Worker exited with code ${exitCode}`)); | ||
| }); | ||
|
|
||
| const opts: ClientOptions = { | ||
| baseURL: client.baseURL, | ||
| apiKey: client.apiKey, | ||
| userID: client.userID, | ||
| defaultHeaders: { | ||
| 'X-Stainless-MCP': 'true', | ||
| }, | ||
| }; | ||
| // Strip null/undefined values so that the worker SDK client can fall back to | ||
| // reading from environment variables (including any upstreamClientEnvs). | ||
| const opts: ClientOptions = Object.fromEntries( | ||
| Object.entries({ | ||
| baseURL: client.baseURL, | ||
| apiKey: client.apiKey, | ||
| userID: client.userID, | ||
| defaultHeaders: { | ||
| 'X-Stainless-MCP': 'true', | ||
| }, | ||
| }).filter(([_, v]) => v != null), | ||
| ) as ClientOptions; | ||
|
|
||
| const req = worker.request( | ||
| 'http://localhost', | ||
|
|
||
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: The
packageManagerfield specifiesyarn@1.22.22, which respectsresolutionsbut ignoresoverridesandpnpm.overrides. Additionally, Yarn 1 resolutions officially require an exact version rather than a range (e.g.,9.0.5instead of^9.0.5) to ensure the dependency is pinned correctly.🤖 AI Agent Prompt for Cursor/Windsurf