Skip to content

ENG-2597: Promote stlc-generated node SDK + MCP server (Stainless cutover)#96

Merged
aburkard merged 1 commit into
mainfrom
stage2/nd-promote-preview
Jun 26, 2026
Merged

ENG-2597: Promote stlc-generated node SDK + MCP server (Stainless cutover)#96
aburkard merged 1 commit into
mainfrom
stage2/nd-promote-preview

Conversation

@aburkard

Copy link
Copy Markdown
Contributor

What

Promote the stlc-generated node SDK + MCP server from node-sdk-staging onto main, replacing the Stainless-hosted generation pipeline. node analogue of the proven go cutover.

This is the largest of the four (it carries the @hyperspell/hyperspell-mcp MCP server) and folds in the ENG-2260 MCP work.

SDK diff (audited clean — same OAS snapshot as go/python)

  • API: Conversation.title, connections.revoke docs, regenerated resources/tests. No whole-file deletions.
  • Version pinned to manifest 0.39.0 across all 5 version locations: package.json, src/version.ts, packages/mcp-server/package.json, packages/mcp-server/manifest.json, and the MCP server's advertised version in packages/mcp-server/src/server.ts (this last one is not a release-please extra-file but public had 0.39.0 — staging shipped a 0.0.1 placeholder, stamped back to avoid regressing the version the MCP server reports at runtime).
  • Embedded SDK-doc version refs in local-docs-search.ts left as-is — pre-existing gen artifact, byte-identical to current main.

⚠️ MCP server behavioral change (review carefully)

@hyperspell/hyperspell-mcp migrates off Stainless's hosted infra:

  • code execution default: 'stainless-sandbox''local'. The hosted sandbox goes away when Stainless is retired; local execution is the replacement. Callers can still pass codeExecutionMode explicitly.
  • docs search: now an in-memory index from embedded SDK method data — no Stainless API dependency.

De-Stainless'd

README MCP badges → npx @hyperspell/hyperspell-mcp; .stats.yml dropped the Stainless spec URL + hashes. Residual stainless strings remain only in embedded docs, de-Stainless instruction text, and dev/CI tooling — gen-controlled, same as go/python.

⚠️ HOLD — do not merge yet

Publishes to npm, so order matters (same as python):

  1. Validate go (release: 0.1.0-alpha.2 #6) + python first.
  2. Close the stale Stainless release PR on node-sdk first — so there is never a window where both it and our release PR could each publish to npm.
  3. Merge this promote PR.
  4. Add .github/workflows/release-please.yml (separate PR) using PRODUCTION_REPO_TOKEN.
  5. Our release-please opens a fresh release: x.y.z PR → merging that is the single, gated npm publish.

🤖 Generated with Claude Code

…over)

Sync the stlc-generated SDK from node-sdk-staging onto main, replacing the
Stainless-hosted generation pipeline.

SDK API (same OAS snapshot as go/python): Conversation.title, connections.revoke
docs, regenerated resources/tests.

MCP server (@hyperspell/hyperspell-mcp) — folds in the ENG-2260 work:
- code-execution default switched from Stainless's hosted sandbox
  ('stainless-sandbox') to 'local' (the hosted sandbox dies with Stainless)
- docs search now uses an in-memory index built from embedded SDK method data
  (no Stainless API dependency)
- regenerated tools (code-tool, methods, options, instructions)

De-Stainless'ing: README MCP badges -> npx; .stats.yml dropped Stainless metadata.

Mechanics:
- version pinned to manifest 0.39.0 across all 5 version locations
  (package.json, src/version.ts, packages/mcp-server/{package.json,manifest.json},
  and the MCP server's advertised version in server.ts)
- release-please files preserved; staging repo refs rewritten to node-sdk
- embedded SDK-doc version refs in local-docs-search.ts left as-is (pre-existing
  gen artifact, byte-identical to current main)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jun 26, 2026

Copy link
Copy Markdown

ENG-2597

@firetiger-agent

firetiger-agent Bot commented Jun 26, 2026

Copy link
Copy Markdown

Firetiger has created a monitoring plan for this PR.

View monitor

Comment on lines +5 to +7
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

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>


```
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`

}

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.

@entelligence-ai-pr-reviews

Copy link
Copy Markdown

Confidence Score: 2/5 - Changes Needed

Not safe to merge — this PR promoting the stlc-generated Node SDK and MCP server has three MAJOR reliability and correctness issues that would cause real user-facing failures. The bin/check-release-environment script's RELEASE_PLEASE_TOKEN check will always fail because GitHub Actions secrets aren't automatically injected as env vars, breaking the release pipeline entirely. The MCP server README's claude mcp add command is malformed — passing multiple env vars to a single --env flag means HYPERSPELL_USER_ID would be interpreted as a positional argument rather than an env var, causing broken installations for every user who follows the docs. The PR does represent meaningful work in promoting a cleanly generated SDK, but these issues need resolution before merge.

Key Findings:

  • In bin/check-release-environment, the check for RELEASE_PLEASE_TOKEN will unconditionally fail because GitHub Actions secrets require explicit env: mapping in the workflow YAML to be available as environment variables — the release gate is effectively broken.
  • In packages/mcp-server/src/docs-search-tool.ts, the searchLocal call is made unconditionally without checking whether _localSearch is initialized, meaning any request that reaches this branch without local search setup will throw an unhandled 'Local search not initialized' error rather than gracefully falling back or returning a useful error message.
  • The claude mcp add command in packages/mcp-server/README.md uses --env HYPERSPELL_API_KEY=... HYPERSPELL_USER_ID=... which passes only the first value to --env and silently treats the second as a positional argument, producing a broken MCP server configuration for users following the quickstart instructions.
Files requiring special attention
  • bin/check-release-environment
  • packages/mcp-server/src/docs-search-tool.ts
  • packages/mcp-server/README.md

@aburkard aburkard merged commit 8492977 into main Jun 26, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant