Skip to content

feat: self-contained MCP host extensions for stateless engine construction#226

Merged
roackb2 merged 1 commit into
mainfrom
feat/sdk-self-contained-mcp-host-extension
Jul 9, 2026
Merged

feat: self-contained MCP host extensions for stateless engine construction#226
roackb2 merged 1 commit into
mainfrom
feat/sdk-self-contained-mcp-host-extension

Conversation

@roackb2

@roackb2 roackb2 commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Why

An embedder that builds a fresh conversation engine per request (e.g. a multi-tenant server: per-user API key + injected per-user storage) currently has to prep MCP under every engine's stateRoot — a per-request config write + catalog refresh + a per-user .heddle/ directory. The MCP host extension re-read the MCP config/catalog from context.stateRoot at runtime, so it was not reusable across cheap per-request engines.

This is the Phase A enhancement identified while pressure-testing Heddle against a real server integration: make engine construction effectively stateless so one prepared MCP extension can be shared across many per-request engines.

What

  • prepareMcpHostExtension(...) now returns a self-contained extension: it embeds the resolved server config + cached catalog. At runtime the toolkit resolves tools from that embedded data and executes them straight through McpClientService using the embedded config — no stateRoot read.
    • MCP execution was already stateless (spawn-per-call via McpClientService, then close); the embedded config just removes the stateRoot dependency. The same allow/deny guard McpService.callTool applies is preserved.
  • Backward compatible: defineMcpHostExtension(options) with no embedded data keeps the original stateRoot-reading path (the local CLI host is unchanged). Preparation passes embedded data via the optional 2nd arg defineMcpHostExtension(options, resolved).

Result

Prepare the MCP extension once (one spawn + catalog refresh), then reuse the same extension across per-request engines with per-user key + injected stores — zero per-engine MCP prep, no per-user .heddle/mcp*.

Tests

  • New unit test: a prepared/embedded extension resolves and executes tools against an empty stateRoot, honoring deny policy, and never calls the stateRoot-backed McpService.
  • Full suite green: 602 unit + 285 integration. Typecheck, lint, and build clean.

🤖 Generated with Claude Code

…ction

Prepared MCP host extensions now embed their resolved server config and cached
catalog, so the toolkit resolves and executes tools from that embedded data
instead of re-reading the MCP config/catalog from context.stateRoot at runtime.

Why: an embedder that builds a fresh conversation engine per request (e.g. a
multi-tenant server with a per-user API key and injected per-user storage) had
to prep MCP under every engine's stateRoot, forcing a per-request config write +
catalog refresh + a per-user .heddle/ directory. Now one prepared extension is
reused across cheap per-request engines with zero per-engine MCP prep.

- prepareMcpHostExtension returns a self-contained extension (embeds
  resolvedServer + catalog); execution goes straight to McpClientService using
  the embedded config (already stateless: spawn-per-call, then close), honoring
  the same allow/deny guard McpService applies.
- Backward compatible: defineMcpHostExtension(options) with no embedded data
  keeps the stateRoot-reading path (the local CLI host is unchanged).
- Adds a unit test proving resolution + execution work against an EMPTY
  stateRoot and never touch McpService.

Refs: heddle-workspace-notes stateless-engine design (Phase A step 1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CjFdZgZhov8wdwFinVTfjz
@roackb2 roackb2 merged commit 0c9a29a into main Jul 9, 2026
5 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