Skip to content

spike: MCP 2026-07-28 stateless server - #1143

Closed
jirispilka wants to merge 5 commits into
masterfrom
claude/migration-continuation-39ybso
Closed

spike: MCP 2026-07-28 stateless server#1143
jirispilka wants to merge 5 commits into
masterfrom
claude/migration-continuation-39ybso

Conversation

@jirispilka

@jirispilka jirispilka commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Status

Spike only. Do not merge.

This branch proved that the v2 SDK can register the 2026-07-28 surface, route it through src/dev_server.ts, and reuse the shared tool-call engine. It also showed that adding stateless-only methods to the legacy ActorsMcpServer and duplicating prompt/error adaptation produces the wrong dependency direction.

The replacement work is tracked by:

Keep this PR open as implementation reference until the replacement for #1140 exists, then close it as superseded.

Part of #1128.

Serve MCP revision 2026-07-28 (stateless) via a new createServer2 thin
shell over the shared tool-call engine. Per-request client identity,
capabilities, protocol version, mode, and report-problem gating come from
the _meta envelope; the Apify token from authInfo. Outcomes map to the v2
SDK via ProtocolError + projectCallToolResult. No tasks/* handler and no
logging side-channel. Adds the v2 SDK deps at exact 2.0.0-beta.5 and a
public prepare() hook; threads per-request initializeRequestData so origin
attribution (incl. APIFY_AI) works on the stateless path.

Removes the ACTOR_MCP connect-failure logging notification from the shared
dispatch path (server-side log and the soft-fail isError result retained) —
a breaking v1 behavior change; closes the reachable sendLoggingMessage bug
on the never-connected modern instance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nyfevcePd9aec2prAC6uh
@github-actions github-actions Bot added t-ai Issues owned by the AI team. tested Temporary label used only programatically for some analytics. labels Jul 23, 2026
jirispilka and others added 4 commits July 23, 2026 17:50
Compute the modern surface's instructions report-problem flag from
isReportProblemServableForClient (telemetry + client-known + not-blocklisted)
instead of tool presence, so a blocklisted client is never told to call a
tool that tools/list withholds per request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nyfevcePd9aec2prAC6uh
…ybso' into claude/migration-continuation-39ybso
@jirispilka jirispilka changed the title feat: 2026-07-28 stateless modern surface (server2) spike: MCP 2026-07-28 stateless server Jul 23, 2026
jirispilka added a commit that referenced this pull request Jul 24, 2026
## Why

Closes #1146. Part of #1128.

The prompt and resource services were welded to the v1 MCP SDK:
`resource_service.ts` and `api_resources.ts` threw `McpError`, and
prompt list/get lived private inside `ActorsMcpServer` and threw
`McpError` too. The 2026-07-28 stateless-server spike (#1143) therefore
had to duplicate the prompt handlers verbatim and hand-translate v1
errors into v2 `ProtocolError` — the wrong dependency direction. This is
the second protocol-neutral prerequisite after #1145 (client context;
this PR is stacked on `refactor/client-context`).

## What changed

The services stop knowing about any protocol error class. They throw
plain domain errors (`InvalidParamsError` / `InternalError`, new
`src/mcp/errors.ts`, zero SDK imports) and return plain MCP-shaped data.
A single seam — `toMcpError` in `server.ts` — projects each domain error
1:1 back to `McpError` in the `resources/read` and `prompts/get` handler
bodies, copying `message` and `data` unchanged, so the wire output
(code, message, presence of `data`) is byte-identical. Prompt list/get
moved into `createPromptService(prompts)`, mirroring the existing
`createResourceService` factory. `api_resources.ts`'s existing
3xx/4xx-except-429 vs 429/5xx/network-drop split now selects the
domain-error class instead of an `ErrorCode`.

No public/`./internals` surface change, no v2 SDK dependency. A future
v2 adapter reuses the same services with its own `ProtocolError`
projection.

**Base:** stacked on `refactor/client-context` (#1145 work, not yet
merged). Retarget to `master` once that lands.

**Follow-up:** #1149 (live mcpc smoke test for the `prompts/get`
success/invalid-args paths — test-infra, empty prompt registry blocks it
today; unit-covered).

## Notes for reviewers (human-written)

<!-- your words -->

## Proof it works

- Unit: added `tests/unit/prompts.service.test.ts` (list / render /
unknown-name / invalid-args, isolated from `ActorsMcpServer`); added an
`InternalError`-class case at the resource-service layer and one through
the real `resources/read` handler asserting `-32603` is not downgraded
to `-32602`; existing resource assertions swapped 1:1 to the domain
classes with no scenario loss. `pnpm run test:unit` — 1179 passed / 1
skipped.
- Live mcpc probe (stdio + HTTP): `resources/read` of
`file://missing.md`, `https://example.com/x`, and a real 404 dataset all
return JSON-RPC `-32602` with `data:{uri}`; a successful dataset read
still returns normal content; `resources/list` /
`resources/templates/list` / `prompts/list` unchanged; `prompts/get`
unknown-name returns `-32602` naming the available prompts;
`tools/list`, `ping`, `search-actors`, `call-actor` all work
post-refactor.

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

https://claude.ai/code/session_01SiMgojbYtbycioTzKyeV4R

---
_Generated by [Claude
Code](https://claude.ai/code/session_01SiMgojbYtbycioTzKyeV4R)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
jirispilka pushed a commit that referenced this pull request Jul 27, 2026
Extract all v1 MCP SDK wiring out of ActorsMcpServer into a new
package-private LegacyMcpServer (src/mcp/legacy_server.ts). ActorsMcpServer
becomes the shared-Apify facade that constructs and delegates to the adapter;
shared tool execution now takes plain values instead of the server object or
the v1 RequestHandlerExtra/McpError. This fixes the dependency direction PR
#1143 exposed, so a later stateless adapter can reuse one Apify core.

Behavior is unchanged: tool names, results, error codes/messages/data,
notifications, Tasks, telemetry, and timing are byte-for-byte identical,
verified by a live mcpc diff against the pre-change server plus the full unit
suite (1177 passed + 1 skipped). close() teardown is the deliberate
reverse-of-connect order (server close, then tool-map clear).

BREAKING: the raw v1 .server and .taskStore are no longer public on
ActorsMcpServer; they move to the private adapter. Runtime-safe for the hosted
server, which reads neither, but external TypeScript consumers touching them
break at compile time.

Closes #1147
Part of #1128

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zHfSwpr57aUaxYjxAgsof
jirispilka added a commit that referenced this pull request Jul 28, 2026
## Why

Closes #1147. Part of #1128.

`ActorsMcpServer` combined two concerns: shared Apify behavior (tools,
`actorStore`, server-mode, prompts/resources, widgets, payments,
telemetry) and sessionful v1 SDK wiring (the SDK `Server`, request
handlers, initialize, Tasks, logging, notifications, errors, transport
lifecycle). PR #1143 showed that adding a second (stateless) serving
unit forced the dependency direction backwards — shared tool execution
reached up into the class (`apifyMcpServer.server`, `.tools`, `extra`,
`McpError`). This extraction fixes the direction so #1140 can expose
both protocol eras through `dev_server.ts` while reusing one Apify core.

**Breaking:** the raw v1 `.server` and `.taskStore` are no longer public
on `ActorsMcpServer` — they move to the private adapter. Runtime-safe
for `apify-mcp-server-internal` (it reads neither; verified against its
`new ActorsMcpServer(...)` call sites, which use only
`.options.initializeRequestData`, `.loadToolsByName()`,
`.loadToolsFromUrl()`, `.listAllToolNames()`, `.connect()`). External
TypeScript consumers touching those two members break at compile time —
hence `refactor!`.

## What changed

All v1 SDK wiring moved verbatim into a new package-private
`LegacyMcpServer` (`src/mcp/legacy_server.ts`). `ActorsMcpServer` keeps
the shared Apify behavior, constructs exactly one adapter, and delegates
`connect()`/`close()` to it via a narrow `LegacyMcpServerHost` interface
— the adapter never imports the concrete facade, and shared modules
never import the adapter. Shared tool execution (`tool_call_engine`,
`tool_dispatch`, `tool_call_telemetry`, `task_execution`) now takes
plain values; `InternalToolArgs` drops
`extra`/`apifyMcpServer`/`mcpServer`. No v2 SDK dependency and no
stateless routing were added (that is #1140).

Behavior is preserved byte-for-byte: tool names, results, error
codes/messages/data, notifications, Tasks, telemetry, and timing are
unchanged. `close()` teardown is the deliberate reverse-of-connect order
(close the transport, then clear the tool map).

## Notes for reviewers (human-written)

The huge diff is because of server.ts -> legacy_server.ts
I checked it (also with Claude), all the changes seems to be ok.

## Proof it works

A live `mcpc` probe built both this branch and the pre-change `master`
server, then diffed their MCP output byte-for-byte: `initialize`,
`tools/list` (all default-mode tools), `prompts/list`, `resources/list`
+ `read`, `tools/call` happy paths, a live Actor round-trip
(`call-actor` → `get-dataset-items` → `get-key-value-store-record` →
`resources/read`), every error path (invalid tool name, invalid
arguments, resource 404, unknown prompt, tool-level `SOFT_FAIL`),
`logging/setLevel`, and the task-mode pre-flight path were all identical
(only per-run IDs/timestamps differed). The unit suite is unchanged at
1177 passed + 1 skipped, and
`type-check`/`lint`/`check:agents`/`format:check` are clean.

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

https://claude.ai/code/session_017zHfSwpr57aUaxYjxAgsof

---
_Generated by [Claude
Code](https://claude.ai/code/session_017zHfSwpr57aUaxYjxAgsof)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
@jirispilka

Copy link
Copy Markdown
Collaborator Author

This is no longer needed, closing

@jirispilka jirispilka closed this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-ai Issues owned by the AI team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants