diff --git a/docs/design/agent-workflows/projects/pi-mcp-parity/README.md b/docs/design/agent-workflows/projects/pi-mcp-parity/README.md new file mode 100644 index 0000000000..ed7e8fad90 --- /dev/null +++ b/docs/design/agent-workflows/projects/pi-mcp-parity/README.md @@ -0,0 +1,37 @@ +# Pi MCP parity + +Status: design ready for review +Date: 2026-07-14 + +This project makes the existing remote MCP configuration work with Pi. The service-side path stays +identical to Claude. Only the harness-specific last mile changes: + +```text +saved agent.mcps + | + v +service resolves secret references + | + v +ResolvedMCPServer[] + | + +--> Claude adapter --> ACP mcpServers + | + +--> Pi adapter ------> pi-mcp-adapter config +``` + +The Pi path reuses [`pi-mcp-adapter`](https://github.com/nicobailon/pi-mcp-adapter). Agenta does +not implement another MCP client, introduce a gateway, or change the saved object. + +## Decision + +- Keep `MCPServerConfig` and `ResolvedMCPServer` unchanged. +- Resolve secrets in the agent service exactly as for Claude. +- Generate private `pi-mcp-adapter` configuration from the resolved run object. +- Start with its default `mcp` proxy tool, which works without cache warm-up or restart. +- Support remote HTTP, no auth, secret-backed headers, and all tools. +- Use the same implementation locally and on Daytona. +- Do not add OAuth, public stdio, MCP UI, probing, flags, compatibility, or a gateway. + +See [context](context.md), [research](research.md), [interface](interface.md), +[implementation plan](plan.md), [QA](qa.md), and [status](status.md). diff --git a/docs/design/agent-workflows/projects/pi-mcp-parity/context.md b/docs/design/agent-workflows/projects/pi-mcp-parity/context.md new file mode 100644 index 0000000000..81120826d3 --- /dev/null +++ b/docs/design/agent-workflows/projects/pi-mcp-parity/context.md @@ -0,0 +1,44 @@ +# Context + +## Current path + +The agent service reads `parameters.agent.mcps`, validates it, resolves project secret references, +and sends `ResolvedMCPServer[]` to the runner. This path is harness-independent. + +Claude receives the resolved HTTP servers as ACP `sessionInit.mcpServers`. The runner validates +URLs and applies its SSRF policy before delivery. + +The session fingerprint already includes `mcpServers`. Changing an endpoint, secret reference, or +policy makes the next normal turn cold. Approval resume intentionally continues the parked process. + +## Why Pi does not work + +The runner rejects Pi requests containing MCP servers with +`PI_USER_MCP_UNSUPPORTED_MESSAGE`. Removing the gate alone does not help because +`buildSessionMcpServers()` returns an empty list for Pi. + +Installed `pi-acp@0.0.29` accepts ACP MCP entries but does not forward them into Pi. Latest +`pi-acp@0.0.31` documents the same limitation. This is not a UI or warm-session bug. + +Pi supports extensions, and the runner already installs an Agenta extension in local and Daytona +runs. `pi-mcp-adapter` supplies the missing HTTP MCP client. + +## Boundary + +| Concern | Shared | Claude last mile | Pi last mile | +| --- | --- | --- | --- | +| Saved config | Yes | None | None | +| Secret resolution | Agent service | Reused | Reused | +| URL and SSRF validation | Runner | Reused | Reused | +| MCP client | No | Claude Code | `pi-mcp-adapter` | +| Harness config | No | ACP entries | Temporary adapter config | + +This remains direct MCP delivery, not the future gateway. + +## Scope + +In scope: remote HTTP, none and secret-header auth, `tools.mode = "all"`, local and Daytona, +cold configuration replacement, and redacted errors. + +Out of scope: OAuth, public stdio, host-config imports, setup panels, MCP UI, sampling, elicitation, +direct-tool cache reload, gateway connections, and public contract changes. diff --git a/docs/design/agent-workflows/projects/pi-mcp-parity/interface.md b/docs/design/agent-workflows/projects/pi-mcp-parity/interface.md new file mode 100644 index 0000000000..3cda038266 --- /dev/null +++ b/docs/design/agent-workflows/projects/pi-mcp-parity/interface.md @@ -0,0 +1,75 @@ +# Interface + +## Public interfaces do not change + +```json +{ + "name": "exa", + "connection": { + "type": "http", + "url": "https://mcp.exa.ai/mcp", + "headers": {}, + "credentials": { + "type": "header_secret_refs", + "headers": {"x-api-key": "exa_api_key"} + } + }, + "policy": { + "tools": {"mode": "all"}, + "permission": "ask" + } +} +``` + +The service resolves this into the existing per-run object. Pi adds no API, ACP field, transport +selector, config version, feature flag, or compatibility decoder. + +## Private rendering + +```ts +interface PiMcpAdapterConfig { + settings: { + toolPrefix: "server"; + directTools: false; + sampling: false; + elicitation: false; + }; + mcpServers: Record; + lifecycle: "lazy"; + exposeResources: false; + directTools: false; + }>; +} +``` + +This is a private renderer, not another source of truth. Unsupported connection and policy modes +fail closed. + +## Runtime artifact + +The generated adapter file is secret-bearing. It must: + +- live outside the durable conversation workspace; +- use a unique per-process path and restrictive permissions; +- remain for the process lifetime because the adapter may reload it; +- stay out of logs, traces, errors, snapshots, and inspect responses; +- be deleted during teardown; +- follow the same lifecycle locally and on Daytona. + +A narrow `pi-acp` launch patch may pass only the config path to Pi. Header values never enter the +environment. + +This does not claim secrets are hidden from the MCP client. Like Claude, the selected sandboxed +harness receives the resolved header so it can authenticate, while Agenta avoids persistence. + +After local and Daytona acceptance, Pi publishes the existing `mcp.user_servers` capability. The +frontend then shows the editor without Pi-specific UI logic. + +Pi sees the adapter's `mcp` proxy tool and can list servers, discover tools, and call them. This +presentation differs from Claude's direct list, but public and resolved interfaces are identical. + +Remote tool execution must reuse Pi's existing extension `tool_call` permission hook and +`pi-acp` approval request path. Discovery-only proxy actions may proceed without approval. +Do not add another approval protocol. diff --git a/docs/design/agent-workflows/projects/pi-mcp-parity/plan.md b/docs/design/agent-workflows/projects/pi-mcp-parity/plan.md new file mode 100644 index 0000000000..fb4020c121 --- /dev/null +++ b/docs/design/agent-workflows/projects/pi-mcp-parity/plan.md @@ -0,0 +1,55 @@ +# Implementation plan + +## Slice 0: Prove the adapter + +1. Pin `pi-mcp-adapter@2.11.0`. +2. Package and load it beside the Agenta Pi extension. +3. Use an HTTPS fixture to list a server and call one tool. +4. Confirm no `pi-acp` upgrade is required. + +Exit: the pinned upstream adapter works in headless Pi. If bundling fails, ship its package tree. +Do not implement an MCP client. + +## Slice 1: Render shared resolved servers + +1. Add a pure `ResolvedMCPServer[]` to `PiMcpAdapterConfig` renderer. +2. Reuse URL, reserved-name, and SSRF validation. +3. Accept only HTTP, `tools.mode = all`, and current credential outcomes. +4. Set proxy mode, lazy lifecycle, no resources, sampling, or elicitation. +5. Keep headers out of errors and logs. + +Exit: the same resolved input used by Claude produces deterministic Pi config. + +## Slice 2: Deliver locally + +1. Isolate Pi assets when MCP servers exist. +2. Install both extensions and write temporary config with restrictive permissions. +3. Pass the path through the narrow launch option. +4. Remove the unsupported gate only after delivery is wired. +5. Route proxy tool execution through the existing Pi extension permission hook. +6. Preserve fingerprint and teardown behavior. + +Exit: local Pi calls no-secret and secret-header fixtures. + +## Slice 3: Deliver identically on Daytona + +1. Upload the same pinned adapter asset. +2. Write the same config outside durable cwd. +3. Start Pi with the same option and clean up after exit. +4. Add no Daytona-specific protocol or gateway route. + +Exit: the same fixtures pass with the same revision. + +## Slice 4: Advertise and accept + +1. Publish existing `mcp.user_servers` capability for Pi. +2. Verify the existing UI appears without Pi-specific code. +3. Run the local and Daytona matrix. +4. Capture a replay test from a successful no-secret run. + +Exit: users configure the same MCP in the same UI and Pi can use it. + +## Non-goals + +No gateway, Agenta MCP client, OAuth, public stdio, flags, compatibility, migration, direct-tool +cache warm-up, or new saved and `/run` fields. diff --git a/docs/design/agent-workflows/projects/pi-mcp-parity/qa.md b/docs/design/agent-workflows/projects/pi-mcp-parity/qa.md new file mode 100644 index 0000000000..7a746458ee --- /dev/null +++ b/docs/design/agent-workflows/projects/pi-mcp-parity/qa.md @@ -0,0 +1,46 @@ +# QA plan + +## Unit and packaging tests + +- Render one and multiple resolved HTTP servers. +- Preserve empty headers and deliver secrets only through the runtime artifact. +- Reject unsupported connection and policy modes. +- Validate URLs and reserved names before materialization. +- Keep headers out of errors and logs. +- Preserve the existing MCP fingerprint behavior. +- Route Pi MCP requests to the adapter and leave non-Pi requests unchanged. +- Reuse the existing Pi approval path for remote tool execution and honor denial. +- Start the packaged extension with pinned Pi, list a fixture server, and call one tool. + +## Acceptance matrix + +| Environment | Credentials | Expected result | +| --- | --- | --- | +| Local | None | List server, discover tool, call tool | +| Local | Secret header | Call succeeds; secret absent from output and logs | +| Daytona | None | Same behavior as local | +| Daytona | Secret header | Same behavior; no durable secret file | + +Use the same revision and fixtures across environments. + +## Configuration change + +1. Start Pi with endpoint A and confirm A. +2. Edit the endpoint to B and send a normal turn. +3. Assert the session goes cold and reports B only. +4. Repeat with a header-secret reference change. + +Approval resume continues the exact parked process and must not rebuild configuration. + +## Failure cases + +- DNS failure identifies the server without headers. +- SSRF-blocked URLs fail before Pi starts. +- Missing project secrets fail in the service, as for Claude. +- Invalid adapter config fails the run instead of omitting MCP. +- Adapter startup failure does not fall back to an MCP-less run. + +## Completion evidence + +Include focused unit results, runner typecheck, package smoke output, and four live traces. Capture +one replay test after the first stable live run. diff --git a/docs/design/agent-workflows/projects/pi-mcp-parity/research.md b/docs/design/agent-workflows/projects/pi-mcp-parity/research.md new file mode 100644 index 0000000000..f595bb8827 --- /dev/null +++ b/docs/design/agent-workflows/projects/pi-mcp-parity/research.md @@ -0,0 +1,48 @@ +# Research + +## Versions + +The runner pins `@earendil-works/pi-coding-agent@0.80.6` and `pi-acp@0.0.29`, with a small Agenta +patch for skill and session paths. + +The latest published `pi-acp` checked on 2026-07-14 is `0.0.31`. Its README still says MCP +servers are stored from ACP parameters but not wired into Pi. Upgrading it alone does not provide +MCP and is not a prerequisite. + +The same README points to +[`pi-mcp-adapter`](https://github.com/nicobailon/pi-mcp-adapter). Latest checked release: +`2.11.0`. + +## Reused behavior + +The adapter already implements: + +- Streamable HTTP with SSE fallback; +- arbitrary HTTP headers; +- initialization, discovery, calls, and cleanup; +- argument validation and result conversion; +- one `mcp` proxy tool for status, search, describe, and call; +- optional cache-backed direct tools. + +It accepts standard configuration: + +```json +{ + "mcpServers": { + "exa": { + "url": "https://mcp.exa.ai/mcp", + "headers": {"x-api-key": "resolved-value"} + } + } +} +``` + +## Why proxy mode is first + +The proxy works on first start. Direct tools depend on disk metadata and become available after a +restart for a new server. Adding cache warm-up only to imitate Claude's presentation adds +complexity without adding MCP functionality. + +First prove the pinned adapter loads beside `agenta.js` under Pi `0.80.6`. Prefer the existing +bundled-asset path. If auxiliary files prevent one bundle, ship the pinned upstream package +directory. Do not replace it with a custom client. diff --git a/docs/design/agent-workflows/projects/pi-mcp-parity/status.md b/docs/design/agent-workflows/projects/pi-mcp-parity/status.md new file mode 100644 index 0000000000..2e41933c99 --- /dev/null +++ b/docs/design/agent-workflows/projects/pi-mcp-parity/status.md @@ -0,0 +1,36 @@ +# Status + +Last updated: 2026-07-14 + +## Stage + +Design ready for review. No implementation has started. + +## Confirmed + +- PRs #5296 and #5297 established secret resolution and the MCP UI. +- The runner fingerprint includes MCP servers. +- `pi-acp@0.0.29` does not forward ACP MCP entries. +- Latest checked `pi-acp@0.0.31` still documents that limitation. +- `pi-mcp-adapter@2.11.0` supports remote HTTP and headers. +- The runner has local and Daytona Pi extension-asset machinery. + +## Decisions + +- Reuse `pi-mcp-adapter`; do not build an MCP client. +- Keep service resolution and public config identical to Claude. +- Add only a Pi-private rendering at the harness boundary. +- Use the adapter proxy first. +- Keep secret-bearing config ephemeral. +- Add no gateway, OAuth, stdio, probing, flag, compatibility, or migration. +- Publish Pi capability only after local and Daytona acceptance. + +## Review focus + +1. Is the proxy acceptable for the first slice, with direct tools deferred? +2. Does the temporary config lifecycle match the secret boundary? +3. Is anything beyond HTTP plus current secret headers required? + +## Next action + +Approve the design, then implement slices 0 and 1 together.