feat(addie): server-side Socket Mode for adopter conformance assistance#4007
Merged
Conversation
Outbound-WebSocket channel that lets adopter dev/staging MCP servers connect to Addie. Adopter installs a small library, runs it locally, points it at Addie. Addie sees the connected session and (in PR #2) runs storyboards against it conversationally — surfacing bugs in chat, no public DNS, no inbound exposure. Reverse RPC at the TCP level only — the MCP protocol is symmetric, so a server-side Transport over an inbound WebSocket plus an MCP Client that consumes it gives Addie a normal MCP client interface against the adopter's existing MCP Server. What lands here: - server/src/conformance/ws-server-transport.ts — wraps a ws.WebSocket in the MCP Transport interface. Strict JSON-RPC parsing, idempotent close, error-without-disconnect on malformed frames. - server/src/conformance/session-store.ts — in-memory map keyed by WorkOS org_id. Last-writer-wins on duplicate connects, auto-evicts on transport close. - server/src/conformance/token.ts — issues/verifies HS256 JWTs scoped to "conformance", 1h TTL, signed with CONFORMANCE_JWT_SECRET. Distinct from WorkOS-signed tokens; these are first-party Addie tokens. - server/src/conformance/ws-route.ts — attaches the WS upgrade handler to the existing http.Server. Single shared endpoint at /conformance/connect; tenant scoping enforced server-side via JWT sub claim, never in URL. Heartbeat ping/pong. - server/src/conformance/token-route.ts — POST /api/conformance/token authenticated via existing requireAuth + resolveCallerOrgId. GET /api/conformance/_debug for dev introspection. - server/src/http.ts — wires both routes and shutdown closes sockets cleanly. - examples/conformance-client/ — prototype adopter library. Will move to adcp-client repo as @adcp/conformance-client before publish. - 5 unit + integration tests covering transport, session store, token, token route, and end-to-end (real WebSocket round-trip with tools/list + tools/call against an in-process adopter). PR #2 adds the storyboard runner adapter (depends on upstream @adcp/sdk patch for AgentClient.fromMcpClient). PR #3 adds the Addie chat tools, feature-flagged on CONFORMANCE_SOCKET_ENABLED. Closes nothing; tracks under #3991. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
bokelley
added a commit
to adcontextprotocol/adcp-client
that referenced
this pull request
May 3, 2026
* feat(server): ConformanceClient Socket Mode primitive
Outbound-WebSocket bridge so adopter dev/staging MCP servers can be
reached by a remote AdCP runner without public DNS, ngrok, or inbound
firewall exposure. Slack Socket Mode pattern.
Three-line integration:
import { ConformanceClient } from '@adcp/sdk/server';
import { mcpServer } from './my-mcp-server';
const client = new ConformanceClient({
url: 'wss://addie.agenticadvertising.org/conformance/connect',
token: process.env.ADCP_CONFORMANCE_TOKEN!,
server: mcpServer,
});
await client.start();
Reverse RPC at the TCP level only — MCP is symmetric (JSON-RPC 2.0,
request/response with correlation IDs), so swapping HTTP for WebSocket
and flipping connection direction gives the runner a normal MCP Client
against the adopter's existing MCP Server. The SDK already exposes
`AgentClient.fromMCPClient()` (line 248 of core/AgentClient.ts) so
runners can wrap that MCP Client for the storyboard pipeline.
Dev/staging only by design — production deployments must not expose
`comply_test_controller` on any surface, including this channel
(adcontextprotocol/adcp#3986). The ConformanceClient JSDoc names the
constraint and points at the spec rule.
What lands:
- src/lib/server/socket-mode/ws-transport.ts — WebSocket-backed MCP
Transport. Strict JSON-RPC parsing via JSONRPCMessageSchema, rejects
binary frames, idempotent close, error-without-disconnect on
malformed frames.
- src/lib/server/socket-mode/conformance-client.ts — high-level
adopter-facing class. Status callback, exponential-backoff reconnect
capped at 30s, opt-out via close().
- src/lib/server/socket-mode/index.ts — module exports.
- src/lib/server/index.ts — re-exports ConformanceClient + types from
the @adcp/sdk/server entry, alongside the existing server primitives.
- src/lib/server/socket-mode/conformance-client.test.ts — 3 tests
covering full WebSocket round-trip with tools/list + tools/call,
status transitions, and error path on unreachable URL.
- ws ^8 added as a regular dep; @types/ws as devDep.
Server-side terminator (the runner accepting these connections) is the
adopter's choice — Addie ships one in adcontextprotocol/adcp#4007 and
will host the canonical endpoint at addie.agenticadvertising.org. The
transport is neutral; any orchestrator that speaks the same handshake
could host the other end.
cc @bokelley
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(deps): move @types/ws to dependencies so adopter d.ts type-checks
The new socket-mode/ws-transport.ts and conformance-client.ts both
declare private fields typed against `WebSocket` from `ws`. That type
leaks into the published .d.ts; without `@types/ws` in dependencies,
adopters get TS7016 ("Could not find a declaration file for module
'ws'") on a clean install.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
The in-tree prototype at examples/conformance-client/src/ shipped upstream as ConformanceClient in @adcp/sdk 6.9 (adcp-client#1506). Replace the local copy with an import from @adcp/sdk/server so adopters who already install the SDK get the primitive for free and we avoid the version-skew risk of two parallel implementations. - Bump @adcp/sdk dep from ^6.7.0 to ^6.9.0. - Delete examples/conformance-client/src/ (170 LOC of duplicate ConformanceClient + WebSocketTransport). - Rewrite demo.ts to import from @adcp/sdk/server. - Update README to point at the published package and drop the "lives here while we prototype" framing. The server-side ConformanceWSServerTransport stays in server/src/conformance/ — that's the runner half of the channel and lives here because the runner is Addie. Only the adopter-side primitive moved upstream. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 4, 2026
PR #2 of 3 for Addie Socket Mode. PR #1 added the server-side WS plumbing; this PR adds the runner that lets Addie execute a storyboard against a connected adopter session. The adapter is small (~80 LOC) because the SDK already gives us everything we need: - `AgentClient.fromMCPClient(mcpClient)` is the in-process injection factory the SDK has shipped since 6.7. It wraps any pre-connected MCP `Client` into an AgentClient that the storyboard runner consumes via `_client` (the same path `comply()` uses internally). - `runStoryboard(agentUrl, storyboard, options)` is the existing runner. We pass a placeholder `adcp-conformance-socket://<orgId>` URL plus `_client: agentClient` and the runner happily ignores the URL and dispatches via the injected client. Net result: zero changes to `server/src/services/storyboards.ts`, `server/src/addie/services/compliance-testing.ts`, or `server/src/addie/jobs/compliance-heartbeat.ts`. The conformance runner is a separate function picking storyboards from the same registry. Files: - server/src/conformance/run-storyboard-via-ws.ts — the adapter. Throws `ConformanceNotConnectedError` when no live session, `StoryboardNotFoundError` when storyboard id is unknown. - server/src/conformance/index.ts — re-export. - server/tests/unit/conformance-run-storyboard.test.ts — 3 tests covering both error paths and the success path. The success test stands up a real WebSocket connection (proving the wiring is end-to-end) but mocks `runStoryboard` itself so we can inspect the AgentClient and options the adapter passes through, without needing a real sales agent + test kits to actually run a storyboard. PR #3 adds the Addie chat tools (`issue_conformance_token`, `run_conformance_against_my_agent`) that consume this adapter, gated behind `CONFORMANCE_SOCKET_ENABLED=1`. Stacked on `bokelley/conformance-socket-mode-server` (PR #4007). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
bokelley
added a commit
that referenced
this pull request
May 4, 2026
…script Adds docs/building/addie-socket-mode.mdx — an adopter-facing walkthrough of the conformance Socket Mode channel from PRs #4007/#4051/#4054. Covers when to use it (vs the public-endpoint AAO heartbeat), prerequisites, the five-minute setup, what Addie can do once connected, privacy/safety posture, and troubleshooting for the common failure modes I hit while smoke-testing. Mounted in the "Build" sidebar between validate-your-agent and grading so it lives next to the other agent-development tools rather than buried in implementation reference. Cross-linked from the existing get-test-ready and aao-verified pages where appropriate via inline references in the body. Also adds scripts/smoke-conformance.ts — the end-to-end smoke I ran against the stack before writing the doc. Spins up the server-side conformance routes, connects a real adopter via @adcp/sdk 6.9 ConformanceClient, exercises both Addie chat tools (issue_token + run storyboard). Stays as a runnable artifact for future regression checks and as a worked example for anyone who wants to see the full flow in ~150 LOC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 4, 2026
PR #2 of 3 for Addie Socket Mode. PR #1 added the server-side WS plumbing; this PR adds the runner that lets Addie execute a storyboard against a connected adopter session. The adapter is small (~80 LOC) because the SDK already gives us everything we need: - `AgentClient.fromMCPClient(mcpClient)` is the in-process injection factory the SDK has shipped since 6.7. It wraps any pre-connected MCP `Client` into an AgentClient that the storyboard runner consumes via `_client` (the same path `comply()` uses internally). - `runStoryboard(agentUrl, storyboard, options)` is the existing runner. We pass a placeholder `adcp-conformance-socket://<orgId>` URL plus `_client: agentClient` and the runner happily ignores the URL and dispatches via the injected client. Net result: zero changes to `server/src/services/storyboards.ts`, `server/src/addie/services/compliance-testing.ts`, or `server/src/addie/jobs/compliance-heartbeat.ts`. The conformance runner is a separate function picking storyboards from the same registry. Files: - server/src/conformance/run-storyboard-via-ws.ts — the adapter. Throws `ConformanceNotConnectedError` when no live session, `StoryboardNotFoundError` when storyboard id is unknown. - server/src/conformance/index.ts — re-export. - server/tests/unit/conformance-run-storyboard.test.ts — 3 tests covering both error paths and the success path. The success test stands up a real WebSocket connection (proving the wiring is end-to-end) but mocks `runStoryboard` itself so we can inspect the AgentClient and options the adapter passes through, without needing a real sales agent + test kits to actually run a storyboard. PR #3 adds the Addie chat tools (`issue_conformance_token`, `run_conformance_against_my_agent`) that consume this adapter, gated behind `CONFORMANCE_SOCKET_ENABLED=1`. Stacked on `bokelley/conformance-socket-mode-server` (PR #4007). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 4, 2026
PR #3 of 3 for Addie Socket Mode. PRs #1+#2 added the server WS plumbing and the storyboard runner adapter; this PR exposes them to the user via two Addie chat tools: - `issue_conformance_token` — mints a fresh JWT bound to the caller's WorkOS organization, returns shell exports + a copy- paste @adcp/sdk/server ConformanceClient snippet so the adopter can wire it into their dev environment in under a minute. - `run_conformance_against_my_agent` — runs a storyboard against the adopter MCP server connected to the live conformance session for the caller's org. Renders phase/step pass/fail/ skipped status as markdown with trimmed error text on failures. Both tools are bound to a WorkOS organization. Anonymous chats get a not-mapped hint; orgs with no live conformance session get a connect-the-client hint with the exact snippet they need. Wiring: - server/src/addie/mcp/conformance-tools.ts — tool definitions + handler factory `createConformanceToolHandlers(memberContext)`. - server/src/addie/bolt-app.ts — registration block gated on `CONFORMANCE_SOCKET_ENABLED=1`. Server-side WS plumbing remains always-wired (the chat surface is what the flag toggles). - server/src/addie/tool-sets.ts — new `agent_conformance` toolset for the router. - server/tests/unit/conformance-addie-tools.test.ts — 8 unit tests covering org-binding enforcement, missing-secret error, not-connected hint, missing-id message, passing+failing storyboard markdown rendering. Stacked on `bokelley/conformance-storyboard-runner` (PR #4051), which is itself stacked on `bokelley/conformance-socket-mode-server` (PR #4007). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 4, 2026
…script Adds docs/building/addie-socket-mode.mdx — an adopter-facing walkthrough of the conformance Socket Mode channel from PRs #4007/#4051/#4054. Covers when to use it (vs the public-endpoint AAO heartbeat), prerequisites, the five-minute setup, what Addie can do once connected, privacy/safety posture, and troubleshooting for the common failure modes I hit while smoke-testing. Mounted in the "Build" sidebar between validate-your-agent and grading so it lives next to the other agent-development tools rather than buried in implementation reference. Cross-linked from the existing get-test-ready and aao-verified pages where appropriate via inline references in the body. Also adds scripts/smoke-conformance.ts — the end-to-end smoke I ran against the stack before writing the doc. Spins up the server-side conformance routes, connects a real adopter via @adcp/sdk 6.9 ConformanceClient, exercises both Addie chat tools (issue_token + run storyboard). Stays as a runnable artifact for future regression checks and as a worked example for anyone who wants to see the full flow in ~150 LOC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
bokelley
added a commit
that referenced
this pull request
May 4, 2026
… tools (#4082) * feat(addie): conformance Socket Mode storyboard runner adapter PR #2 of 3 for Addie Socket Mode. PR #1 added the server-side WS plumbing; this PR adds the runner that lets Addie execute a storyboard against a connected adopter session. The adapter is small (~80 LOC) because the SDK already gives us everything we need: - `AgentClient.fromMCPClient(mcpClient)` is the in-process injection factory the SDK has shipped since 6.7. It wraps any pre-connected MCP `Client` into an AgentClient that the storyboard runner consumes via `_client` (the same path `comply()` uses internally). - `runStoryboard(agentUrl, storyboard, options)` is the existing runner. We pass a placeholder `adcp-conformance-socket://<orgId>` URL plus `_client: agentClient` and the runner happily ignores the URL and dispatches via the injected client. Net result: zero changes to `server/src/services/storyboards.ts`, `server/src/addie/services/compliance-testing.ts`, or `server/src/addie/jobs/compliance-heartbeat.ts`. The conformance runner is a separate function picking storyboards from the same registry. Files: - server/src/conformance/run-storyboard-via-ws.ts — the adapter. Throws `ConformanceNotConnectedError` when no live session, `StoryboardNotFoundError` when storyboard id is unknown. - server/src/conformance/index.ts — re-export. - server/tests/unit/conformance-run-storyboard.test.ts — 3 tests covering both error paths and the success path. The success test stands up a real WebSocket connection (proving the wiring is end-to-end) but mocks `runStoryboard` itself so we can inspect the AgentClient and options the adapter passes through, without needing a real sales agent + test kits to actually run a storyboard. PR #3 adds the Addie chat tools (`issue_conformance_token`, `run_conformance_against_my_agent`) that consume this adapter, gated behind `CONFORMANCE_SOCKET_ENABLED=1`. Stacked on `bokelley/conformance-socket-mode-server` (PR #4007). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(addie): conformance Socket Mode chat tools (feature-flagged) PR #3 of 3 for Addie Socket Mode. PRs #1+#2 added the server WS plumbing and the storyboard runner adapter; this PR exposes them to the user via two Addie chat tools: - `issue_conformance_token` — mints a fresh JWT bound to the caller's WorkOS organization, returns shell exports + a copy- paste @adcp/sdk/server ConformanceClient snippet so the adopter can wire it into their dev environment in under a minute. - `run_conformance_against_my_agent` — runs a storyboard against the adopter MCP server connected to the live conformance session for the caller's org. Renders phase/step pass/fail/ skipped status as markdown with trimmed error text on failures. Both tools are bound to a WorkOS organization. Anonymous chats get a not-mapped hint; orgs with no live conformance session get a connect-the-client hint with the exact snippet they need. Wiring: - server/src/addie/mcp/conformance-tools.ts — tool definitions + handler factory `createConformanceToolHandlers(memberContext)`. - server/src/addie/bolt-app.ts — registration block gated on `CONFORMANCE_SOCKET_ENABLED=1`. Server-side WS plumbing remains always-wired (the chat surface is what the flag toggles). - server/src/addie/tool-sets.ts — new `agent_conformance` toolset for the router. - server/tests/unit/conformance-addie-tools.test.ts — 8 unit tests covering org-binding enforcement, missing-secret error, not-connected hint, missing-id message, passing+failing storyboard markdown rendering. Stacked on `bokelley/conformance-storyboard-runner` (PR #4051), which is itself stacked on `bokelley/conformance-socket-mode-server` (PR #4007). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(addie): user-facing Pair-with-Addie (Socket Mode) guide + smoke script Adds docs/building/addie-socket-mode.mdx — an adopter-facing walkthrough of the conformance Socket Mode channel from PRs #4007/#4051/#4054. Covers when to use it (vs the public-endpoint AAO heartbeat), prerequisites, the five-minute setup, what Addie can do once connected, privacy/safety posture, and troubleshooting for the common failure modes I hit while smoke-testing. Mounted in the "Build" sidebar between validate-your-agent and grading so it lives next to the other agent-development tools rather than buried in implementation reference. Cross-linked from the existing get-test-ready and aao-verified pages where appropriate via inline references in the body. Also adds scripts/smoke-conformance.ts — the end-to-end smoke I ran against the stack before writing the doc. Spins up the server-side conformance routes, connects a real adopter via @adcp/sdk 6.9 ConformanceClient, exercises both Addie chat tools (issue_token + run storyboard). Stays as a runnable artifact for future regression checks and as a worked example for anyone who wants to see the full flow in ~150 LOC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(conformance): dev-only run-storyboard trigger + training-agent smoke Adds a dev-only POST /api/conformance/_debug/run-storyboard endpoint that triggers runStoryboardViaConformanceSocket(orgId, storyboardId) against a live conformance session. Gated on NODE_ENV !== 'production' alongside the existing /_debug session-list endpoint, requires auth, and exists so local smoke harnesses can exercise the full PR #2 path without the Addie chat surface. Also adds scripts/smoke-conformance-training-agent.ts — a "proxy adopter" that connects to the local conformance endpoint via @adcp/sdk 6.9 ConformanceClient and forwards every inbound MCP request to the locally running training agent's /api/training-agent/sales/mcp HTTP endpoint. Demonstrates the full Socket Mode → training agent path end-to-end without modifying the training agent's HTTP-bound setup. Run output (storyboard: media_buy_state_machine): ✓ Capability discovery ✓ Create a media buy (2/2) ✗ Valid state transitions (Pause + Resume passed, Cancel returned a training-agent 500 — separate bug to file) ⊘ Terminal state enforcement (skipped on prerequisite failure) The failures here are real training-agent issues surfaced by the storyboard runner reaching it through Socket Mode + the proxy. The transport itself is invisible above MCP: same shape as running the storyboard over direct HTTP, plus a hop through the WebSocket. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(conformance): close expert-review findings on Socket Mode stack Five fixes from the security + code-review pass: 1. **Race-eviction on same-org reconnect (must-fix).** The close-listener on each WS connection removed the session by orgId only. When a second adopter from the same org connected, register()'s last-writer- wins displacement closed the prior socket, which fired its close listener and deleted the just-registered new session — leaving the org permanently unreachable until the next disconnect. Fix: identity- keyed eviction, only remove if `sessions.get(orgId)?.transport` still points at THIS transport. New regression test covers it. 2. **Pre-register disconnect leak.** If the adopter closed the socket between `client.connect(transport)` and `register(...)`, we'd register a session whose underlying socket is already gone. Fix: check `ws.readyState === OPEN` before registering; bail otherwise. 3. **Liveness check before runner dispatch.** `runStoryboardViaConformanceSocket` now bails with `ConformanceNotConnectedError` if the resolved session's transport is closed (and evicts it from the store as a side effect). Avoids handing a dead AgentClient to the runner. Adds `ConformanceWSServerTransport.isClosed()` for the check. 4. **Subprotocol-sentinel tightening.** Earlier code accepted `Sec-WebSocket-Protocol: mcp, <token>` as a fallback. Drop it; require the explicit `adcp.conformance` sentinel. Reordered token extraction to prefer the header path (out of access logs) over the `?token=` query (which lands in pino/proxy logs). Documented the staging-log-as-token-equivalent caveat for the query fallback. New regression test rejects the wrong-sentinel form. 5. **Debug endpoint tenant scoping.** `_debug/run-storyboard` previously accepted arbitrary `org_id` from the request body, letting any authenticated user (on a misconfigured staging) fan storyboards into another tenant's session. Fix: normal callers must run against their own resolved org; static-admin-API-key callers retain the ability to specify `org_id` for local smoke tools (consistent with the rest of the admin-key surface). Production builds still skip the entire `_debug/*` block. 42/42 conformance tests pass (added 2 regression tests for #1 and #4). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
bokelley
added a commit
that referenced
this pull request
May 4, 2026
…4098) * fix(training-agent): tenant router lock + storyboard-smoke --brand Two follow-ups surfaced by the Socket Mode stack work (#4007/#4082) against the local training agent. 1. **Tenant router race (closes #4084).** The tenant router shares one MCP `Server` instance per tenant from the framework's `DecisioningAdcpServer` registry. Each request created a fresh `StreamableHTTPServerTransport`, called `server.connect(transport)`, handled the request, and `server.close()`'d. Two concurrent requests against the same tenant overlapped and the second `.connect()` threw "Already connected to a transport" — surfaced as intermittent 500s under back-to-back HTTP load. Fix: per-tenant async lock (`withTenantLock`) serializes the `connect/handle/close` window per tenant so the shared server only ever has one transport bound at a time. Throughput is gated by the in-flight request's wallclock; the storyboard runner's sequential dispatch makes this a non-issue, and the compliance heartbeat runs one tenant at a time. A future improvement could pool servers per tenant for true parallelism — this lock is the minimum-mass correctness change. Verification: 10 concurrent `tools/list` POSTs against `/api/training-agent/sales/mcp` return all `"result"` (was a mix of "result" and 500s before); server log shows zero "Already connected" errors (was 7+ per run before). 2. **storyboard-smoke `--brand` flag (closes #4083).** That issue reported `update_media_buy` on a cancelled buy returning `MEDIA_BUY_NOT_FOUND` instead of `INVALID_STATE`. After diagnosis the training agent is correct — the bug is in the upstream SDK runner's `update_media_buy` enricher, which fabricates an account from `resolveBrand(options)` (defaulting to `test.example`) when options.brand is unset. Positive-path steps get rewritten to `test.example`; `expect_error` steps skip the enricher and keep the YAML's literal `acmeoutdoor.example`. Result: split-brain session keying and stale-state reads on the negative-path probes. Fix: storyboard-smoke now accepts `--brand <domain>`. With `--brand acmeoutdoor.example`, the SDK runner's `applyBrandInvariant` normalizes both step kinds to the kit's domain and the storyboard passes 9/9. Long-form rationale in the JSDoc. Also un-blocks the pre-commit typecheck by adding a `// @ts-expect-error` on the v6-sales-platform `handoffToTask` call. PR #4080 bumped @adcp/sdk to 6.11 expecting the two-arg signature from adcp-client#1554, but the published 6.11 .d.ts still declares the single-arg shape only. Runtime accepts the second arg correctly — pure typings gap. Drop the directive when 6.12+ ships the matching typing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(training-agent): drop @ts-expect-error — local was stale on @adcp/sdk CI surfaced "Unused '@ts-expect-error' directive" because CI's npm install resolved @adcp/sdk 6.11.0 (which has the two-arg `handoffToTask` typing from adcp-client#1554), while my local node_modules was still on 6.9.0 (no typing for the second arg) — the version mismatch made the directive necessary locally but unused on CI. Refreshed node_modules to 6.11.0 and dropped the directive. Both surfaces typecheck clean now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(training-agent): expand tenant-lock comments per code review Two doc-only fixes from the PR #4098 code review pass: 1. `withTenantLock` — explain why the lock includes flushDirtySessions and server.close() (not just the transport window): in-memory session state mutations from request N must persist before N+1 runs against the same shared `DecisioningAdcpServer`. Narrowing the lock would race on the v5 handlers' session-context state. 2. `withTenantLock` — explain the `.catch(() => {})` chain-keepalive pattern so a future reader doesn't "fix" it by removing the catch (which would poison every subsequent same-tenant request). 3. `storyboard-smoke.ts` — add `--brand` to the usage block so the flag is discoverable from the file header. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR #1 of three for Addie Socket Mode — an outbound-WebSocket conformance/live-assistance channel that lets adopters' dev/staging MCP servers connect to Addie. Once connected, Addie can run storyboards against them and surface bugs in chat. No public DNS, no ngrok, no inbound exposure.
This PR lands the server-side plumbing only. Storyboard runner adaptation is PR #2, Addie chat tool integration is PR #3 (feature-flagged).
Architecture
Reverse RPC at the TCP level only. MCP is symmetric (JSON-RPC 2.0, request/response with correlation IDs), so swapping HTTP for WebSocket and flipping connection direction gives Addie a normal MCP
Clientagainst the adopter's existing MCPServer. The MCP SDK'sTransportinterface does the heavy lifting; we add ~250 LOC to wire up server-side WS, token issuance, and session lifecycle.Tenant scoping is enforced server-side at auth via the JWT
subclaim — never in the URL. Single shared endpoint by design, Slack Socket Mode pattern. See adcontextprotocol/adcp#3991 for the full strategic context.What's in this PR
server/src/conformance/ws-server-transport.ts— wrapsws.WebSocketin MCPTransportserver/src/conformance/session-store.ts— in-memory map keyed by WorkOS org_idserver/src/conformance/token.ts— HS256 JWT issuance/verification, scopedconformance, 1h TTLserver/src/conformance/ws-route.ts— WS upgrade handler at/conformance/connect, ping/pong heartbeatserver/src/conformance/token-route.ts—POST /api/conformance/token+ dev_debugendpointserver/src/http.ts— wires routes, attaches WS to listening server, closes sockets on shutdownexamples/conformance-client/— prototype adopter library (will move toadcp-clientrepo before publish)tools/list+tools/callagainst an in-process adopter MCP server). 29 tests, all green.Privacy posture
Channel is dev/staging only — same constraint as
comply_test_controllerper #3986. Adopter explicitly opens the socket each session, can disconnect at any time, and what Addie sees stays in their Addie context. No persistent tunnel, no production exposure.Configuration
Two new env vars:
CONFORMANCE_JWT_SECRET(required when feature is used) — HS256 signing keyCONFORMANCE_WS_PUBLIC_URL(optional) — overrides the auto-derivedwss://URL returned by the token endpointNo env changes required for builds that don't use the feature; the routes exist but the WS handler 401s without a valid token, and the token endpoint 500s with a clear error message if the secret is unset.
Sequencing
@adcp/sdkupstream patch forAgentClient.fromMcpClient)issue_conformance_token,run_conformance_against_my_agent), gated onCONFORMANCE_SOCKET_ENABLED=1Test plan
POST /api/conformance/tokenwith WorkOS session, runexamples/conformance-client/demo.tswith token, verifyGET /api/conformance/_debugshows the connected session🤖 Generated with Claude Code