Skip to content

feat: Add v2 SDK foundation (modern registration shell, prepare())#1136

Closed
jirispilka wants to merge 1 commit into
fix/add-actor-cutoff-review-followupsfrom
claude/mcp-migration-prs-piypd3
Closed

feat: Add v2 SDK foundation (modern registration shell, prepare())#1136
jirispilka wants to merge 1 commit into
fix/add-actor-cutoff-review-followupsfrom
claude/mcp-migration-prs-piypd3

Conversation

@jirispilka

Copy link
Copy Markdown
Collaborator

Why

Closes #1130. Public PR 1 of the 2026-07-28 stateless migration (plan: apify/apify-mcp-server-internal#663, parent #1128): the v2 SDK foundation the hosted server's modern HTTP entry point (internal PR 3) will consume. Stacked on #1135 (→ #1133#1127) because it touches src/mcp/server.ts, which the cutover chain also modifies — retarget/rebase onto master after the chain merges.

What changed

  • Deps: @modelcontextprotocol/core + @modelcontextprotocol/server at exact-pinned 2.0.0-beta.5, alongside the untouched v1 SDK. Betas can break between releases, hence exact pins — and hence the minimumReleaseAgeExclude entries (the 3-day gate blocks a 41-hour-old beta; with exact pins every bump is a reviewed diff anyway). zod converges on the already-present 4.4.3 (satisfies the v2 ^4.2.0 range), as the plan's spike predicted. @modelcontextprotocol/node is deliberately absent — it belongs to internal PR 1 (apify/apify-mcp-server-internal#680).
  • src/mcp/modern_server.ts (new): createModernServer(apifyMcpServer) — a v2 low-level Server registering tools/list, tools/call, resources/*, prompts/*, calling today's unmodified ToolEntry.call() / resource-service / prompts logic. The tools/call dispatcher is a port of the v1 sync path (token gate → tool resolution → payment context → AJV validation → standby/402 preflight → dispatchToolCall → error-kind switch → telemetry finally) with the plan's five per-request adaptations: optional session ID; client identity/capabilities from the _meta envelope (synthesized into an initialize-shaped object so the existing client-keyed helpers are reused unchanged); server mode and report-problem gating re-resolved per request; Apify token from ctx.http?.authInfo?.token, never _meta.apifyToken. No tasks/* registration (the v2 SDK rejects them -32601 itself — pinned by test), no logging side-channel (SEP-2577). The v1 outer-catch's toolTelemetry wire leak (fix: Uncaught tool errors leak toolTelemetry to the wire #1052) is intentionally not replicated on this new surface.
  • ActorsMcpServer: new public prepare() (pre-connect widget resolution; connect() now calls it — no behavior change for existing callers), plus per-request helpers isReportProblemServableForClient() / resolveServerModeForClient() / getAvailableWidgets(); buildPreflightFailureOutcome exported. Legacy registration and behavior untouched — legacy clients (including Tasks) are served exactly as before.
  • prepareTelemetryData accepts an optional per-request initializeRequestData, defaulting to the initialize-scoped value on legacy connections.

Plan corrections discovered against SDK beta.5 (worth folding back into the res/ docs): ctx.mcpReq.envelope keeps the full reserved key names (read via the exported *_META_KEY constants, not .clientInfo shorthand); the published typings erase the envelope shape (RequestMetaEnvelope = {}), so the module carries a local typed view; the throwable on this path is ProtocolError, not v1's McpError; a v2 Server binds one transport, so createModernServer is called per request from a createMcpHandler factory.

Notes for reviewers (human-written)

Proof it works

type-check, lint, format, check:agents, test:unit all green locally — 1154 passed / 1 skipped, including 11 new tests in tests/unit/mcp.modern_server.test.ts (registration surface incl. the no-tasks/* assertion, report-problem gating by envelope clientInfo, authInfo-over-instance-token precedence and fallback, per-request apps/default mode resolution, unknown-tool/missing-token rejections, prepare() running before transport connect). All 1143 pre-existing unit tests pass unchanged.

An end-to-end probe of the modern path is not possible with mcpc (it can't negotiate 2026-07-28 — confirmed in the plan's spike); live-path verification arrives with the integration-suite '2026-07-28' transport dimension, sequenced in the plan after internal PR 3.


Generated by Claude Code

Public PR 1 of the 2026-07-28 stateless migration (#1130):

- Add @modelcontextprotocol/core + /server at exact-pinned 2.0.0-beta.5,
  alongside the untouched v1 SDK (excluded from minimumReleaseAge: exact
  pins make every bump a reviewed change).
- New src/mcp/modern_server.ts: createModernServer() registers tools/list,
  tools/call, resources/*, prompts/* on a v2 Server, calling today's
  unmodified ToolEntry.call() logic. Per-request: session ID optional,
  client identity/capabilities from the _meta envelope, server mode and
  report-problem gating re-resolved per request, Apify token from
  ctx.http.authInfo (never _meta.apifyToken). No tasks/* registration
  (v2 SDK rejects them -32601), no logging side-channel (SEP-2577).
- ActorsMcpServer: new prepare() (pre-connect widget resolution;
  connect() now calls it), per-request helpers
  isReportProblemServableForClient()/resolveServerModeForClient()/
  getAvailableWidgets(), buildPreflightFailureOutcome exported.
- prepareTelemetryData accepts per-request initializeRequestData,
  defaulting to the initialize-scoped value on legacy connections.

Existing ActorsMcpServer registration and behavior unchanged; legacy
clients (including Tasks) are served exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ABxfYds29KVxn2C7nA11r
An error occurred while trying to automatically change base from fix/add-actor-cutoff-review-followups to refactor/isrestore-rename-cleanup July 23, 2026 07:59
@jirispilka
jirispilka marked this pull request as draft July 23, 2026 07:59
@jirispilka jirispilka closed this Jul 23, 2026
jirispilka added a commit that referenced this pull request Jul 23, 2026
## Why

Prerequisite refactor for the 2026-07-28 (stateless) MCP migration. The
modern surface needs the same `tools/call` orchestration the v1 handler
runs today; rather than clone that spine into a second dispatcher, this
extracts it into one shared engine both eras will call. Landed first,
behavior-preserving, per the repo's "refactoring is a separate PR — land
the refactor first" rule.

This is **PR 1a** of the refactor-first split of the original "PR 1"
(#1130): the shared engine lands here; **#1140** (PR 1b) then adds the
2026-07-28 modern surface as a thin shell over it. The split was driven
by an architecture review of the original single-PR attempt (#1136, now
superseded), which cloned ~280 lines of orchestration per era and hid a
latent `sendLoggingMessage`-on-unbound-v1-`Server` bug. The migration
plan is updated to match in apify/apify-mcp-server-internal#684.

Closes #1139. Part of #1130, #1128.

## What changed

The v1 `CallToolRequest` handler's orchestration spine — token gate →
tool resolution → payment context → AJV validation → standby/402
preflight → dispatch → error classification → report-problem nudge —
moved into a new shared `src/mcp/tool_call_engine.ts` that returns a
neutral, era-agnostic outcome and imports no SDK error type. The v1
handler is now a thin shell that maps that outcome back to today's exact
wire results, JSON-RPC errors, telemetry, and `sendLoggingMessage`
side-channel. `dispatchToolCall` gains an optional `emitLog` seam
(default preserves the current call) so the shared leaf no longer
hard-binds the v1 `Server`. No behavior change for v1 clients.

Follow-up notes:
- Contract-test count reads 36, not 29: the 29 pre-existing tests are
unedited; 7 tests were appended, 5 of them characterization tests that
fence previously-untested reject / side-channel / abort paths the
extraction rewrites.
- The engine ships a third function/return variant
(`classifyToolCallError` / `PreparedCallError`) beyond a literal "two
functions" split — it preserves actor-context telemetry on
post-resolution prep-spine throws, which a two-variant engine would have
dropped.
- The 2026-07-28 modern-surface shell that consumes this engine is #1140
(PR 1b) — a separate follow-up.
- The missing-token reject path and end-to-end actor dispatch aren't
reachable without a platform token; a human should run `pnpm run
test:integration` before merge as the belt-and-suspenders check.

## Notes for reviewers (human-written)
<!-- your own words -->

## Proof it works

All standard gates green: `type-check`, `lint` (0/0), `test:unit` **1157
passed / 1 skipped**, `check:agents`. The
`mcp.server.tool_call_contracts.test.ts` suite passes at **36** with the
29 pre-existing assertions unedited (verified against `master` in an
isolated worktree). Live `mcpc` probe of the refactored v1 stdio server:
all 11 default tools register, and unknown-tool / invalid-args (5 tools)
/ legacy-name resolution return the correct `-32602 InvalidParams` — the
exact reject paths the extraction rewrote. End-to-end actor dispatch
needs a platform token and is deferred to a human integration run.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Xax7bhBYLfE6agNXqRgyv6

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

3 participants