Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 0 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
configured_endpoints: 31
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell/hyperspell-e57add0181eb2a057f8416eaf4020dd5b3042431342a51e3d4dc39af4a41aced.yml
openapi_spec_hash: d0d66b814ebe56ac7c0135f9f3aab616
config_hash: 11e84d884a86d2db0411c35fae6e9121
2 changes: 1 addition & 1 deletion MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ This affects the following methods:

- `client.folders.deletePolicy()`
- `client.memories.update()`
- `client.memories.delete()`
- `client.memories.get()`
- `client.memories.delete()`

### URI encoded path parameters

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ It is generated with [Stainless](https://www.stainless.com/).

Use the Hyperspell MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.

[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40hyperspell%2Fhyperspell-mcp&config=eyJuYW1lIjoiQGh5cGVyc3BlbGwvaHlwZXJzcGVsbC1tY3AiLCJ0cmFuc3BvcnQiOiJodHRwIiwidXJsIjoiaHR0cHM6Ly9oeXBlcnNwZWxsLnN0bG1jcC5jb20iLCJoZWFkZXJzIjp7IngtaHlwZXJzcGVsbC1hcGkta2V5IjoiTXkgQVBJIEtleSIsIlgtQXMtVXNlciI6Ik15IFVzZXIgSUQifX0)
[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40hyperspell%2Fhyperspell-mcp%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fhyperspell.stlmcp.com%22%2C%22headers%22%3A%7B%22x-hyperspell-api-key%22%3A%22My%20API%20Key%22%2C%22X-As-User%22%3A%22My%20User%20ID%22%7D%7D)
[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40hyperspell%2Fhyperspell-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBoeXBlcnNwZWxsL2h5cGVyc3BlbGwtbWNwIl0sImVudiI6eyJIWVBFUlNQRUxMX0FQSV9LRVkiOiJNeSBBUEkgS2V5IiwiSFlQRVJTUEVMTF9VU0VSX0lEIjoiTXkgVXNlciBJRCJ9fQ)
[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40hyperspell%2Fhyperspell-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40hyperspell%2Fhyperspell-mcp%22%5D%2C%22env%22%3A%7B%22HYPERSPELL_API_KEY%22%3A%22My%20API%20Key%22%2C%22HYPERSPELL_USER_ID%22%3A%22My%20User%20ID%22%7D%7D)

> Note: You may need to set environment variables in your MCP client.

Expand Down
24 changes: 12 additions & 12 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ Types:

Methods:

- <code title="get /connections/list">client.connections.<a href="./src/resources/connections.ts">list</a>() -> ConnectionListResponse</code>
- <code title="delete /connections/{connection_id}/revoke">client.connections.<a href="./src/resources/connections.ts">revoke</a>(connectionID) -> ConnectionRevokeResponse</code>
- <code title="get /connections/list">client.connections.<a href="./src/resources/connections.ts">list</a>() -> ConnectionListResponse</code>

# Folders

Expand All @@ -71,9 +71,9 @@ Types:
Methods:

- <code title="get /connections/{connection_id}/folders">client.folders.<a href="./src/resources/folders.ts">list</a>(connectionID, { ...params }) -> FolderListResponse</code>
- <code title="delete /connections/{connection_id}/folder-policies/{policy_id}">client.folders.<a href="./src/resources/folders.ts">deletePolicy</a>(policyID, { ...params }) -> FolderDeletePolicyResponse</code>
- <code title="get /connections/{connection_id}/folder-policies">client.folders.<a href="./src/resources/folders.ts">listPolicies</a>(connectionID) -> FolderListPoliciesResponse</code>
- <code title="post /connections/{connection_id}/folder-policies">client.folders.<a href="./src/resources/folders.ts">setPolicies</a>(connectionID, { ...params }) -> FolderSetPoliciesResponse</code>
- <code title="delete /connections/{connection_id}/folder-policies/{policy_id}">client.folders.<a href="./src/resources/folders.ts">deletePolicy</a>(policyID, { ...params }) -> FolderDeletePolicyResponse</code>

# Integrations

Expand Down Expand Up @@ -130,15 +130,15 @@ Types:

Methods:

- <code title="post /memories/update/{source}/{resource_id}">client.memories.<a href="./src/resources/memories.ts">update</a>(resourceID, { ...params }) -> MemoryStatus</code>
- <code title="get /memories/list">client.memories.<a href="./src/resources/memories.ts">list</a>({ ...params }) -> MemoryListResponsesCursorPage</code>
- <code title="delete /memories/delete/{source}/{resource_id}">client.memories.<a href="./src/resources/memories.ts">delete</a>(resourceID, { ...params }) -> MemoryDeleteResponse</code>
- <code title="post /memories/add">client.memories.<a href="./src/resources/memories.ts">add</a>({ ...params }) -> MemoryStatus</code>
- <code title="post /memories/add/bulk">client.memories.<a href="./src/resources/memories.ts">addBulk</a>({ ...params }) -> MemoryAddBulkResponse</code>
- <code title="post /memories/upload">client.memories.<a href="./src/resources/memories.ts">upload</a>({ ...params }) -> MemoryStatus</code>
- <code title="post /memories/update/{source}/{resource_id}">client.memories.<a href="./src/resources/memories.ts">update</a>(resourceID, { ...params }) -> MemoryStatus</code>
- <code title="get /memories/list">client.memories.<a href="./src/resources/memories.ts">list</a>({ ...params }) -> MemoryListResponsesCursorPage</code>
- <code title="get /memories/status">client.memories.<a href="./src/resources/memories.ts">status</a>() -> MemoryStatusResponse</code>
- <code title="get /memories/get/{source}/{resource_id}">client.memories.<a href="./src/resources/memories.ts">get</a>(resourceID, { ...params }) -> MemoryGetResponse</code>
- <code title="post /memories/query">client.memories.<a href="./src/resources/memories.ts">search</a>({ ...params }) -> QueryResult</code>
- <code title="get /memories/status">client.memories.<a href="./src/resources/memories.ts">status</a>() -> MemoryStatusResponse</code>
- <code title="post /memories/upload">client.memories.<a href="./src/resources/memories.ts">upload</a>({ ...params }) -> MemoryStatus</code>
- <code title="delete /memories/delete/{source}/{resource_id}">client.memories.<a href="./src/resources/memories.ts">delete</a>(resourceID, { ...params }) -> MemoryDeleteResponse</code>

# Evaluate

Expand All @@ -150,10 +150,10 @@ Types:

Methods:

- <code title="get /evaluate/query/{query_id}">client.evaluate.<a href="./src/resources/evaluate.ts">getQuery</a>(queryID) -> QueryResult</code>
- <code title="get /evaluate/queries">client.evaluate.<a href="./src/resources/evaluate.ts">listQueries</a>({ ...params }) -> EvaluateListQueriesResponsesCursorPage</code>
- <code title="post /evaluate/highlight/{highlight_id}">client.evaluate.<a href="./src/resources/evaluate.ts">scoreHighlight</a>(highlightID, { ...params }) -> EvaluateScoreHighlightResponse</code>
- <code title="get /evaluate/query/{query_id}">client.evaluate.<a href="./src/resources/evaluate.ts">getQuery</a>(queryID) -> QueryResult</code>
- <code title="post /evaluate/query/{query_id}">client.evaluate.<a href="./src/resources/evaluate.ts">scoreQuery</a>(queryID, { ...params }) -> EvaluateScoreQueryResponse</code>
- <code title="post /evaluate/highlight/{highlight_id}">client.evaluate.<a href="./src/resources/evaluate.ts">scoreHighlight</a>(highlightID, { ...params }) -> EvaluateScoreHighlightResponse</code>

# Actions

Expand All @@ -164,8 +164,8 @@ Types:

Methods:

- <code title="post /actions/add_reaction">client.actions.<a href="./src/resources/actions.ts">addReaction</a>({ ...params }) -> ActionAddReactionResponse</code>
- <code title="post /actions/send_message">client.actions.<a href="./src/resources/actions.ts">sendMessage</a>({ ...params }) -> ActionSendMessageResponse</code>
- <code title="post /actions/add_reaction">client.actions.<a href="./src/resources/actions.ts">addReaction</a>({ ...params }) -> ActionAddReactionResponse</code>

# Sessions

Expand Down Expand Up @@ -193,6 +193,6 @@ Types:

Methods:

- <code title="delete /auth/delete">client.auth.<a href="./src/resources/auth.ts">deleteUser</a>() -> AuthDeleteUserResponse</code>
- <code title="get /auth/me">client.auth.<a href="./src/resources/auth.ts">me</a>() -> AuthMeResponse</code>
- <code title="post /auth/user_token">client.auth.<a href="./src/resources/auth.ts">userToken</a>({ ...params }) -> Token</code>
- <code title="get /auth/me">client.auth.<a href="./src/resources/auth.ts">me</a>() -> AuthMeResponse</code>
- <code title="delete /auth/delete">client.auth.<a href="./src/resources/auth.ts">deleteUser</a>() -> AuthDeleteUserResponse</code>
4 changes: 4 additions & 0 deletions bin/check-release-environment
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

errors=()

if [ -z "${RELEASE_PLEASE_TOKEN}" ]; then
errors+=("The RELEASE_PLEASE_TOKEN secret has not been set. Create a fine-grained GitHub PAT and add it as a repository secret.")
fi
Comment on lines +5 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

MAJOR RELIABILITY RELEASE_PLEASE_TOKEN check always fails — secret never injected into environment

GitHub Actions secrets are not exposed as env vars automatically; .github/workflows/release-doctor.yml runs this script with no env: block, so RELEASE_PLEASE_TOKEN is always empty and the check always errors, blocking every release-please PR.

Prompt to fix with AI

Copy this prompt into your AI coding assistant to fix this issue.

In `.github/workflows/release-doctor.yml`, the step 'Check release environment' (around line 17-19) runs `bash ./bin/check-release-environment` but does not expose the `RELEASE_PLEASE_TOKEN` secret as an environment variable. GitHub Actions secrets are never auto-injected; without an explicit `env:` block the variable is always empty, causing the new check in `bin/check-release-environment` lines 5-7 to always fail.

Fix: add an `env:` block to that step:
```yaml
      - name: Check release environment
        env:
          RELEASE_PLEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
        run: |
          bash ./bin/check-release-environment

</details>


lenErrors=${#errors[@]}

if [[ lenErrors -gt 0 ]]; then
Expand Down
4 changes: 2 additions & 2 deletions bin/migration-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
},
{
"base": "memories",
"name": "delete",
"name": "get",
"params": [
{
"type": "param",
Expand Down Expand Up @@ -108,7 +108,7 @@
},
{
"base": "memories",
"name": "get",
"name": "delete",
"params": [
{
"type": "param",
Expand Down
6 changes: 3 additions & 3 deletions packages/mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,22 @@ For clients with a configuration JSON, it might look something like this:
If you use Cursor, you can install the MCP server by using the button below. You will need to set your environment variables
in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > New MCP Server.

[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40hyperspell%2Fhyperspell-mcp&config=eyJuYW1lIjoiQGh5cGVyc3BlbGwvaHlwZXJzcGVsbC1tY3AiLCJ0cmFuc3BvcnQiOiJodHRwIiwidXJsIjoiaHR0cHM6Ly9oeXBlcnNwZWxsLnN0bG1jcC5jb20iLCJoZWFkZXJzIjp7IngtaHlwZXJzcGVsbC1hcGkta2V5IjoiTXkgQVBJIEtleSIsIlgtQXMtVXNlciI6Ik15IFVzZXIgSUQifX0)
[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40hyperspell%2Fhyperspell-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBoeXBlcnNwZWxsL2h5cGVyc3BlbGwtbWNwIl0sImVudiI6eyJIWVBFUlNQRUxMX0FQSV9LRVkiOiJNeSBBUEkgS2V5IiwiSFlQRVJTUEVMTF9VU0VSX0lEIjoiTXkgVXNlciBJRCJ9fQ)

### VS Code

If you use MCP, you can install the MCP server by clicking the link below. You will need to set your environment variables
in VS Code's `mcp.json`, which can be found via Command Palette > MCP: Open User Configuration.

[Open VS Code](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40hyperspell%2Fhyperspell-mcp%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fhyperspell.stlmcp.com%22%2C%22headers%22%3A%7B%22x-hyperspell-api-key%22%3A%22My%20API%20Key%22%2C%22X-As-User%22%3A%22My%20User%20ID%22%7D%7D)
[Open VS Code](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40hyperspell%2Fhyperspell-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40hyperspell%2Fhyperspell-mcp%22%5D%2C%22env%22%3A%7B%22HYPERSPELL_API_KEY%22%3A%22My%20API%20Key%22%2C%22HYPERSPELL_USER_ID%22%3A%22My%20User%20ID%22%7D%7D)

### Claude Code

If you use Claude Code, you can install the MCP server by running the command below in your terminal. You will need to set your
environment variables in Claude Code's `.claude.json`, which can be found in your home directory.

```
claude mcp add hyperspell_hyperspell_mcp_api --header "x-hyperspell-api-key: My API Key" --header "X-As-User: My User ID" --transport http https://hyperspell.stlmcp.com
claude mcp add hyperspell_hyperspell_mcp_api --env HYPERSPELL_API_KEY="My API Key" HYPERSPELL_USER_ID="My User ID" -- npx -y @hyperspell/hyperspell-mcp

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

MAJOR BUG Fix claude mcp add command: options must precede server name and each env var needs its own --env flag

--env HYPERSPELL_API_KEY=... HYPERSPELL_USER_ID=... passes the second var as a positional argument (parsed as the server name), not as an env var, causing the command to fail. Per Claude Code docs, all options must also come before the server name.

Suggested change
claude mcp add hyperspell_hyperspell_mcp_api --env HYPERSPELL_API_KEY="My API Key" HYPERSPELL_USER_ID="My User ID" -- npx -y @hyperspell/hyperspell-mcp
claude mcp add --env HYPERSPELL_API_KEY="My API Key" --env HYPERSPELL_USER_ID="My User ID" hyperspell_hyperspell_mcp_api -- npx -y @hyperspell/hyperspell-mcp
Prompt to fix with AI

Copy this prompt into your AI coding assistant to fix this issue.

In packages/mcp-server/README.md at line 59, fix the `claude mcp add` command. The current command `claude mcp add hyperspell_hyperspell_mcp_api --env HYPERSPELL_API_KEY="My API Key" HYPERSPELL_USER_ID="My User ID" -- npx -y @hyperspell/hyperspell-mcp` has two bugs: (1) options must precede the server name, and (2) each env var needs its own --env flag. Replace with: `claude mcp add --env HYPERSPELL_API_KEY="My API Key" --env HYPERSPELL_USER_ID="My User ID" hyperspell_hyperspell_mcp_api -- npx -y @hyperspell/hyperspell-mcp`

```

## Code Mode
Expand Down
87 changes: 4 additions & 83 deletions packages/mcp-server/src/code-tool.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import {
ContentBlock,
McpRequestContext,
McpTool,
Metadata,
ToolCallResult,
asErrorResult,
asTextContentResult,
} from './types';
import { ContentBlock, McpRequestContext, McpTool, Metadata, ToolCallResult, asErrorResult } from './types';
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { readEnv, requireValue } from './util';
import { WorkerInput, WorkerOutput } from './code-tool-types';
import { WorkerOutput } from './code-tool-types';
import { getLogger } from './logger';
import { SdkMethod } from './methods';
import { McpCodeExecutionMode } from './options';
Expand Down Expand Up @@ -108,13 +99,8 @@ export function codeTool({
let result: ToolCallResult;
const startTime = Date.now();

if (codeExecutionMode === 'local') {
logger.debug('Executing code in local Deno environment');
result = await localDenoHandler({ reqContext, args });
} else {
logger.debug('Executing code in remote Stainless environment');
result = await remoteStainlessHandler({ reqContext, args });
}
logger.debug('Executing code in local Deno environment');
result = await localDenoHandler({ reqContext, args });

logger.info(
{
Expand All @@ -131,71 +117,6 @@ export function codeTool({
return { metadata, tool, handler };
}

const remoteStainlessHandler = async ({
reqContext,
args,
}: {
reqContext: McpRequestContext;
args: any;
}): Promise<ToolCallResult> => {
const code = args.code as string;
const intent = args.intent as string | undefined;
const client = reqContext.client;

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 };

// 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',
'x-stainless-mcp-client-envs': JSON.stringify(mergedClientEnvs),
},
body: JSON.stringify({
project_name: 'hyperspell',
code,
intent,
client_opts: { userID: readEnv('HYPERSPELL_USER_ID') },
} satisfies WorkerInput),
});

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
} error when trying to contact Code Tool server. Details: ${await res.text()}`,
);
}

const { is_error, result, log_lines, err_lines } = (await res.json()) as WorkerOutput;
const hasLogs = log_lines.length > 0 || err_lines.length > 0;
const output = {
result,
...(log_lines.length > 0 && { log_lines }),
...(err_lines.length > 0 && { err_lines }),
};
if (is_error) {
return asErrorResult(typeof result === 'string' && !hasLogs ? result : JSON.stringify(output, null, 2));
}
return asTextContentResult(output);
};

const localDenoHandler = async ({
reqContext,
args,
Expand Down
63 changes: 2 additions & 61 deletions packages/mcp-server/src/docs-search-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { Metadata, McpRequestContext, asTextContentResult } from './types';
import { getLogger } from './logger';

import type { LocalDocsSearch } from './local-docs-search';

export const metadata: Metadata = {
Expand Down Expand Up @@ -41,9 +41,6 @@ export const tool: Tool = {
},
};

const docsSearchURL =
process.env['DOCS_SEARCH_URL'] || 'https://api.stainless.com/api/projects/hyperspell/docs/search';

let _localSearch: LocalDocsSearch | undefined;

export function setLocalSearch(search: LocalDocsSearch): void {
Expand All @@ -67,58 +64,6 @@ async function searchLocal(args: Record<string, unknown>): Promise<unknown> {
}).results;
}

async function searchRemote(args: Record<string, unknown>, reqContext: McpRequestContext): Promise<unknown> {
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 }),
...(reqContext.mcpSessionId && { 'x-stainless-mcp-session-id': reqContext.mcpSessionId }),
...(reqContext.mcpClientInfo && {
'x-stainless-mcp-client-info': JSON.stringify(reqContext.mcpClientInfo),
}),
},
});

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: ${errorText}`,
);
}

const resultBody = await result.json();
logger.info(
{
durationMs: Date.now() - startTime,
query: body.query,
},
'Got docs search result',
);
return resultBody;
}

export const handler = async ({
reqContext,
args,
Expand All @@ -128,11 +73,7 @@ export const handler = async ({
}) => {
const body = args ?? {};

if (_localSearch) {
return asTextContentResult(await searchLocal(body));
}

return asTextContentResult(await searchRemote(body, reqContext));
return asTextContentResult(await searchLocal(body));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

MAJOR RELIABILITY Unconditional searchLocal call throws when local search not initialized

searchLocal throws 'Local search not initialized' if _localSearch is undefined, but initMcpServer only calls setLocalSearch when mcpOptions.docsSearchMode === 'local' is explicitly set (server.ts:70). Programmatic callers who omit docsSearchMode will include the docs tool by default yet never initialize _localSearch, causing search_docs to throw an unhandled error at runtime.

Suggested change
return asTextContentResult(await searchLocal(body));
if (!_localSearch) {
return asTextContentResult('Docs search is not initialized. Pass docsSearchMode: \'local\' when calling initMcpServer.');
}
return asTextContentResult(await searchLocal(body));
Prompt to fix with AI

Copy this prompt into your AI coding assistant to fix this issue.

In `packages/mcp-server/src/docs-search-tool.ts`, line 76, the handler unconditionally calls `searchLocal(body)`. `searchLocal` throws `'Local search not initialized'` if `_localSearch` is undefined (line 51-52). But in `packages/mcp-server/src/server.ts` lines 70-74, `setLocalSearch` is only called when `params.mcpOptions?.docsSearchMode === 'local'`. Programmatic callers who don't set `docsSearchMode` will include the docs tool by default (server.ts:191) but never initialize `_localSearch`, making `search_docs` throw at runtime with no error handling in `executeHandler` (server.ts:200-209).

Fix: In `server.ts` `initMcpServer`, change the condition from `if (params.mcpOptions?.docsSearchMode === 'local')` to always initialize local search when docs tools are included — e.g., `if ((params.mcpOptions?.includeDocsTools ?? true) && params.mcpOptions?.docsSearchMode !== undefined ? params.mcpOptions.docsSearchMode === 'local' : true)`. Alternatively, add a null-guard in the handler at line 76 in `docs-search-tool.ts` to return a friendly error message instead of throwing.

};

export default { metadata, tool, handler };
Loading
Loading