Skip to content

Design doc: IMCP2 local deployment (minimal stdio binary, no OAuth) - #77

Draft
aterga wants to merge 12 commits into
mainfrom
claude/secondary-mcp-local-deploy-jpof9i
Draft

Design doc: IMCP2 local deployment (minimal stdio binary, no OAuth)#77
aterga wants to merge 12 commits into
mainfrom
claude/secondary-mcp-local-deploy-jpof9i

Conversation

@aterga

@aterga aterga commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Design docs (docs-only) for imcp2-local: a separate, minimal run-it-yourself binary that speaks MCP over stdio, talks to mainnet IC + production Internet Identity, keeps the full II session model, and drops the entire OAuth 2.1 authorization server (the stdio process boundary replaces the bearer gate on the tool surface; login is a built-in browser handshake). The shared components move into a new imcp2-core library from which either binary is composed; the existing imcp2 crate keeps its name, binary, and deployments as the hosted server built on core.

Two documents, side by side, both in the standard feature-doc structure (Summary → Problem → Non-goals → Approach → Design components → Implementation Stages):

  • docs/local-deployment.md — the concise, high-level overview for stakeholders: one altitude up, no code-level detail.
  • docs/scoping-local-deployment.md — the full design: code-level partitions, the AI-client capability matrix and registration snippets, file:line evidence, verification plan. Rebased on main (v0.2.0) with every claim re-verified against the current tree.

Related issues

Changes

  • Add docs/local-deployment.md (concise overview) and docs/scoping-local-deployment.md (full design), cross-linked. The full doc covers:
    • Problem / Non-goals / Approach — why a local binary (trust + unnecessary OAuth machinery for a single-user pipe), what it is not (no hosted changes, no v1 persistence, not for cloud-only surfaces; the shipped binary targets mainnet + prod II, while a local-replica test configuration — an IC-endpoint override with a loopback-guarded fetch_root_key, against an ICP CLI-spawned replica — is a supported nice-to-have and the integration-test vehicle).
    • Design components: crate layout (imcp2-core as the common dependency of imcp2 and imcp2-local; the single-crate hosted feature flag is analyzed and rejected — feature unification would make the minimal closure invocation-dependent, and the crate boundary needs no #[cfg] gates; imcp2 re-exports core so embedders keep compiling; core becomes a second, family-internal published crate); dependency profile; mainnet + prod II wiring incl. the local-replica test configuration; the auth.rs OAuth-drop / II-connect-keep partition; the built-in browser II login (lazy, non-blocking, in-band authenticate tool; the transient loopback listener is the RFC 8252-style unavoidable minimum for II's redirect + #4091 origin fetch, and never serves the tool surface); AI tool client integration (Claude, Codex, Cursor, Perplexity, Antigravity); the tool/session singleton seam (13 call-sites); the security model (wallet-grade trust note, loopback hardening).
    • Implementation Stages with exits, the verification plan (integration tests run imcp2-local in the local-replica test configuration against an ICP CLI-spawned replica or PocketIC with a deployed II canister; remaining unknowns are loopback mixed-content/CORS), and the open decisions.

Testing

Docs-only change — no build/test impact.

  • No code changed (docs/ only)

Checklist

  • I have read the Contributing guidelines.
  • Docs updated (this PR is the docs).
  • No secrets, credentials, or internal-only information are included.

Open decisions (full doc → Implementation Stages)

  1. Login loopback listener: hand-roll (drops axum/tower-http, recommended) vs reuse axum.
  2. Browser open: open/webbrowser crate vs std::process::Command.
  3. Session persistence: in-memory only (v1) vs keychain-backed session key.
  4. Crate layout: imcp2-core common dependency (recommended) vs single-crate hosted feature flags (rejected in the doc); cost to accept is publishing core as a second, family-internal 0.x crate.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK

@aterga aterga changed the title Scope a secondary MCP server instance for local deployments Scope a minimal local MCP binary (stdio, no OAuth, production II) Jul 23, 2026
@aterga
aterga requested a review from Copilot July 27, 2026 13:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a scoping/design document for a separate “run-it-yourself” local MCP server binary that communicates over stdio, targets mainnet IC plus production Internet Identity, and removes the hosted OAuth 2.1 authorization-server layer while retaining an II browser-based delegation handshake.

Changes:

  • Add a draft scoping document defining the local-deployment model (stdio transport, no OAuth) and its security assumptions.
  • Propose a crate/binary layout (imcp2 core + imcp2-local) and a feature-gated hosted surface to keep the existing deployed imcp2 binary name unchanged.
  • Document verified dependency-stripping opportunities and a minimal tool/session seam for a singleton local session.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@aterga aterga mentioned this pull request Jul 29, 2026
7 tasks
claude added 4 commits August 20, 2026 15:01
Analyze the current beta/prod composition and scope a secondary server
instance that bridges the MCP tools to a local dfx replica instead of
mainnet. Documents the mainnet assumptions that would break locally
(root-key fetch, hard-coded IC_URL, the discovery SSRF/https guard,
target_origin canonicalization, and the CMC/ledger management path), a
proposed local run-profile design, the security guardrails, a phased
work breakdown, and open decisions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Rewrite the scoping doc for the intended model: a separate, minimal
run-it-yourself binary that speaks MCP over stdio and talks to mainnet
IC + production Internet Identity — not a local dfx replica. It drops
the entire OAuth 2.1 authorization-server layer (the local client is
co-located, so the stdio process boundary replaces bearer-token auth)
while keeping Internet Identity: login runs as a built-in browser
handshake and per-app account delegations work as today.

Adds a verified dependency-stripping analysis (four unreferenced crates
plus a vestigial schemars drop; rmcp swaps to the stdio transport and
drops auth), the auth.rs OAuth-drop/II-connect-keep partition, the
tool/session singleton seam, a 3-crate workspace layout, the security
model, and the production-II verification risks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
The shared library crate stays imcp2 (its existing embeddable identity)
and the hosted binary stays named imcp2, so the Dockerfile, systemd
unit, and deploy scripts that build/run an imcp2 binary are unchanged.
The hosted server + OAuth layer move behind a default-on `hosted`
feature (optional axum/tower-http); the new minimal stdio binary is a
separate imcp2-local crate depending on imcp2 with default-features
off, so it never compiles the OAuth/HTTP deps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Cover how imcp2-local integrates with Claude (Desktop/Code + the
cloud surfaces), Codex, Cursor, Perplexity, and Antigravity, verified
against each client's current MCP docs.

Key findings: a stdio server is reachable only by clients that spawn a
local subprocess (Claude Desktop/Code, Codex CLI/IDE/desktop, Cursor,
Antigravity, and the Perplexity macOS app via its PerplexityXPC helper);
cloud/remote-only surfaces (claude.ai web/mobile, Perplexity web/Windows,
Codex Cloud) can't reach localhost and need the hosted imcp2 server — the
reason the OAuth layer stays in imcp2. Host-driven OAuth never applies to
stdio, and stderr is invisible in chat on every client, so the II login
URL is surfaced in-band via an authenticate tool (lazy, non-blocking)
plus a best-effort browser auto-open. Adds a capability matrix, per-client
registration snippets, the login invariants, and the Perplexity remote
OAuth/discovery caveats; updates the login section and open decisions to
match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
@aterga
aterga force-pushed the claude/secondary-mcp-local-deploy-jpof9i branch from 38fb07f to ffbfaba Compare August 20, 2026 15:03
claude added 2 commits August 20, 2026 15:28
Re-verify every claim and file:line reference against the rebased tree
and absorb what main already did or changed:

- The five vestigial deps (ed25519-dalek, p256, ic-signature-verification,
  ic-representation-independent-hash, schemars 0.8) were already removed
  on main (#100) — reframe that stripping work as done.
- Account for the new hosted-only surfaces the local split must gate:
  prometheus + src/metrics.rs (/metrics, #124) and McpConfig's
  state_dir/require_resource (#144/#127); note the local binary needs no
  state directory at all. pocket-ic (e2e-only, #76) never enters any
  closure.
- Rewrite §10: production II now serves at /mcp by default (#92), CI
  probes it (#113), and the PocketIC e2e harness verifies the full
  connect contract — the remaining unknowns are the local binary's
  http://127.0.0.1 mixed-content and CORS behavior, and the harness is
  the plan for testing the local flow.
- Prod-instance env overrides are II_URL_PROD/II_CANISTER_ID_PROD (#92);
  registration chains are also verified client-side against the agent's
  root key (new_with_root_key); registration_pubkey_b64 is now fallible;
  AuthStore's dropped fields now include mcp_path and require_resource,
  and the dropped OAuth side grew RFC 8707/9207 and the bounded DCR store.
- Note the crates.io publishing dimension (v0.2.0, tag-guarded trusted
  publishing, docs/ excluded from the .crate) and re-stamp every stale
  file:line reference (auth.rs shifted ~500-650 lines; the 13 call-site
  list and all evidence-index anchors updated).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Reorganize into Summary -> Problem -> Non-goals -> Approach -> Design
components -> Implementation Stages (with the verification plan and
open decisions under the stages, and the code-evidence index as an
appendix). Content is unchanged apart from the connective text: the
same components, client matrix, security model, and file:line evidence,
now in the house design-doc structure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
@aterga aterga changed the title Scope a minimal local MCP binary (stdio, no OAuth, production II) Design doc: IMCP2 local deployment (minimal stdio binary, no OAuth) Aug 20, 2026
claude added 6 commits August 20, 2026 16:25
docs/local-deployment.md is the short, stakeholder-facing overview
(same Summary -> Problem -> Non-goals -> Approach -> Design components
-> Implementation Stages shape, one altitude up: no file:line evidence
or dependency minutiae); the full doc keeps the detail and both docs
cross-link.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Local replicas are spawned by the ICP CLI now; drop the dfx naming from
both design docs' non-goals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Talking to an ICP CLI-spawned local replica is a nice-to-have rather
than something we rule out: a test build enables it with an explicit
IC-endpoint override plus a loopback-guarded fetch_root_key, and the
integration tests run imcp2-local in exactly that configuration
(against an ICP CLI-spawned replica or PocketIC carrying a deployed II
canister). Chains are already verified against the injected agent's
root key, so the flow works on a test network. The shipped binary still
targets mainnet + production II only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
"The server has no listening socket" overclaimed: it is true of the MCP
tool surface (stdio), but the login handshake necessarily binds a
transient loopback listener — II navigates the browser to the callback
and fetches the #4091 well-known from the callback's origin, both of
which require a real HTTP origin (a custom URI scheme has no origin and
II offers no device-grant alternative; this is the RFC 8252 loopback
redirect). Scope the trust-boundary claim to the tool surface, note that
reaching the login listener confers nothing (static pages; /redeem is
state-correlated and only accepts a chain targeting the in-process X),
and add the rationale to the login component.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
Make the shared components a new imcp2-core library that both binaries
build on: imcp2 (unchanged name, lib + hosted binary, OAuth AS, routers,
metrics, e2e harness) and imcp2-local depend on it, and imcp2 re-exports
core's public items so existing embedders keep compiling.

The default-on `hosted` cargo feature is demoted to a rejected
alternative: features unify additively, so any crate enabling
imcp2/hosted would switch the OAuth/axum closure on for the whole build
graph, leaving the minimal-closure claim invocation-dependent; it also
needs cfg gates scattered through the code, where the crate boundary
states the component split structurally — with the core split the
session seam needs no #[cfg] at all (AuthedSession lives in core and the
Bearer arm reads http::request::Parts). Cost accepted: imcp2-core
becomes a second published crate (family-internal, 0.x, published before
imcp2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
The overview's "session seam" bullet leaned on the full doc's jargon
(seam, bearer-token lookup, singleton). State the motivation instead:
every tool call acts as the user's II session, the hosted server
resolves it per request from the OAuth bearer token, the single-user
local server reads the one in-memory session from login, and the tool
implementations are identical in both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK
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