Commit Graph
61 Commits
Author SHA1 Message Date
can1357 4f7b61ab62 feat(coding-agent): added MCP server auto-reconnect with SSE monitoring
- Added auto-reconnect capability for MCP servers with SSE stream monitoring and exponential retry backoff.
- Added tool-level reconnect handling for retriable connection errors (ECONNREFUSED, ECONNRESET, 404/502/503).
- Added `/mcp reconnect <name>` command for manual MCP server recovery.
- Improved reconnect robustness by aborting retries when MCP configuration changes via epoch checking.
- Extended transport reconnect handling to all transport types (stdio, HTTP/SSE) with unified onClose logic.
- Added comprehensive test coverage for MCPManager reconnect behavior and tool-level abort propagation.
2026-03-20 23:48:38 +01:00
can1357 9a37cc8096 refactor(coding-agent/mcp): restructured tool-bridge abort handling and argument normalization
- Replaced custom withAbort() implementation with untilAborted() utility from pi-utils for consistent abort handling.
- Extracted normalizeToolArgs() helper to consolidate argument normalization logic across renderCall(), renderResult(), and execute() methods.
- Added MCPToolCallParams type import to improve type safety for tool parameter handling.
2026-03-20 23:42:02 +01:00
can1357 51228e9811 refactor(coding-agent): restructured MCP reconnection tracking and error handling
- Refactored MCP manager reconnection logic to track configuration changes via reconnectEpoch parameter.
- Consolidated error pattern matching in tool-bridge to use lowercase normalization for consistent comparison.
- Extracted retry provider variables to eliminate repeated ternary expressions in error handling.
- Reformatted code across multiple files for improved readability with consistent multi-line formatting.
- Added test coverage for transport reconnection scenarios including connection reuse after reconnection.
2026-03-20 23:32:07 +01:00
8b028224e3 feat: auto-reconnect MCP servers on connection loss (#482)
* feat: auto-reconnect MCP servers on connection loss

When an HTTP SSE stream drops (server restart, network interruption),
the transport fires onClose, and the manager proactively reconnects
with retry backoff (500ms, 1s, 2s, 4s). Tools are kept in the registry
during reconnection so they remain selected and available to the agent.

If proactive reconnection fails, stale tools stay registered. When the
agent calls one, the tool bridge detects the retriable connection error
(ECONNREFUSED, ECONNRESET, stale session 404/502/503, etc.), triggers
reconnectServer on the manager, and retries the call once on the fresh
connection. Concurrent reconnect attempts for the same server are deduped.

connectToServer now always installs a default onRequest handler for ping
and roots/list (using getProjectDir()), so all connections -- including
short-lived test/probe ones -- properly respond to server-initiated
requests during initialization.

Post-connection setup (resources, prompts, subscriptions) is extracted
into a shared #loadServerResourcesAndPrompts method used by both initial
connection and reconnection paths.

Add /mcp reconnect <name> command for manual recovery after extended
outages where both proactive and reactive reconnection have failed.

* docs: add changelog entry for MCP auto-reconnect

* fix: address P1 review findings in MCP reconnection

- Save server configs before connection attempt so deferred tools can
  reconnect even when the initial connection timed out (P1-1)
- Make waitForConnection() and getConnectionStatus() aware of in-flight
  reconnections so callers wait instead of failing immediately (P1-2)
- Add epoch counter incremented on disconnectAll() and checked in
  connectAndWireServer() to invalidate stale reconnect attempts that
  outlive a manager reset/reload (P1-3)
- Skip servers with pending reconnections in connectServers() to prevent
  parallel connection attempts for the same server

* fix: deferred tool reconnect and non-blocking transport teardown

- DeferredMCPTool.execute now reconnects when getConnection() fails
  ("MCP server not connected"), not only on network errors from
  callTool. Servers that missed the startup window can now be woken
  by the first tool call against their cached tools. (P1-4)
- #doReconnect fire-and-forgets the old transport close instead of
  awaiting it. HttpTransport.close() sends a DELETE with 30s timeout;
  blocking here delayed the first reconnect attempt by that amount
  on every server restart. (P1-5)

* fix: abort-aware reconnect waits and preserve tool selection on reconnect

- Wrap all reconnect() awaits with withAbort(signal) so user
  cancellation (Esc) interrupts the reconnect backoff loop instead
  of blocking for up to 7.5s. Applies to MCPTool (1 site) and
  DeferredMCPTool (2 sites). (P2-1)
- Remove activateDiscoveredMCPTools call from /mcp reconnect handler.
  refreshMCPTools already preserves the user's prior MCP tool
  selection; the extra activation was silently opting into all
  server tools including ones the user had not enabled. (P2-2)

* fix: rebind MCPTool connection after reconnect, add stdio retriable error

- MCPTool.connection is now mutable; after a successful reconnect retry,
  this.connection is rebound to the fresh connection so subsequent calls
  on the same instance (e.g. batched tool calls) use it instead of
  triggering another reconnect cycle. (P2-3)
- Add "Transport closed" to RETRIABLE_PATTERNS. StdioTransport rejects
  pending requests with this message when the subprocess dies, which
  should trigger the reconnect path just like HTTP transport errors. (P2-4)

---------

Co-authored-by: Miroslav Drbal <miroslav.drbal@gendigital.com>
Co-authored-by: Can Bölük <can1357@users.noreply.github.com>
2026-03-20 23:27:30 +01:00
can1357 51fb0ade87 feat: add discoveryDefaultServers config for MCP discovery mode
fixes #470
2026-03-18 23:05:33 +01:00
21a86a693d feat(mcp): implement roots/list and server-to-client request handling (#474)
* feat(mcp): implement roots/list and server-to-client request handling

Add support for MCP server-to-client JSON-RPC requests across both
stdio and HTTP transports, enabling servers to query client capabilities
such as roots/list during initialization.

Transport layer (types.ts, stdio.ts, http.ts):
- Add onRequest callback to MCPTransport interface for server-initiated
  requests; add toJsonRpcError helper for error code propagation
- Classify incoming messages by checking method+id (request), id-only
  (response), method-only (notification); guard against id:null per
  JSON-RPC 2.0 spec
- StdioTransport: detect server requests in #handleMessage, respond via
  #sendResponse writing JSON-RPC response to subprocess stdin
- HttpTransport: detect server requests via #dispatchSSEMessage across
  all SSE streams (dedicated listener, POST response drain, notify
  piggybacking), respond via #sendServerResponse POST with proper
  Accept header and session ID
- Refactor startSSEListener to resolve once SSE GET connects (not when
  stream ends), enabling await before notifications/initialized; reset
  #sseConnection via .finally() for reconnection after transient failure
- #parseSSEResponse continues reading after capturing the primary
  response to drain piggybacked server requests/notifications; clears
  timeout after capture so drain phase is unbounded
- notify() reads text/event-stream response bodies for piggybacked
  messages; cancels non-SSE response bodies to release connections
- #sendServerResponse includes AbortSignal.timeout and cancels response
  body; fire-and-forget handlers wrapped in try/catch to prevent
  unhandled rejections

Client wiring (client.ts):
- Add onRequest to connectToServer options, wire to transport before
  initialization
- Add awaitable onInitialized hook in initializeConnection, called
  between initialize response (which sets session ID) and initialized
  notification, so SSE stream is open when server sends roots/list
- Pass only signal to transport.request (not full options object)
- Hoist transport ref to outer scope; close on timeout/abort to prevent
  orphaned transports when SSE GET hangs

Manager (manager.ts):
- Wire onRequest handler in connectServers for all MCP connections
- Handle roots/list by returning project CWD as file:// URI via
  pathToFileURL; return -32601 for unsupported methods

Tests (mcp-roots-list.test.ts):
- toJsonRpcError: code extraction, defaults, non-Error values
- Message classification spec tests: request/response/notification/
  unknown dispatch, id:null and id:0 edge cases
- Roots response shape: file:// URI generation, Windows paths, spaces

* fix(mcp): return SSE response immediately instead of blocking on stream drain

The #parseSSEResponse loop continued iterating the SSE stream after
capturing the response for the expected request ID. Since clearTimeout
was called after capture, a server that holds the SSE stream open for
follow-up events (permitted by Streamable HTTP) would block the
request() call indefinitely.

Return the result as soon as it's captured and drain remaining
messages in a detached background task via #readSSEStream, which
already handles dispatch and error swallowing.

* fix(mcp): handle batched JSON-RPC messages in both transports

JSON-RPC 2.0 section 6 allows sending an array of request/notification
objects as a batch. If a server sent a batch, the message classifier
in both transports would fail the 'method in message' check on the
array object and silently drop all contained messages.

Add an Array.isArray guard at the top of #handleMessage (stdio) and
#dispatchSSEMessage (http) that recurses into each element. Defensive
measure — no known MCP server sends batches today, but the guard is
cheap and correct per the JSON-RPC spec.

* fix(mcp): address second Codex review round

- http: break from SSE loop before starting background drain to avoid
  ReadableStream locked error (the for-await iterator still holds the
  reader when #drainSSEBackground was called inline)
- types: toJsonRpcError now accepts plain { code, message } objects,
  not just Error instances, so onRequest handlers can throw structured
  JSON-RPC errors without wrapping in Error
- test: relax Windows path name assertion to toBeTruthy since
  path.basename is platform-dependent for backslash paths; add tests
  for plain-object toJsonRpcError

* fix(mcp): address third Codex review round

- parseSSEResponse: flatten JSON-RPC batch arrays before checking for
  the expected response, so a server that batches the primary response
  with piggybacked requests/notifications in a single SSE event still
  has the response extracted correctly
- sendServerResponse: retry once on 401/403 via onAuthError, matching
  the auth-refresh logic in #executeRequest; prevents server-initiated
  request replies from failing after token expiry on long-lived SSE
  sessions

---------

Co-authored-by: Miroslav Drbal <miroslav.drbal@gendigital.com>
2026-03-18 23:02:22 +01:00
can1357 66c80fd613 feat: add MCP JSON schema and documentation
fixes #462
2026-03-18 22:43:59 +01:00
can1357 afc2276a12 refactor(ai): extracted OpenAI compat logic into dedicated module
- Extracted OpenAI compatibility detection and resolution logic into dedicated `openai-completions-compat` module.
- Refactored `detectCompat()` and `getCompat()` to delegate to new compat module functions with simplified conditional logic.
- Fixed OAuth redirect URI validation to preserve exact configured values without trailing slash normalization.
- Improved session deletion to return boolean status and display error messages in UI instead of silently failing.
- Added `/session delete` command with Delete key support and confirmation dialogs for session management.
2026-03-17 16:07:26 +01:00
571510bdf3 fix: MCP OAuth exact redirect URIs for Slack-style providers (#454)
* Add exact MCP OAuth redirect URI support

* Allow proxied HTTPS loopback MCP redirects

* Fix MCP OAuth busy-port test determinism

* Make MCP OAuth redirect test deterministic

* fix(coding-agent): honor exact loopback redirect ports

* fix(coding-agent): expand env vars in standalone MCP oauth config

---------

Co-authored-by: can1357 <me@can.ac>
2026-03-17 14:49:23 +01:00
94ad651d17 feat: add MCP tool discovery search (#352)
* Add MCP tool discovery search and live refresh

* Fix MCP discovery review feedback

* Address remaining MCP discovery review comments

* feat: compact MCP discovery search results

* fix: align MCP discovery search contract

* feat: add MCP server tool counts to discovery hints

* fix(agent): corrected stale toolChoice validation against active tools

- Fixed stale forced toolChoice passed to provider after mid-turn tool refresh by validating against active tools.
- Added refreshToolChoiceForActiveTools() to filter invalid tool choices when available tools change.
- Changed getToolChoice config to use computed function instead of static property for dynamic validation.
- Fixed MCP tool selection tracking in coding-agent to distinguish between discovery-enabled and non-discovery sessions.
- Updated search_tool_bm25 to filter already-selected tools before applying limit parameter.

---------

Co-authored-by: can1357 <me@can.ac>
2026-03-16 13:43:43 +01:00
can1357 7818c5c316 feat(coding-agent): enabled interactive input submission and fixed continue path state handling
- Exported `submitInteractiveInput()` function for programmatic submission of user input in interactive mode.
- Fixed continue special path to skip optimistic submission state check for already-started prompts.
- Added 2 test cases covering continue submission behavior and optimistic state cancellation.
2026-03-10 20:15:03 +01:00
can1357 bcfbc710de fix(mcp): force OAuth token refresh on 401/403 auth errors
#resolveAuthConfig only refreshed tokens within the 5-minute pre-expiry
window, so the onAuthError retry path reused stale credentials when
tokens were revoked, clocks skewed, or expires was missing. Add a
forceRefresh parameter and pass true from the auth error handler so
401/403 always triggers an unconditional refresh attempt.
2026-03-10 18:40:18 +01:00
Kevin LoftisandGitHub 6f02d42eb4 feat: add MCP OAuth token refresh (#359)
* Add OAuth token refresh for MCP connections

Proactive refresh with 5-minute buffer before token expiry, plus
retry on 401/403 with automatic token refresh for HTTP transports.
Persist tokenUrl, clientId, and clientSecret in auth config so
refresh can happen without re-prompting the user.

* docs(coding-agent): updated CHANGELOG for MCP OAuth token refresh
2026-03-10 18:31:01 +01:00
can1357 6357b245a5 fix: corrected resource tracking and context cleanup across MCP and virtualization layers
- Fixed resource refresh tracking by storing connection references alongside promises to prevent stale deduplication.
- Fixed update target resolution to explicitly handle missing ompPath and use path.resolve() for consistent normalization.
- Added error handling and logging in Smithery registry detail fetching to gracefully handle failures and track issues.
- Fixed virtualization context cleanup in error paths to prevent partially-started instances from remaining active.
- Fixed API key retrieval to use dynamic provider configuration instead of hardcoded provider string.
- Enhanced test utilities to capture and verify request parameters for improved test coverage and debugging.
2026-03-03 06:07:22 +01:00
c22ba6b49c feat: implemented Smithery Registry for MCP search (#96)
* Implemented Smithery MCP Searchable Registry

* refactor(coding-agent): consolidated MCP registry search and improve error handling

- Extracted `parseCommandArgs` and `stripControlChars` utilities to reduce duplication in MCP command controller. Replaced custom URL opening logic with centralized `openPath` utility across OAuth and registry flows.
- Enhanced Smithery auth error handling to gracefully degrade on file read failures with logging instead of throwing, and improved chmod error reporting. Normalized Smithery API base URL to strip trailing slashes.
- Improved registry search pagination to fetch multiple pages until sufficient results are found, with semantic mode support to preserve API relevance ranking. Deduplicated entries by identity key and applied local sorting only in non-semantic mode.

---------

Co-authored-by: can1357 <me@can.ac>
2026-03-03 04:48:23 +01:00
can1357 f6553a00ff fix(coding-agent): guard stale MCP subscription post-actions 2026-03-03 04:00:30 +01:00
can1357 7b4ab548c0 fix: corrected URI template expansion and MCP notification epoch handling
- Fixed URI template matching to handle empty string expansions in MCP resource queries.
- Fixed LM Studio URL validation to preserve invalid baseUrl instead of applying localhost fallback.
- Fixed MCP notification epoch handling to prevent unsubscribe calls when old subscriptions resolve after re-enabling.
- Extracted hardcoded LM Studio base URL to named constant for improved maintainability.
- Refactored notification epoch check logic for improved code clarity and readability.
2026-03-03 03:56:30 +01:00
can1357 019c593782 fix: resolve merged regression set across coding-agent and ai
- Normalize LM Studio discovery URLs to avoid duplicated /v1 segments
- Harden status-line PR cache with branch+repo context validation and guarded async writes
- Apply Foundry auth precedence correctly and preserve system trust roots when custom CA is provided
- Split Copilot premium multiplier handling by plan tier while preserving agent-initiated zero billing
- Catch MCP notification refresh failures and route read_resource via deterministic full template matching
- Add regression tests for each fix cluster and keep targeted suites green
2026-03-03 03:39:19 +01:00
63b203b937 feat(mcp): resource notifications, subscriptions, and read_resource builtin tool (#254)
* feat(mcp): resource notifications, subscriptions, and read_resource builtin tool

- Add MCP resource subscription lifecycle (subscribe/unsubscribe on connect/disconnect)
- Wire mcp.notifications setting with live toggle support
- Add debounced followUp injection for resource change notifications
- Add global read_resource builtin tool with server resolution by URI/template scheme
- Add MCP prompt commands (buildMCPPromptCommands) with array content support
- Add server instructions injection into system prompt with attribution
- Add mcp.notificationDebounceMs configurable setting

Client (client.ts):
  listResources, listResourceTemplates, readResource with pagination
  subscribeToResources, unsubscribeFromResources
  listPrompts, getPrompt, serverSupportsPrompts
  serverSupportsResources, serverSupportsResourceSubscriptions

Manager (manager.ts):
  Notification dispatch with subscribed-URI guard
  Concurrent refresh deduplication via pending promise map
  setNotificationsEnabled with subscribe/unsubscribe toggle

Tests:
  client-resources.test.ts (31 tests)
  client-prompts.test.ts (20 tests)
  mcp-read-resource.test.ts (13 tests)

* fix(mcp): address PR review - eager prompt init and stale subscription cleanup

P1: Make setOnPromptsChanged eagerly fire for servers that already
have prompts loaded. The callback is registered after MCP discovery
has already loaded prompts and fired the hook, so without this the
handler is never called on the common startup path. The fix is in
the manager itself (not the caller), eliminating the race condition
regardless of when the callback is wired.

P2: Unsubscribe removed resource URIs on resource refresh.
refreshServerResources was subscribing to the new URI set and
overwriting #subscribedResources without unsubscribing URIs that
were previously subscribed but no longer present, leaving stale
subscriptions active on the server.

* fix(mcp): add resources and prompts to /mcp help text and subcommand completions

* feat(mcp): add /mcp notifications command

Shows per-server notification capabilities with subscription state:
- Lists supported notification types (tools/list_changed, resources/list_changed,
  prompts/list_changed) with check marks
- Shows resources/subscribe status with active subscription count
- Lists subscribed URIs with green ticks when notifications are enabled
- Displays overall enabled/disabled state (mcp.notifications setting)

* fix(mcp): address PR review comments on race conditions and stale state

- Await subscribe/unsubscribe in refreshServerResources so the refresh
  promise doesn't resolve before subscriptions are settled, preventing
  a second refresh from racing and overwriting tracking state (P2 #3)

- Guard setNotificationsEnabled subscribe .then() against a disable
  that happens while the subscribe request is in-flight (P2 #5)

- Re-check mcp.notifications setting inside debounce setTimeout
  callback so toggling off mid-window actually suppresses the
  follow-up message (P2 #4)

- Fire onToolsChanged and onPromptsChanged callbacks in
  disconnectServer so stale slash commands and tool registrations
  are cleaned up when a server is removed (P2 #2)

---------

Co-authored-by: Miroslav Drbal <miroslav.drbal@gendigital.com>
2026-03-03 03:26:07 +01:00
can1357 9af71f5f61 feat(oauth-flow): enabled Codex identity for OAuth client registration
- Updated OAuth client name from 'oh-my-pi MCP' to 'Codex' for dynamic client registration.
- Added test coverage for OAuth client registration with Codex identity.
2026-03-01 20:51:51 +01:00
can1357 f1a63a5ed1 refactor(coding-agent/cli): restructured agents unpack to support user and project directories
- Replaced `getConfigDirs()` with `getAgentDir()` to resolve user agent directory at `~/.omp/agent/agents` by default.
- Added `--user` and `--project` flags to control target directory, with `--project` exporting to `./.omp/agents`.
- Updated help text and examples to document the new directory resolution behavior.
2026-03-01 15:51:11 +01:00
can1357 2551dc04a2 feat(mcp): added MCP OAuth server metadata discovery with header parsing
- Added `authServerUrl` field to `AuthDetectionResult` to capture MCP OAuth server metadata.
- Added `extractMcpAuthServerUrl()` function to parse and validate `Mcp-Auth-Server` header URLs from OAuth errors.
- Enhanced `discoverOAuthEndpoints()` to accept optional `authServerUrl` parameter and query `/.well-known/oauth-protected-resource` endpoint.
- Improved OAuth metadata extraction to handle multiple `clientId` field variations (`clientId`, `default_client_id`, `public_client_id`).
- Extracted metadata parsing logic into reusable `findEndpoints()` helper function supporting multiple OAuth metadata formats.
- Added comprehensive test coverage for OAuth endpoint discovery, header parsing, and error validation.

Fixes #235
2026-03-01 15:26:52 +01:00
can1357 85c5fad2bc refactor: replace named re-exports with star re-exports in barrel files
Convert export { A, B, ... } from and export type { ... } from blocks
to export * from across all packages (ai, coding-agent, natives, tui, utils).
2026-02-28 22:34:02 +01:00
can1357 cba80c79c3 fix(schema): harden all provider schema normalizers with cycle detection, fixpoint iteration, and correctness fixes
## New: schema compatibility validation API

Add `validateSchemaCompatibility(schema, provider)` in
`packages/ai/src/utils/schema/compatibility.ts` that performs a static
audit of a JSON Schema against three provider targets:

- `openai-strict`: checks forbidden keys, required/properties symmetry,
  additionalProperties constraint, and that every node declares a type,
  combinator, or $ref
- `google`: checks unsupported keyword set and array-valued type
- `cloud-code-assist-claude`: checks forbidden keywords, array type,
  null type, nullable keyword, and combiner presence; also validates via
  AJV 2020 draft

Add `validateStrictSchemaEnforcement(original, result)` to assert the
fail-open contract: when strict enforcement succeeds the output must pass
openai-strict validation; when it fails the output must be the original
schema object (same reference).

Export both functions and their types from `./utils/schema/index.ts`.

## New: shared constants in fields.ts

Extract `COMBINATOR_KEYS` (`anyOf`, `allOf`, `oneOf`) and add
`CCA_UNSUPPORTED_SCHEMA_FIELDS` as exported constants, eliminating the
local duplicate in `strict-mode.ts` and providing a canonical field set
for Cloud Code Assist (much narrower than the Google set — CCA supports
validation keywords like `additionalProperties`, `minLength`,
`pattern`, etc.).

## Fix: cycle detection in all recursive schema traversals

All recursive walkers now carry a `WeakSet<object>` guard. Previously any
schema with a reference cycle (or a schema object that appears at two
nodes in the tree) would cause an infinite loop or a stack overflow:

- `sanitizeSchemaForStrictMode` / `enforceStrictSchema`
- `normalizeSchemaForCloudCodeAssistClaude`
- `normalizeNullablePropertiesForCloudCodeAssist`
- `stripResidualCombiners`
- `sanitizeSchemaImpl` (Google sanitizer)
- `hasResidualCloudCodeAssistIncompatibilities`

`hasResidualCloudCodeAssistIncompatibilities` previously returned `true`
for already-visited nodes, producing false positives that forced the CCA
fallback schema on valid (but multiply-referenced) schemas. It now
correctly returns `false`.

## Fix: stripResidualCombiners iterates to fixpoint

The previous single-pass approach missed chained combiner reductions
where one collapsed variant exposed another reducible combiner. The
rewriter now loops until no further reduction occurs.

## Fix: mergeObjectCombinerVariants required-field computation

The merged object schema now takes the intersection of all variants'
`required` arrays, then unions in own-level required properties that
exist in the merged schema. Previously the `required` field was silently
dropped from the flattened schema, making all properties effectively
optional.

## Fix: sanitizeSchemaForGoogle improvements

- Type inference for const-collapsed enums: type is derived from all
  variants (must unanimously agree), falling back to inference from enum
  values; mixed null/non-null infers the non-null scalar type and sets
  `nullable: true`
- Const→enum deduplication now uses deep structural equality instead of
  `Object.is`
- Recursion spreads the full options object so new fields (`unsupportedFields`,
  `seen`) are not silently dropped when descending into sub-schemas
- Array-valued `type` is filtered to strings before processing
- Removed incorrect stripping of `additionalProperties: false` (the
  field is valid and should be preserved)
- Parameterized `unsupportedFields` in `SanitizeSchemaOptions` enables
  code reuse between the Google and CCA sanitizers

## Fix: sanitizeSchemaForStrictMode / enforceStrictSchema

- `nullable: true` is now stripped during sanitization and expanded into
  `anyOf: [schema, {type: "null"}]` in the enforcer output, matching
  what OpenAI strict mode requires
- Type inference: `type: "array"` is inferred when `items` is present;
  a scalar type is inferred from uniform `enum` values
- Const→enum merge uses deep equality to avoid duplicate entries when
  both `const` and `enum` exist with the same value
- `additionalProperties` is now dropped unconditionally in sanitization
  (previously only object-valued `additionalProperties` was recursed;
  non-object values were passed through)
- `enforceStrictSchema` recurses into `$defs` and `definitions` blocks
- `enforceStrictSchema` handles tuple-style `items` arrays
- `enforceStrictSchema` skips double-wrapping: optional properties
  already expressed as `anyOf: [..., {type: "null"}]` are not wrapped again
- `tryEnforceStrictSchema` now caches results in a `WeakMap` keyed on
  the input schema object to avoid redundant work on repeated calls

## Fix: mergeCompatibleEnumSchemas deep equality

Uses `areJsonValuesEqual` instead of `Object.is` when deduplicating
enum members, so structurally equal objects are not duplicated.

## New: test coverage

- `packages/ai/test/schema-normalization.test.ts`: comprehensive unit
  tests for strict mode, Google, and Cloud Code Assist normalization
- `packages/ai/test/schema-compatibility.test.ts`: unit tests for all
  three provider targets in the new compatibility validator
- `packages/coding-agent/test/tools/provider-schema-compatibility.test.ts`:
  integration test that instantiates every builtin and hidden tool, runs
  their parameter schemas through all three provider pipelines, and
  asserts zero compatibility violations
2026-02-28 18:41:10 +01:00
Kevin LoftisandGitHub 2f97e36209 fix: invalidate capability fs cache after writing MCP config (#199)
writeMCPConfigFile writes to disk via node:fs, but the capability
system's readFile (capability/fs.ts) caches file contents in an
in-memory Map. When /mcp reauth writes a new credential ID to the
config file and then #reloadMCP calls discoverAndConnect, the
builtin provider's readFile hits the stale cache and returns the
old config — causing the connection to use the old (deleted)
credential ID and fail with HTTP 401 missing_token.

Fix: call invalidateFsCache(filePath) after the atomic rename in
writeMCPConfigFile so subsequent loadCapability reads see fresh
content.
2026-02-28 05:24:49 +01:00
Kevin LoftisandGitHub 6c5b3739a4 feat: support explicit OAuth clientId and callbackPort in MCP server config (#189)
Add an optional `oauth` config block on MCP server entries in mcp.json,
allowing explicit `clientId` and `callbackPort` values for servers that
don't expose these through auto-discovery (e.g. Slack).

The `oauth.clientId` is used as a fallback — if the server's error
response or well-known metadata includes a client_id, that takes
precedence. The `callbackPort` defaults to 3000 when not specified.
2026-02-27 11:58:48 +01:00
Chay ChoongandGitHub b29b06d73a fix(mcp): make disabledServers work on user-level third-party servers (#175)
The disabledServers mechanism introduced in 4bfa3b0c (Merge branch
'pr-82', 2026-02-16) was defeated by a level guard added after merge:
servers with _source.level === "user" were exempt from the disabled
check. This meant servers discovered from ~/.claude.json (which the
Claude provider tags as level "user") were never actually filtered out,
even when present in disabledServers.

Remove the level guard so disabledServers applies unconditionally. This
is safe because the /mcp disable command already uses updateMCPServer
(setting enabled: false inline) for servers defined in omp own configs;
the disabledServers path only fires for third-party discovered servers.

Also fix the /mcp list display to cross-filter discovered servers
against the disabled list, preventing a server from appearing both as
"connected" and "disabled" when the MCP manager has stale state.
2026-02-25 19:43:42 +01:00
can1357 a83175c94c refactor: migrated imports to unified package root and consolidated skill discovery logic
- Consolidated @oh-my-pi/pi-utils subpath imports into single package root import across 100+ files.
- Moved tryParseJson utility from local web scrapers module to @oh-my-pi/pi-utils package for centralized JSON parsing.
- Renamed loadSkillsFromDir to scanSkillsFromDir and refactored skill discovery to use fs.promises.readdir instead of glob-based approach.
- Replaced custom parseJSON with tryParseJson across discovery modules for consistent error handling.
- Removed emitCustomToolSessionEvent method and cleanupSshResources function, consolidating shutdown logic into dispose method.
- Updated glob pattern construction to use GlobBuilder with literal_separator(true) for improved path handling.
2026-02-23 20:59:17 +01:00
can1357 8b17a91a74 feat(coding-agent): introduced stripInternalArgs utility to filter harness-internal keys
- Added stripInternalArgs() utility function to filter harness-internal keys from tool arguments.
- Hidden agent__intent parameter from UI and log displays across agent, session, MCP, and tool-execution components.
- Implemented HIDDEN_ARG_KEYS constant to centrally manage internal argument filtering.
- Updated formatArgsInline() to exclude internal keys when rendering tool arguments.
2026-02-22 12:56:21 +01:00
can1357 abf8c1efc9 refactor: unslop common utilities 2026-02-22 11:01:11 +01:00
can1357 d5b28d6d3b feat(mcp): added browser MCP server filtering to prevent conflicts with builtin tool
- Added filterBrowser configuration option to disable MCP browser servers when builtin browser tool is enabled.
- Added isBrowserMCPServer() function to detect browser automation MCP servers by name, URL, or command patterns.
- Added filterBrowserMCPServers() function to remove browser MCP servers from loaded configurations.
- Added BrowserFilterResult type for browser MCP server filtering results.
2026-02-21 16:30:14 +01:00
DeprecatedLukeandCan Bölük faf90b274b refactor(coding-agent)!: standardize renderCall signatures to (args, options, theme) 2026-02-21 15:50:31 +01:00
Miroslav Drbal 8be261cd29 fix(mcp): strip $schema and nullable from MCP tool schemas before AJV validation
MCP servers using JSON Schema draft 2020-12 (e.g. servers built with Rust rmcp/schemars)
emit "$schema": "https://json-schema.org/draft/2020-12/schema" and "nullable": true in
tool inputSchemas. AJV (configured for draft-07) throws:

  Error: no schema with key or ref "https://json-schema.org/draft/2020-12/schema"

This causes all tool calls from these servers to fail argument validation silently.

Add sanitizeSchema() in convertSchema() — the single chokepoint for all MCP tool schemas
(MCPTool and DeferredMCPTool) — that recursively strips before schemas reach AJV or any
LLM provider:

- $schema: meta-schema declaration, not a validation keyword. AJV rejects unknown URIs.
- nullable: OpenAPI 3.0 extension, not standard JSON Schema. AJV rejects unknown keywords
  in strict mode.

This approach keeps AJV on draft-07 with no version switch. Switching to Ajv2020 was
considered but rejected: it would introduce the symmetric failure for servers that still
emit draft-07 $schema URIs.
2026-02-21 09:42:58 +01:00
can1357 f399b8e323 fix(coding-agent): fixes for PRs 80, 82, 85
- Parse --full as exact token in /changelog command
- Scope MCP disabled list to discovered servers only
- Guard malformed disabledServers config field
- Parse regex literal /pattern/flags syntax in secret entries
- Prevent infinite loop on zero-length regex matches
- Replace longer plain secrets before shorter prefixes
- Default secrets.enabled to false
2026-02-16 19:23:37 +01:00
Roman Alexander 0800cfa2d3 feat(mcp): support disabling discovered servers from third-party configs
Previously, /mcp enable|disable only worked for servers defined directly
in user or project config files. Discovered servers from third-party
configs (e.g. capability-provided) could not be toggled off without
removing them at the source.

This adds a disabledServers list to the user-level .mcp.json config that
acts as an overlay. When loading MCP configs, servers whose names appear
in this list are excluded alongside those with enabled: false.

Changes:
- Add disabledServers field to MCPConfigFile type
- Add readDisabledServers/setServerDisabled helpers in config-writer
- Filter discovered servers against the disabled list during config load
- Handle enable/disable toggle for discovered servers in the MCP
  command controller, including reconnection on re-enable
- Show disabled discovered servers in /mcp list output
2026-02-16 00:49:35 +07:00
can1357 d1e16d2101 refactor: implemented $PWD > cwd to prevent macOS symlink resolution
- Replaced all direct `process.cwd()` calls with `getProjectDir()` utility function across 40+ files to centralize project directory resolution logic.
- Added `getProjectDir()` and `setProjectDir()` functions to `@oh-my-pi/pi-utils/dirs` module to provide abstracted project directory management.
- Made `SessionManager.list()` method asynchronous to support asynchronous session discovery operations.
- Updated default working directory resolution throughout codebase to use `getProjectDir()` instead of `process.cwd()` for improved project directory detection.
2026-02-13 23:40:02 +01:00
can1357 fcd7ff4f84 refactor(dirs): centralized directory path utilities into @oh-my-pi/pi-utils/dirs module
- Extracted directory path utilities from multiple packages into a centralized '@oh-my-pi/pi-utils/dirs' module.
- Moved 30+ path helper functions (getAgentDir, getConfigRootDir, getPluginsDir, getMCPConfigPath, etc.) from scattered locations into a single shared utility module.
- Consolidated APP_NAME, CONFIG_DIR_NAME, and VERSION constants into the centralized dirs module for reuse across packages.
- Updated 70+ import statements across packages/ai, packages/coding-agent, packages/stats, and packages/tui to use the new centralized module.
- Removed local path construction logic and replaced with utility function calls for improved maintainability and consistency.
- Deleted packages/coding-agent/src/extensibility/plugins/paths.ts as its functions were moved to the centralized dirs module.
2026-02-13 15:06:05 +01:00
can1357 412ab9ea00 fix(coding-agent,ai): resolve issues #33, #34, #35, #37
- show help instead of crashing on `omp setup` with no args
- show runtime-discovered MCP servers in `/mcp list`
- remove deprecated Anthropic model entries from models.json
- sort models by recency in model selector
2026-02-12 20:29:06 +01:00
can1357 c518e24678 fix(coding-agent/mcp): added type assertion to OAuth metadata response for type safety
- Added type assertion to OAuth metadata response to ensure proper type safety.
2026-02-12 12:09:48 +01:00
Chris WatsonandGitHub da7eb2db83 fix(mcp): normalize object schemas by adding empty properties (#19)
Some MCP servers (e.g., Pencil) return tool schemas with just
{"type": "object"} without a "properties" field. While this is
valid JSON Schema, some LLM providers reject it with errors like:

  Error: Invalid schema for function 'mcp_pencil_get_style_guide_tags':
  In context=(), object schema missing properties.

This fix normalizes such schemas by adding an empty properties object
when missing, ensuring compatibility with stricter providers.
2026-02-11 08:04:01 +01:00
can1357 8d10a6b879 feat(coding-agent): added abort signal support and improved cancellation handling in MCP server operations
- Added abort signal support to MCP server connection and tool listing operations, enabling cancellation via Escape key during testing.
- Enhanced MCP connection timeout handling with improved abort signal integration in the withTimeout function to prevent race conditions.
- Improved MCP test command UI to display '(esc to cancel)' indicator and handle cancellation gracefully.
- Updated HTTP transport session termination to include timeout mechanism preventing indefinite hangs.
- Fixed MCP test command cleanup to prevent resource leaks when operations are cancelled or aborted.
2026-02-10 17:33:46 +01:00
can1357 c6c27d8ed1 fix(coding-agent/exec): improved bash executor abort handling and resource cleanup
- Added abort event listener registration in bash executor to properly handle abort signals and clean up resources in finally block.
- Improved bash tool error handling to distinguish between user-initiated aborts via AbortSignal and other cancellations, throwing ToolAbortError for aborted requests.
- Wrapped bash executor command execution in try-finally block to ensure abort event listeners are properly removed after execution completes.
2026-02-10 17:21:09 +01:00
can1357 9f94d39789 feat(coding-agent/mcp): added abort signal support to MCP request handling for cancellation
- Added MCPRequestOptions interface with signal property for request cancellation via AbortSignal.
- Added abort signal support to MCP tool execution enabling request cancellation via Escape-to-interrupt or other abort mechanisms.
- Enhanced MCP request handling with abort signal propagation through HTTP, SSE, and stdio transports with proper cleanup.
- Improved stdio transport request handling to use Promise.withResolvers for cleaner async flow and better abort signal integration.
- Updated HTTP transport to combine operation abort signals with timeout signals using AbortSignal.any() for unified cancellation.
- Modified SSE response parsing to support abort signals and distinguish between timeout and user-initiated cancellation.
2026-02-10 17:19:50 +01:00
copilot-swe-agent[bot]andcan1357 f3b185d5ec feat(mcp): added wizard confirm step and parallel auth resolve
- Introduced a confirmation step to the MCP add wizard.
- Parallelized authentication configuration resolution for server connections.
2026-02-10 15:25:10 +01:00
can1357 4da2acbee4 fix(coding-agent): addressed review findings for runtime MCP support
- Removed unsafe OAuth endpoint extraction from error message text
- Fixed PKCE verifier storage with typed #codeVerifier field
- Fixed refresh token fallback using access token as refresh token
- Enforced restrictive file permissions (0o700/0o600) for MCP configs
- Fixed wizard buildConfig() to respect user-chosen env var and header names
- Fixed reauth endpoint discovery for non-OAuth servers
- Stored original config on connection, resolved config only for transport
- Added runtime type validation for enabled/timeout in config loaders
- Converted all TS private keywords to ES # private fields
- Wrapped uncaught throws in /mcp add with try/catch error handling
- Replaced new Promise with Promise.withResolvers() pattern
- Sanitized TUI output with replaceTabs/truncateToWidth
- Enforced http/https URL validation in add wizard
- Fixed greedy /mcp prefix match in input controller
- Corrected config filename references in MCP guide
- Added server name validation to updateMCPServer
- Fixed timeout timer leak in stdio transport
2026-02-10 14:31:19 +01:00
7e4bedf18b Runtime MCP support (#17)
* + /mcp

- Reloads MCP manager in runtime state (no restart needed) and syncs with mcp.json.
- Handles OAuth discovery/auth flow automatically for auth-required servers.
- Validates server names and config shape before saving.
- Persists OAuth credentials in auth storage and links them to MCP config.
- Provides immediate connection checks and clear status messages.
- Supports enable/disable, reauth, and unauth flows that are easy to get wrong by hand.

* active agent tool registry runtime reload + token support for bearer auth http based transport

* +session rebind on succesful connection

* fix(coding-agent): address /mcp check failures

---------

Co-authored-by: can1357 <me@can.ac>
2026-02-10 14:09:41 +01:00
can1357 a5d9bbb4ca chore(coding-agent): migrated console logging to structured logger and updated import styles
- Migrated console.error() calls to structured logger.warn() and logger.error() throughout codebase.
- Updated os.tmpdir() import style in browser tool from destructured to namespace import.
2026-02-10 11:26:42 +01:00
can1357 acf8ab5225 style: stylistic changes 2026-02-10 07:39:39 +01:00
can1357 0171cd7b75 refactor(utils): restructured stream utilities for improved reusability and correctness
- Extracted stream abort handling into dedicated `createAbortableStream` utility function for reuse across stream operations.
- Refactored `sanitizeBinaryOutput` to handle surrogate pairs correctly and filter additional control characters (DEL and C1 control block) with lazy array allocation.
- Simplified JSONL parsing by replacing manual buffer management with new `pullJSONL` generator method in ConcatSink class.
- Removed unused `createSanitizerStream` and `createTextDecoderStream` helper functions that are no longer needed.
- Refactored `readSseJson` to use generator-based line processing with fast-path optimization for OpenAI-style done markers.
- Updated stdio transport to use new `readJsonl` utility instead of manual JSON parsing with TextDecoder.
2026-02-05 23:39:05 +01:00
can1357 b12a27830c refactor(stream): improved SSE parsing & added GPT-5.3, Claude 4.6 Opus
- Extracted stream parsing utilities into reusable functions in pi-utils package (readLines, readJsonl, parseJsonlLenient, readSseJson).
- Replaced manual buffer management with ConcatSink class for efficient stream handling across multiple modules.
- Simplified SSE stream parsing in google-gemini-cli provider by using readSseJson utility instead of manual reader setup.
- Refactored MCP stdio transport to use readLines utility for cleaner line-based stream processing.
- Updated RPC client and mode to use readJsonl utility, removing duplicate JSONL parsing logic.
- Optimized buffer allocations by using allocUnsafe where data is immediately populated and reusing empty buffer instances.
2026-02-05 21:06:58 +01:00