Commit Graph
254 Commits
Author SHA1 Message Date
can1357 43fcfb4475 Merge PR #8688: fix(mcp): preserve image tool results (@roboomp) 2026-08-19 01:36:02 +02:00
roboomp d421944598 fix(mcp): preserved image tool results
- Forwarded MCP image blocks to the agent and TUI without copying their base64 payloads.

- Retained existing text and resource formatting around image blocks.

- Added regression coverage for mixed text and image tool results.

Fixes #8687
2026-08-16 00:52:19 +00:00
can1357 5102d3068f fix(mcp): parsed separate Exa tools argument 2026-08-16 02:13:38 +02:00
can1357 9af713f2b0 Merge PR #8576: fix(mcp): keep Exa MCP servers that request non-native tools (@dhruvkej9) 2026-08-16 02:13:38 +02:00
dhruv.kejriwal f36cf20d56 fix(mcp): keep Exa MCP servers that request non-native tools
Exa MCP servers were always filtered out because the native Exa integration covers web_search_exa. But configs that explicitly request web_fetch_exa or web_search_advanced_exa have no native equivalent, so filtering them made /mcp reconnect exa fail and hid those tools. Keep the MCP server mounted when its tools restriction includes anything beyond web_search_exa, while still extracting the API key for native search.
2026-08-15 00:39:58 +05:30
roboomp 83d08936ae fix(mcp): initialized sessions before opening sse stream
Moved the optional Streamable HTTP GET listener after the initialized notification so stateful servers do not terminate the session during setup.

Added regression coverage for a server that rejects pre-initialization GET traffic.

Fixes #8514
2026-08-14 05:33:38 +00:00
can1357 b279db1790 test: refactored test suites to eliminate time-based sleeps and polling loops
- Replaced time-based sleeps and polling loops with event-driven promise resolvers and fake timers across agent and tool tests.
- Migrated test suites to share in-memory auth storage and fixtures using lifecycle hooks.
- Updated catalog model definitions, metadata, and configurations.
2026-08-13 19:32:22 +02:00
can1357 3ab29d4059 fix(mcp): shorten config source before sanitizing 2026-08-13 01:14:52 +02:00
can1357 ecea726004 Merge PR #8316: fix(mcp): show config source in startup failures (@jeffscottward) 2026-08-13 01:14:52 +02:00
can1357 436135ac6f Merge PR #8124: fix(mcp): close stale initial connections (@roboomp) 2026-08-13 01:14:48 +02:00
Jeff Scott Ward 75a233a000 fix(mcp): show config source in startup failures 2026-08-12 01:26:28 -04:00
can1357 a4d8860a6c feat: added google reasoning controls mcp stream resumption and tar support
- Added Google provider thinking configuration parameters and force-reasoning-off controls.
- Implemented MCP SSE stream resumption using Last-Event-ID and `SSEResumeError`.
- Added support for TAR old-GNU sparse extension blocks, path length checks, and archive entry overrides.
- Restricted external thinking support to specific models and added semver fallback parsing.
2026-08-12 02:32:45 +02:00
roboomp eaa5f46d32 fix(mcp): resumed interrupted HTTP response streams
Implemented 2025-11-25 Streamable HTTP polling semantics for POST SSE responses: retain event IDs and retry intervals, wait as instructed, and reconnect with GET plus Last-Event-ID until the originating JSON-RPC response arrives.

Extended the shared SSE parser to expose valid id/retry fields and control-only events so reconnecting consumers do not need to reparse raw lines.

Fixes #8264
2026-08-11 18:21:12 +00:00
roboomp b65ac59653 fix(mcp): reserve MCP-Protocol-Version from configured headers
The header is transport-owned. Strip any user-configured MCP-Protocol-Version so it cannot leak onto the initialize request before negotiation, nor override the negotiated value afterwards.

Fixes #8264
2026-08-11 18:10:59 +00:00
roboomp 004821342c fix(mcp): defer MCP-Protocol-Version header until after negotiation
A server supporting only an older MCP revision may reject an initialize request that already carries a newer MCP-Protocol-Version header. The spec requires the header only on requests after initialize. Hold it back until the initialize response is negotiated (setProtocolVersion), then echo the negotiated value.

Fixes #8264
2026-08-11 18:04:39 +00:00
roboomp 10bce6900b fix(mcp): send MCP-Protocol-Version header and negotiate 2025-11-25
The Streamable HTTP transport never emitted the MCP-Protocol-Version header and the client pinned the stale 2025-03-26 revision. Spec-current servers (e.g. AWS Bedrock AgentCore Gateway with an outbound per-user OAuth target) discard the negotiated version and deny every tools/call with a generic internal error.

Bump the negotiated version to the current stable 2025-11-25 (MCP_PROTOCOL_VERSION) and echo the negotiated version in the MCP-Protocol-Version header at the transport's single #fetch choke point, so it rides GET/POST/DELETE requests uniformly.

Fixes #8264
2026-08-11 17:57:41 +00:00
Duncan Ogilvie 4d5ebd23f9 fix(mcp): replace deferred tool placeholders 2026-08-10 02:17:31 +02:00
roboomp 6d17c85323 fix(mcp): reject initial tools/list failure before close
Detached and dropped the connection synchronously, then closed the transport in the background, so a slow or hung close no longer keeps the tool-load promise pending past the startup race and no longer strands pending state that skips future connects.

Applied the same reject-fast teardown to the stale initial-connect and reconnect bailouts.

Fixes #8112
2026-08-09 23:55:17 +00:00
roboomp 5802aa78b4 fix(mcp): closed stale initial connections
Closed late initial connection results before callback wiring and removed unhealthy connections after initial tools/list failures.

Centralized identity-safe transport disposal so stale cleanup cannot delete a newer connection for the same server.

Fixes #8112
2026-08-09 23:47:51 +00:00
can1357 7cebe901b7 refactor(coding-agent): narrowed over-exported internal symbols
- 28 symbols across discovery, mcp header policy, agent-hub projection and
  rendering, the agent registry, shell tokenizing and changelog comparison
  were exported but referenced only inside their own module; they are now
  module-private, shrinking the deep-import surface.
- Kept AGENT_PLUGIN_MANIFEST_SCHEMA, AGENT_PLUGIN_MCP_SCHEMA,
  parseAgentPluginManifest, clearAgentPluginRootCache and mergeMCPHeaders
  exported: each is a seam for tests that defend real parsing or header
  precedence behavior.
- Nothing reachable from an explicit exports entry or public barrel changed.
2026-08-08 06:32:01 +02:00
can1357 b94bfba025 feat(mcp): enforced header precedence and origin policy on remote transports
- Client-generated HTTP/MCP/authorization headers win over configured
  headers case-insensitively (Agent Plugins §7.2.1) via the new
  header-policy fetch wrapper used by the HTTP and legacy SSE transports.
- headerPolicy: "origin-locked" pins configured headers to the configured
  URL's origin: never forwarded across cross-origin redirects, and
  method-changing redirects of JSON-RPC POSTs are refused.
- envPolicy: "literal" exempts stdio env values (and origin-locked
  headers) from config-value resolution: no ambient env-name lookup, no
  __omp_shell("command execution, empty values preserved.")
2026-08-07 05:59:36 +02:00
can1357 39bc9de52f style: applied biome import order and formatting 2026-08-03 15:26:20 +02:00
can1357 9fb082bf14 refactor(utils): consolidated file locking into pi-utils file-lock
- Moved the coding-agent lock-directory primitive to @oh-my-pi/pi-utils/file-lock
  and migrated settings, MCP config-writer, and security store imports.
- Replaced the stats aggregator's parallel ~200-line token/breaker lock protocol
  with the shared primitive: dead owners reclaimed immediately, live-but-wedged
  owners after STATS_SYNC_LOCK_STALE_MS, unstamped acquisitions after the new
  acquireStaleMs grace (10s).
- Shared primitive now treats EPERM kill probes as live owners.
- Rewrote the stats lock-reclamation regressions against the shared protocol
  and moved the file-lock contract test into pi-utils.
2026-08-03 15:25:03 +02:00
roboomp c6a057073b fix(mcp): expand env vars in reauth oauth credentials and reject empty tokens
/mcp reauth read OAuth clientId/clientSecret from the raw, unexpanded config
while URL and resource used expandEnvVarsDeep, so `${VAR}` placeholders were
sent literally to the token exchange. MCPOAuthFlow.exchangeToken() also accepted
any HTTP-success body, storing an empty access token when a provider signals
failure with HTTP 200 (e.g. Slack `{ ok: false, error }`), surfacing only later
as invalid_token.

- Select flow client credentials from runtimeBaseConfig / expanded auth block;
  keep the raw placeholder for the persisted config file.
- Reject token responses without a non-empty access_token, including the
  sanitized provider error when present.
- Add regression tests for env-expanded reauth credentials and HTTP-200 token
  error bodies.

Fixes #7440
2026-08-03 00:33:36 +00:00
can1357 13a36f7c83 refactor(coding-agent/mcp): changed default MCP request ID format to sequential integers
- Change the default MCP JSON-RPC request ID format from snowflake strings to sequential integers.
- Update server configuration schema, connection equivalence checks, and tests to reflect the new integer default.
2026-08-01 20:33:11 +02:00
Jérémy Marchand 4476940a4a docs(mcp): document requestIdFormat as OMP-specific
The option only exists in OMP's own config format, so the OMP-owned discovery
providers are the only ones that parse it. Say so in the schema description, the
MCPServerConfigBase doc, and the changelog, and name the config paths where
setting it actually takes effect, so nobody expects a server imported from
another tool's config to honor it.
2026-07-31 21:04:02 +02:00
Jérémy Marchand 3cb6e5df9c fix(mcp): carry requestIdFormat through MCP config discovery
The option was only present on the transport-facing `MCPServerConfig`, so a
value written in `.omp/mcp.json` or a standalone `.mcp.json` never reached the
transports: discovery normalizes config into the canonical `MCPServer` shape and
`convertToLegacyConfig()` rebuilds the transport config from it, and neither step
knew about the field. Setting `"number"` in the documented config path silently
kept the snowflake-string default, which is the hang the option exists to avoid.

Wire it through the same four places `timeout` already uses: the canonical
`MCPServer` shape, the two OMP-native loaders (with validate-and-warn on an
unrecognized value), and the legacy conversion. Foreign-format providers are
untouched, since the key is OMP-specific.

Also point the `MCPServerConfigBase` doc comment at `RequestIdAllocator` rather
than a helper name that never existed.
2026-07-31 21:03:06 +02:00
Jérémy Marchand 8560188314 feat(mcp): let a server opt into integer JSON-RPC request ids
Apple's `xcrun mcpbridge` decodes JSON-RPC `id` as an integer only. OMP
mints collision-resistant snowflake strings, so the bridge logs
`mcpbridge.DecodeError Code=1`, never replies, and every request hangs
until it times out (#7053). JSON-RPC 2.0 permits String and Number ids
equally, so both shapes are legal and the string default stays.

Add `requestIdFormat: "string" | "number"` to the shared server config
and honor it in all three transports through one allocator. The string
default is unchanged, so this is inert unless a server opts in.

Verified against Xcode 26.3's bridge: with `"number"`, `initialize`
succeeds and `tools/list` returns all 21 tools; with the default, the
same request times out.
2026-07-31 21:03:05 +02:00
can1357 857b70fe99 Merge remote-tracking branch 'refs/remotes/pr/6535' into prep/6535
# Conflicts:
#	packages/coding-agent/src/extensibility/extensions/runner.ts
2026-07-30 01:42:24 +02:00
can1357 d16a251777 chore: reorg tests 2026-07-27 16:43:53 +02:00
can1357 137bad2c8b style: applied biome formatting to review follow-up changes 2026-07-27 16:17:04 +02:00
can1357 41ce810cff fix(mcp): preserved native resource URIs and opaque scheme routing
- Native (non-mcp://) resource URIs now pass through byte-for-byte via
  rawHref; slash elision applies only to the legacy mcp:// wrapper, so
  catalog://root/ style URIs match exact-equality server lookups.
- resources/templates/list failure no longer discards a successful
  resources/list (Promise.allSettled; templates retried later).
- Opaque RFC 3986 URIs (urn:doc, custom:item) are recognized by both
  the router and read-cli discovery gates, with drive-path and
  read-selector false positives guarded.
- Review follow-up for PR #6790.
2026-07-27 16:15:02 +02:00
can1357 9448aac3a6 fix(mcp): kept disable precedence and stable tool collision winners
- Added a suppress load option so disabled servers still claim their
  capability key: a project foo with enabled:false shadows a same-named
  enabled user foo again, while scope-removed entries drop fully.
- Tool-name collisions now resolve by stable server+tool origin key
  instead of manager array order, so reconnect re-appends cannot flip
  the routed implementation.
- Review follow-up for PR #6787.
2026-07-27 16:07:23 +02:00
can1357 b0063dd180 Merge PR #6787: fix(mcp): deduplicate aliased server connections (@roboomp) 2026-07-27 15:57:44 +02:00
Dongmen Laohu 2c77c8535a fix(mcp): resolve native resource URIs 2026-07-27 18:59:12 +08:00
roboomp da11d906ff fix(mcp): unified tool collision handling
Moved first-wins MCP tool-name deduplication and origin-aware warnings into one shared helper used by startup extension registration, SDK custom-tool assembly, and deferred refreshes.

Added an SDK startup regression proving colliding MCP proxy tools keep the first origin instead of silently overwriting it.

Fixes #6786
2026-07-27 10:49:59 +00:00
roboomp 6c96a5ee9f fix(mcp): filter disabled servers before dedup
Applied the denylist and per-server enabled:false exclusions before connection-equivalence deduplication, alongside project scope, so a disabled higher-priority server can no longer shadow a differently-named equivalent enabled server and leave no connection. Parameterized LoadOptions<T> so the pre-dedup filter sees the typed item.

Fixes #6786
2026-07-27 10:37:52 +00:00
roboomp 394eaaeae2 fix(mcp): filter project scope before dedup
Applied the project-scope filter before connection-equivalence deduplication so a project server can no longer shadow a differently-named but equivalent user server and then be dropped, leaving none.

Fixes #6786
2026-07-27 10:31:18 +00:00
Anthony "Asterisk" Ambuehl 29625f08c2 feat(mcp): add mcp_notification extension event + multi-listener API
Convert MCPManager's dangling single-slot setOnNotification callback into
a multi-listener API and expose server-initiated MCP notifications as an
extension event so extensions can bridge push-capable MCP servers (e.g.
peer messaging, ticket nudges) into session behavior.

API changes:
- Removed: MCPManager.setOnNotification(handler) — single-slot, zero callers
- Added:   MCPManager.addNotificationListener(listener): () => void
           Multi-listener with per-listener error isolation, returns unsub.
- Added:   'mcp_notification' extension event
           Payload: { server: string; method: string; params: unknown }

Wired in sdk.ts: one listener bridges to extensionRunner.emitMcpNotification,
captured under postmortem for teardown.

Tests: 3 new (multi-listener fanout, error isolation, unsubscribe),
fixture pattern matches neighboring mcp tests. bun check passes (biome +
tsgo).

Docs: extensions.md (new MCP notifications subsection with bridging
example), mcp-runtime-lifecycle.md (Server-initiated notifications
section), CHANGELOG.
2026-07-24 13:36:03 -07:00
can1357 e8502806ff fix(mcp): retry smithery poll timeouts until the authorization deadline
A single hung/slow poll now aborts with TimeoutError after 30s; without
this, that one timeout escaped #waitForSmitheryCliApiKey and dropped the
browser login to the manual API-key fallback instead of retrying until
the 5-minute deadline. Catch isTimeoutError in the poll loop and
continue; export SmitheryCliPollResponse to type the retried response.
2026-07-23 22:15:21 +02:00
can1357 db2601eaff Merge PR #6425: fix(mcp): bound oauth discovery and smithery poll fetches with abort timeouts (@roboomp) 2026-07-23 22:15:21 +02:00
roboomp af3883c052 fix(mcp): bound oauth discovery and smithery poll fetches with abort timeouts
MCP OAuth endpoint discovery ran metadata, well-known, and recursive
authorization-server fetches with no AbortSignal, and the Smithery
browser-login poll received a never-aborting signal. An endpoint that
accepts the TCP connection but never responds stalled /mcp add,
/mcp reauth, the add wizard, or /mcp smithery-login indefinitely; the
5-minute login/poll deadlines run only after discovery resolves or
between polls, so a hung fetch never reached them.

- discoverOAuthEndpoints and fetchResourceMetadataScopes gain an optional
  signal and wrap every fetch in withTimeoutSignal(DISCOVERY_FETCH_TIMEOUT_MS,
  opts?.signal), threaded through the recursive authorization_servers call.
- pollSmitheryCliAuthSession wraps its fetch in
  withTimeoutSignal(SMITHERY_POLL_TIMEOUT_MS, signal) so a hung poll aborts
  and the loop reaches its 5-minute deadline.

Fixes #4103
2026-07-23 19:45:41 +00:00
roboomp 15577406f8 fix(mcp): serialize mcp.json config writes and use unique temp path
Every exported read-modify-write on mcp.json (add/update/remove server, disabled/force-enabled lists) now runs under a per-file withFileLock, so overlapping in-process or cross-process mutations no longer lose updates. writeMCPConfigFile writes to a pid+uuid temp file instead of a shared ${filePath}.tmp, so concurrent writers cannot rename each other's temp out from under them (ENOENT / clobber).

Fixes #4104
2026-07-23 19:40:13 +00:00
can1357 80ec1cb6ae Merge PR #6347: feat: optionally render MCP results as Markdown (@zeroknots) 2026-07-23 11:53:05 +02:00
zeroknots dd510f2ccc feat(coding-agent): render MCP Markdown results 2026-07-23 13:26:00 +07:00
slee1996 2145ab8f8e fix(coding-agent): retry MCP tool auth challenges 2026-07-22 23:44:38 -06:00
roboomp 04179bf0b8 fix(mcp): route task proxies through source tool and mark tools non-strict
MCP-backed tools never declared an explicit strict value, so OpenAI-family
serializers (post-#4336/#4340) had no false to preserve and models over-filled
mutually exclusive optional fields. Task/subagent proxies also rebuilt a raw
tools/call instead of executing through the source MCPTool, bypassing intent
stripping, placeholder pruning, local-URL resolution, reconnect, abort, and
result metadata; strict servers rejected proxied calls with
unrecognized_keys ["i"].

- MCPTool/DeferredMCPTool now declare `readonly strict = false as const`.
- createMCPProxyTools delegates to the current source tool, re-resolved by raw
  MCP server/tool metadata so reconnect replacements are honored, and keeps the
  Task 60s timeout by combining its abort signal with the caller's.
- Regression coverage: strict flags, proxy parity for i/placeholder shaping,
  declared-i passthrough, and reconnect re-resolution.

Fixes #6208
2026-07-21 22:37:12 +00:00
Mathews-Tom f0d31bcf56 chore: merge upstream/main (mechanical, merge-tree verified clean) 2026-07-18 04:29:23 +05:30
can1357 357d683007 Merge PR #5894: fix(mcp): process-group kill and SIGKILL escalate on stdio transport close (@Mathews-Tom) 2026-07-17 21:22:06 +02:00
Mathews-Tom 22f3701d92 test(mcp): distinguish zombie grandchildren from live ones in kill checks
kill(pid, 0) succeeds for a zombie too: a grandchild whose parent (the
killed leader) is gone sits as <defunct> until whatever reaps orphans
gets around to it, which can lag on some hosts. processExists() now
reads the process's ps state and treats a zombie as already reaped
instead of still alive, so the group-kill regression tests assert what
they actually claim to test.
2026-07-18 00:47:14 +05:30