Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c5c7f31
feat(api): api update
stainless-app[bot] Mar 3, 2026
e7684c4
chore(internal): codegen related update
stainless-app[bot] Mar 4, 2026
2750b02
chore(mcp-server): return access instructions for 404 without API key
stainless-app[bot] Mar 4, 2026
f8b4ea0
chore(internal): use x-stainless-mcp-client-envs header for MCP remot…
stainless-app[bot] Mar 5, 2026
7aded92
codegen metadata
stainless-app[bot] Mar 6, 2026
c49250c
chore(internal): codegen related update
stainless-app[bot] Mar 7, 2026
1d19a8e
chore(test): do not count install time for mock server timeout
stainless-app[bot] Mar 7, 2026
6a453f9
chore(ci): skip uploading artifacts on stainless-internal branches
stainless-app[bot] Mar 8, 2026
b91b593
chore: update placeholder string
stainless-app[bot] Mar 8, 2026
af6d83b
fix(client): preserve URL params already embedded in path
stainless-app[bot] Mar 8, 2026
604dcdd
chore(mcp-server): improve instructions
stainless-app[bot] Mar 8, 2026
8879f0c
chore(internal): update dependencies to address dependabot vulnerabil…
stainless-app[bot] Mar 10, 2026
f00dec6
feat(api): api update
stainless-app[bot] Mar 10, 2026
16f74a7
chore(internal): bump @modelcontextprotocol/sdk, @hono/node-server, a…
stainless-app[bot] Mar 12, 2026
a36859a
feat(api): api update
stainless-app[bot] Mar 12, 2026
7340a84
feat(api): manual updates
stainless-app[bot] Mar 14, 2026
35c5ba8
chore(internal): make generated MCP servers compatible with Cloudflar…
stainless-app[bot] Mar 14, 2026
632cb03
chore(internal): support x-stainless-mcp-client-envs header in MCP se…
stainless-app[bot] Mar 14, 2026
ab78440
release: 0.33.0
stainless-app[bot] Mar 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,28 @@ 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 }}
SHA: ${{ github.sha }}
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 }}
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.32.1"
".": "0.33.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
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
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# Changelog

## 0.33.0 (2026-03-14)

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 ([a36859a](https://github.com/hyperspell/node-sdk/commit/a36859af78f57d190f59bbf9352a3412dfff5356))
* **api:** api update ([f00dec6](https://github.com/hyperspell/node-sdk/commit/f00dec64dd7b4a8fa5bdbb52ee383149d6326898))
* **api:** api update ([c5c7f31](https://github.com/hyperspell/node-sdk/commit/c5c7f31a366080de58b305429d0bcb84dec91493))
* **api:** manual updates ([7340a84](https://github.com/hyperspell/node-sdk/commit/7340a849189533a89d897d4dc87a890aa0abf73b))


### 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:** bump @modelcontextprotocol/sdk, @hono/node-server, and minimatch ([16f74a7](https://github.com/hyperspell/node-sdk/commit/16f74a7124c551479b49242ca83b595c85712b20))
* **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:** make generated MCP servers compatible with Cloudflare worker environments ([35c5ba8](https://github.com/hyperspell/node-sdk/commit/35c5ba8825288782602fce19cd250aa17d7fa6e4))
* **internal:** support x-stainless-mcp-client-envs header in MCP servers ([632cb03](https://github.com/hyperspell/node-sdk/commit/632cb03f6161d2c38c78c33753de58f84e97e9b6))
* **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)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ You can use the `for await … of` syntax to iterate through items across all pa
async function fetchAllMemoryListResponses(params) {
const allMemoryListResponses = [];
// Automatically fetches more pages as needed.
for await (const memoryListResponse of client.memories.list({ collection: 'REPLACE_ME' })) {
for await (const memoryListResponse of client.memories.list()) {
allMemoryListResponses.push(memoryListResponse);
}
return allMemoryListResponses;
Expand All @@ -180,7 +180,7 @@ async function fetchAllMemoryListResponses(params) {
Alternatively, you can request a single page at a time:

```ts
let page = await client.memories.list({ collection: 'REPLACE_ME' });
let page = await client.memories.list();
for (const memoryListResponse of page.items) {
console.log(memoryListResponse);
}
Expand Down
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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 <hello@hyperspell.com>",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -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",
Comment on lines 53 to 69

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: The packageManager field specifies yarn@1.22.22, which respects resolutions but ignores overrides and pnpm.overrides. Additionally, Yarn 1 resolutions officially require an exact version rather than a range (e.g., 9.0.5 instead of ^9.0.5) to ensure the dependency is pinned correctly.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In package.json lines 53-69, the diff adds `overrides`, `pnpm.overrides`, and `resolutions` all set to `minimatch: ^9.0.5`. The project uses Yarn 1 (yarn@1.22.22) which only respects `resolutions`, and Yarn 1 does not support range specifiers like `^9.0.5` in resolutions — it needs an exact version. Remove the `overrides` and `pnpm.overrides` blocks (they are irrelevant for Yarn 1), and change the `resolutions` value from `^9.0.5` to an exact version like `9.0.5`.

Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/manifest.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
23 changes: 13 additions & 10 deletions packages/mcp-server/package.json
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",
Expand All @@ -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",
Expand All @@ -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",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: ESLint v9 uses a flat config system (eslint.config.js) by default, replacing the legacy .eslintrc.* format — if the project uses the old config format, linting will silently fail or throw errors after this upgrade.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/package.json, ESLint is being upgraded from ^8.49.0 to ^9.39.1. ESLint v9 uses a flat config system (eslint.config.js) by default. Check if the project has an eslint.config.js or if it still uses .eslintrc.* format. If using the legacy format, either migrate to flat config or add ESLINT_USE_FLAT_CONFIG=false to scripts, or pin back to eslint v8 until migration is done.

"@typescript-eslint/parser": "8.31.1",
Comment on lines 65 to 67

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: ESLint 9 introduced a breaking flat config format (eslint.config.js) that is incompatible with the legacy .eslintrc.* format used in ESLint 8. If the project still uses a legacy config file, linting will silently skip or error, and eslint-plugin-unused-imports v4 also requires ESLint 9's flat config API, so mixing old config with new plugins may break the lint pipeline entirely.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/package.json, eslint was bumped from ^8.49.0 to ^9.39.1. ESLint 9 uses a new flat config system (eslint.config.js) and drops support for .eslintrc.* files by default. Check whether the project has a flat config file (eslint.config.js/mjs/cjs). If not, either migrate the existing .eslintrc config to flat config format, or pin eslint back to ^8.x. Also verify that eslint-plugin-unused-imports v4 and eslint-plugin-prettier v5.4.1 are compatible with whichever ESLint major version is chosen.

Comment on lines 65 to 67

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: ESLint v9 introduces a flat config system (eslint.config.js) that is incompatible with the legacy .eslintrc.* format used by v8 — if the project still uses .eslintrc style config, linting will break entirely after this upgrade. Additionally, eslint-plugin-unused-imports v4 drops support for ESLint v8 and requires flat config, so both plugins may silently fail or error without config migration.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/package.json, lines 65-67, ESLint is being upgraded from ^8.49.0 to ^9.39.1. ESLint v9 uses a flat config system (eslint.config.js) that is incompatible with .eslintrc.* style configs. Check if the project has an .eslintrc.js/.eslintrc.json/.eslintrc.yaml config file and migrate it to the new flat config format (eslint.config.js). Also verify that eslint-plugin-unused-imports v4 and eslint-plugin-prettier v5.4.1 are compatible with the flat config setup. If migration is not planned, revert to eslint ^8.x.

Comment on lines 65 to 67

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: ESLint v9 uses a flat config format (eslint.config.js) by default, which is a breaking change from v8's .eslintrc.* format — if the project still uses the old config format, linting will fail or produce unexpected results.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/package.json, ESLint is being upgraded from ^8.49.0 to ^9.39.1. ESLint v9 uses flat config (eslint.config.js) by default and drops support for .eslintrc.* files. Check if the project has an eslint.config.js or if it's still using .eslintrc.* format. If using the old format, either migrate to flat config or add ESLINT_USE_FLAT_CONFIG=false environment variable as a temporary workaround, and also verify that eslint-plugin-unused-imports@^4.1.4 and eslint-plugin-prettier@^5.4.1 are compatible with ESLint v9 flat config.

"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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: ESLint v9 uses a flat config system (eslint.config.js) instead of .eslintrc.* — if the project still uses the legacy config format, linting will break entirely after this upgrade. Additionally, eslint-plugin-unused-imports v4 requires ESLint v9's flat config API, so the plugin will fail if the config hasn't been migrated.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/package.json, ESLint is being upgraded from ^8 to ^9. ESLint v9 introduces a breaking change: it uses flat config (eslint.config.js) by default instead of .eslintrc.*. Before merging, verify that the ESLint config file in this package has been migrated to flat config format, and that @typescript-eslint/eslint-plugin 8.31.1 and eslint-plugin-unused-imports ^4.1.4 are compatible with ESLint v9 flat config. If not migrated, add ESLINT_USE_FLAT_CONFIG=false env var as a temporary workaround or migrate the config.

"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",
Expand Down
4 changes: 3 additions & 1 deletion packages/mcp-server/src/code-tool-paths.cts
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: The exported symbol changed from a constant workerPath to a function getWorkerPath() — any callers that reference workerPath directly will now get undefined or a compile error at runtime.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool-paths.cts, the export changed from `export const workerPath = ...` to `export function getWorkerPath(): string { ... }`. Search the entire codebase for all usages of `workerPath` (the old exported constant name) and update them to call `getWorkerPath()` instead. Failure to do so will cause runtime errors or TypeScript compile errors wherever the old name is referenced.

81 changes: 59 additions & 22 deletions packages/mcp-server/src/code-tool.ts
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,
Expand All @@ -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';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: getLogger() throws if configureLogger() was never called, and it is invoked at codeTool() call time (not lazily). If any consumer constructs the codeTool before configuring the logger, the entire server will crash with "Logger has not been configured".

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts, the call to `getLogger()` (added via the new import of `./logger`) is placed at the top level of the `codeTool()` factory function body. `getLogger()` throws synchronously if `configureLogger()` has not been called first. Move the `getLogger()` call inside the `handler` async function (lazily), or ensure all callers of `codeTool()` are guaranteed to run after `configureLogger()` has been invoked. File: packages/mcp-server/src/code-tool.ts, around the line where `const logger = getLogger();` is placed inside `codeTool`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: getLogger() throws if configureLogger() was never called, and it is invoked at codeTool() call time (not lazily). If any consumer constructs the codeTool before configuring the logger, the entire server will crash with "Logger has not been configured".

Affected Locations:

  • packages/mcp-server/src/code-tool.ts:15-15
  • packages/mcp-server/src/code-tool.ts:82-82
  • packages/mcp-server/src/docs-search-tool.ts:63-63
  • packages/mcp-server/src/instructions.ts:49-51
  • packages/mcp-server/src/stdio.ts:13-13
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts, the call to `getLogger()` (added via the new import of `./logger`) is placed at the top level of the `codeTool()` factory function body. `getLogger()` throws synchronously if `configureLogger()` has not been called first. Move the `getLogger()` call inside the `handler` async function (lazily), or ensure all callers of `codeTool()` are guaranteed to run after `configureLogger()` has been invoked. File: packages/mcp-server/src/code-tool.ts, around the line where `const logger = getLogger();` is placed inside `codeTool`.

import { SdkMethod } from './methods';
import { McpCodeExecutionMode } from './options';
import { ClientOptions } from 'hyperspell';
Expand Down Expand Up @@ -83,6 +79,8 @@ export function codeTool({
},
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: getLogger() throws if configureLogger() hasn't been called yet — calling it at codeTool() invocation time (rather than inside handler) means any call to codeTool() before logger configuration will crash immediately instead of at handler execution time, but more critically, if the logger is not yet configured when this module initializes the tool factory, the error surfaces at an unexpected callsite rather than during actual request handling.

Affected Locations:

  • packages/mcp-server/src/code-tool.ts:86-86
  • packages/mcp-server/src/docs-search-tool.ts:63-64
  • packages/mcp-server/src/instructions.ts:54-56
  • packages/mcp-server/src/stdio.ts:13-13
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts at line 86, `const logger = getLogger()` is called at `codeTool()` factory invocation time. Since `getLogger()` throws if `configureLogger()` hasn't been called, this will throw at tool creation time rather than at handler execution time. Move `const logger = getLogger();` inside the `handler` async function body (around line 92) so the logger is resolved lazily when a request is actually processed.

const logger = getLogger();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: getLogger() throws if configureLogger() hasn't been called yet — calling it eagerly at codeTool() construction time means any code that creates a codeTool before configuring the logger will crash, even if the logger is configured before the handler is ever invoked.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts, line 87, `const logger = getLogger()` is called eagerly inside `codeTool()` at construction time. `getLogger()` throws if the logger hasn't been configured yet. Move this line inside the `handler` async function (around line 91) so it is only evaluated when the handler is actually invoked, by which time `configureLogger()` is guaranteed to have been called.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: getLogger() throws if configureLogger() hasn't been called yet — calling it eagerly at codeTool() construction time means any code that creates a codeTool before configuring the logger will crash, even if the logger is configured before the handler is ever invoked.

Affected Locations:

  • packages/mcp-server/src/code-tool.ts:87-87
  • packages/mcp-server/src/docs-search-tool.ts:63-63
  • packages/mcp-server/src/instructions.ts:53-56
  • packages/mcp-server/src/stdio.ts:13-13
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts, line 87, `const logger = getLogger()` is called eagerly inside `codeTool()` at construction time. `getLogger()` throws if the logger hasn't been configured yet. Move this line inside the `handler` async function (around line 91) so it is only evaluated when the handler is actually invoked, by which time `configureLogger()` is guaranteed to have been called.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: getLogger() throws if configureLogger() hasn't been called yet — calling it at codeTool() invocation time (rather than inside handler) means any call to codeTool() before the logger is configured will throw, making the tool completely unusable even before any code execution is attempted.

Affected Locations:

  • packages/mcp-server/src/code-tool.ts:87-87
  • packages/mcp-server/src/docs-search-tool.ts:63-63
  • packages/mcp-server/src/instructions.ts:54-54
  • packages/mcp-server/src/stdio.ts:13-13
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In `packages/mcp-server/src/code-tool.ts`, line 87, `const logger = getLogger()` is called at `codeTool()` invocation time. According to `packages/mcp-server/src/logger.ts`, `getLogger()` throws an error if `configureLogger()` hasn't been called yet. Move the `const logger = getLogger()` call to inside the `handler` async function body so that the logger is only retrieved when a request is actually being handled, by which time `configureLogger()` should have been called. This prevents `codeTool()` from throwing during setup.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: getLogger() throws if configureLogger() hasn't been called yet, and calling it at codeTool() invocation time (rather than inside the handler) means any call to codeTool() before the logger is configured will crash immediately instead of deferring the error to actual request handling.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts, line 82, `const logger = getLogger()` is called at the top of the `codeTool()` factory function. Since `getLogger()` throws if the logger hasn't been configured yet, this means calling `codeTool()` before `configureLogger()` is invoked will throw immediately. Move the `const logger = getLogger()` call inside the `handler` async function body (around line 86) so it is only evaluated at request time, by which point the logger should be configured.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: getLogger() throws if configureLogger() hasn't been called yet, and calling it at codeTool() invocation time (rather than inside the handler) means any call to codeTool() before the logger is configured will crash immediately instead of deferring the error to actual request handling.

Affected Locations:

  • packages/mcp-server/src/code-tool.ts:82-82
  • packages/mcp-server/src/docs-search-tool.ts:63-64
  • packages/mcp-server/src/http.ts:163-163
  • packages/mcp-server/src/instructions.ts:48-51
  • packages/mcp-server/src/stdio.ts:13-13
🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts, line 82, `const logger = getLogger()` is called at the top of the `codeTool()` factory function. Since `getLogger()` throws if the logger hasn't been configured yet, this means calling `codeTool()` before `configureLogger()` is invoked will throw immediately. Move the `const logger = getLogger()` call inside the `handler` async function body (around line 86) so it is only evaluated at request time, by which point the logger should be configured.


const handler = async ({
reqContext,
args,
Expand All @@ -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 };
Expand All @@ -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 };

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: 🔒 reqContext.upstreamClientEnvs can override HYPERSPELL_API_KEY with a caller-supplied value, which then gets forwarded to the Stainless code-tool endpoint as the credential used to call the Hyperspell API. A malicious or misconfigured upstream client could substitute an arbitrary API key, causing all code execution to run under that key instead of the server-configured one — effectively an auth bypass / credential injection.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts at line 155, the spread `{ ...localClientEnvs, ...reqContext.upstreamClientEnvs }` allows a caller-supplied upstream env to override HYPERSPELL_API_KEY. Fix this by stripping HYPERSPELL_API_KEY (and potentially HYPERSPELL_BASE_URL) from upstreamClientEnvs before merging, so that server-configured credentials always take precedence over client-supplied ones.


// 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',
Expand All @@ -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

Choose a reason for hiding this comment

The 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 CODE_MODE_ENDPOINT_URL).

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts around line 171-175, the 404 check assumes a missing Stainless API key is the cause when no key is present. However, a 404 could also mean a wrong endpoint URL or a truly missing resource. Consider softening the message to indicate it 'may' be an auth issue or 'may' also be a misconfigured endpoint URL, so users are not misled into only looking at the API key as the root cause.

'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

Choose a reason for hiding this comment

The 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 hyperspell is not found in Stainless, wrong CODE_MODE_ENDPOINT_URL override). This will surface a misleading "provide a Stainless API key" message when the real cause is something else entirely, masking the actual error response body.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts, lines 167-172, the condition `res.status === 404 && !reqContext.stainlessApiKey` produces a misleading error message when a 404 is returned for reasons unrelated to authentication (e.g., wrong endpoint URL, unknown project). Consider including the actual response body in the error message, or only show the API key hint when the response body specifically indicates an auth/access issue. Example fix: read `res.text()` first, check if it indicates an auth problem, then decide which message to surface.

);
}
throw new Error(
`${res.status}: ${
res.statusText
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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 },

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: Spreading process.env into the Deno subprocess environment exposes all host secrets (tokens, credentials, cloud keys, etc.) to untrusted user-supplied code running inside the worker. An attacker can exfiltrate any env var via the allowed network connection to baseURLHostname.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/mcp-server/src/code-tool.ts at line 276, the change `env: { ...process.env, ...reqContext.upstreamClientEnvs }` spreads the entire host process environment (including secrets like AWS keys, tokens, etc.) into a Deno subprocess that runs user-supplied code. Fix by only forwarding an explicit allowlist of safe env vars (e.g. PATH, HOME, TMPDIR) plus the upstreamClientEnvs, instead of blindly spreading process.env.

},
});

Expand All @@ -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',
Expand Down
Loading
Loading