- 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
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.
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
- 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.
- 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.
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
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
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
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
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
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
- 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.
- 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.")
- 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.
/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
- 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.
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.
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.
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.
- 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.
- 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.
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
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
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
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.
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
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
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
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.