Commit Graph
75 Commits
Author SHA1 Message Date
can1357 e7200c2e4a feat(ai): added unified normalize flow for Google/CCA schema handling
- Implemented a unified normalization flow by switching Google/CCA handling to normalizeSchemaForGoogle/CCA.
- Added normalize.ts with recursive node normalization, nullable-union checks, and combiner collapsing.
- Removed sanitize-google.ts and normalize-cca.ts, replacing them with normalize exports in schema indexes.
- Added spill-to-description utilities with spill/paren modes and `$defs` exclusion for unsupported fields.
- Updated MCP bridge and schema tests to use normalizeSchemaFor* APIs with expanded compatibility checks.
- Documented normalization behavior changes and breaking rename in constraints and package changelog files.
2026-05-16 19:04:09 +02:00
can1357 2867e1f4e3 feat(deps): added pi.zod exports and removed TypeBox package exports
- Added canonical `pi.zod` schema API exports and removed TypeBox package exports/imports.
- Migrated Tool schema typing from TypeBox to shared `TSchema`/Zod flow with legacy TypeBox compatibility.
- Updated AI provider adapters and MCP/agent builders to convert tool params through `toolWireSchema()`.
- Reworked schema validation from AJV to Zod-safe parsing with `fromTypeBox`, `toolWireSchema`, and meta schema checks.
2026-05-15 14:46:54 +02:00
Vilmos Nebehaj a7f73ee645 fix(coding-agent): persist dynamically registered MCP OAuth client_id
When an MCP server uses OAuth Dynamic Client Registration (RFC 7591) and
no client_id is pre-configured, MCPOAuthFlow registers a fresh public
PKCE client on each authorize, captures the issued client_id into a
private field, then discards it once the flow object goes out of scope.
At refresh time, MCPManager#resolveAuthConfig calls refreshMCPOAuthToken
with auth.clientId from mcp.json — which is empty for these servers —
so providers that require client_id on the refresh grant (e.g. Linear at
mcp.linear.app/token) reject with HTTP 401 invalid_client. The user is
forced to /mcp reauth manually every time the access token expires.

This change threads the resolved/registered client credentials back out
of the OAuth flow and persists them into mcp.json so refresh has what
it needs indefinitely:

- MCPOAuthFlow exposes resolvedClientId / registeredClientSecret getters.
- MCPCommandController#handleOAuthFlow returns OAuthFlowResult with
  credentialId + clientId + clientSecret, populated from the flow's
  post-login state.
- The initial-connect non-wizard path and /mcp reauth path persist the
  returned client credentials into both auth.{clientId,clientSecret}
  (used at refresh) and oauth.{clientId,clientSecret} (used by future
  /mcp reauth to skip re-registration).
- The wizard's onOAuth callback signature now returns the same shape;
  #launchOAuthFlow folds the registered credentials into wizard state so
  the final mcp.json entry built by #buildServerConfigWithAuth includes
  them under auth.{clientId,clientSecret}.

Servers that configure a static oauth.clientId in mcp.json (Notion,
Slack, Datadog) are unaffected: #tryRegisterClient short-circuits, the
returned clientId equals the configured one, and the write-back is a
no-op.

Adds two MCPOAuthFlow unit tests covering both paths.
2026-05-13 15:39:04 -07:00
can1357 1bde755933 feat(coding-agent): added global singletons for URL protocol handlers
- Added process-wide singleton instances for InternalUrlRouter, AsyncJobManager, and MCPManager.
- Changed internal URL protocols to resolve through registered sessions and scan all active roots/datasets for matches.
- Refactored agent, artifact, memory, rule, skill, jobs, and mcp handlers to use shared manager and rule/skill state.
- Removed per-session protocol/tool wiring and switched tests to initialize and reset global singleton state.
2026-05-12 05:07:52 +02:00
can1357 4a6c56772f fix(coding-agent/mcp): fixed SSE parsing by draining the stream in a single pass
- Refactored SSE response parsing to read the stream with a single in-progress drain loop.
- Resolved the matching request promise when the expected JSON-RPC result or error arrives and continued dispatching remaining SSE messages on the same stream.
- Adjusted error handling to reject on abort/timeout or missing response and clear the timeout when stream parsing completes.
2026-05-10 06:47:19 +02:00
HabibPro1999andcan1357 941683cd66 refactor(coding-agent): extracted generic tool discovery module
Moved the BM25 discovery index out of mcp/discoverable-tool-metadata.ts
into tool-discovery/tool-index.ts and generalized it to work over any
AgentTool source (built-in, MCP, extension, custom) instead of only MCP.

Added DiscoverableTool with explicit source and summary fields. Kept
the MCP-typed helpers (collectDiscoverableMCPTools, searchDiscoverable
MCPTools, etc.) as thin re-exports from the original path so existing
callers continue to work unchanged.

Tests cover the generic index, source filtering, summary fallback, and
back-compat with the legacy MCP-only API.
2026-05-06 18:08:48 +02:00
Can BölükandGitHub 333510aced Merge pull request #890 from apoc/fix/mcp-tool-cache-stability
fix(coding-agent/mcp): stabilize tool ordering and skip redundant prompt rebuilds
2026-05-01 19:09:23 +02:00
can1357 3a46b748a3 perf(ai): dynamic oauth imports per provider
- Documented and removed `utils/oauth` from the `ai` package entrypoint, noting it as a breaking change.
- Refactored `cli`, `auth-storage`, and `utils/oauth` to load provider modules via scoped dynamic `import()` calls.
- Removed top-level provider imports and barrel exports from `utils/oauth/index.ts`, streamlining oauth module loading.
- Consolidated OAuth symbol, type, and provider imports in coding-agent and tests to `@oh-my-pi/pi-ai/utils/oauth` modules.
- Defined `DEFAULT_LOCAL_TOKEN` locally in model-registry and removed its cross-package OAuth import usage.
2026-04-30 15:14:07 +02:00
Miroslav Drbal b5ca55e79f fix(coding-agent/mcp): stabilize tool ordering and skip redundant prompt rebuilds
Two cache-stability fixes for Anthropic prompt caching during MCP server
reconnects, which happen routinely (~5 min per server) in long sessions
due to SSE transport keepalive timeouts.

1) MCPManager: deterministic tool ordering

   `#tools` is now sorted by name after every mutation. The previous
   filter-out + push-to-end pattern in `#replaceServerTools` moved the
   reconnecting server's tools to the end of the array, producing a new
   byte order whenever the reconnect sequence differed from the initial
   discovery sequence. With multiple healthy servers, each reconnect of
   the non-last server flipped the order and invalidated the tools
   cache breakpoint sent to Anthropic.

   Sort applies in `discoverAndConnect` (initial population) and
   `#replaceServerTools` (used by `reconnectServer` and
   `refreshServerTools`). The comparator is character-code based,
   locale-independent and deterministic. `sortMCPToolsByName` is
   exported as a small generic helper and unit-tested.

2) AgentSession: skip system-prompt rebuild when inputs are unchanged

   `#applyActiveToolsByName` (called from `refreshMCPTools` after every
   reconnect) used to unconditionally call `rebuildSystemPrompt` and
   `setSystemPrompt` even when the resulting prompt was byte-identical.
   This wasted CPU on every flap and risked silent cache invalidation
   if the rebuild path ever became non-deterministic.

   Now `#applyActiveToolsByName` computes a stable signature of the
   inputs `rebuildSystemPrompt` reads and skips the rebuild when the
   signature matches the last successful one. The signature covers:
     - active tool names in render order
     - active tool labels and descriptions (rendered as `{{label}}:
       \`{{name}}\`` in the prompt body)
     - when MCP discovery is on, every registry tool's name + label +
       description (the prompt summarizes discoverable-but-inactive
       MCP tools)
     - per-server MCP `instructions` text (embedded under "## MCP
       Server Instructions" in the appended prompt; can change on
       server upgrade while tool list stays identical)

   Server instructions are read via a new optional
   `getMcpServerInstructions` callback on `AgentSessionConfig`, wired
   from the SDK as `() => mcpManager.getServerInstructions()`.

   `refreshBaseSystemPrompt()` continues to rebuild unconditionally and
   refreshes the cached signature, so explicit refreshes still pick up
   ambient changes (edit-mode toggles, memory writes, etc.) that the
   signature does not cover.

Signature inputs deliberately NOT covered: tool input schemas, memory
instructions read from disk, and other ambient state. Callers that
mutate those must call `refreshBaseSystemPrompt()` explicitly; existing
hooks (`#syncEditToolModeAfterModelChange`, memory hooks, `/clear`)
already do.
2026-04-30 15:04:33 +02:00
can1357 88a1072cc5 feat(coding-agent): implemented mcp__-prefixed MCP tool IDs for parsing
- Renamed MCP tool IDs from `mcp_<server>_<tool>` to `mcp__<server>_<tool>`, and changed built-in `grep` to `search`.
- Updated `parseMCPToolName()` and bridge helpers to require and trim the `mcp__` prefix.
- Updated cursor, manager, and session discovery flows to require `mcp__`-prefixed tool names.
- Updated MCP tests and assertion fixtures to use `mcp__`-prefixed tool IDs and expected system prompts.
2026-04-28 01:15:35 +02:00
can1357 52da3674a4 feat(coding-agent): added auto-rebase for stale atom/hashline anchors
- Added auto-rebasing for stale atom and hashline anchors within ±2 lines, with warning diagnostics.
- Removed atom range-locator support and dropped `between` ops, updating docs/tests for `sub` over `set` nudges.
- Changed no-op handling to track unchanged hashline edits and emit contextual hints for unchanged ranges.
- Expanded Anthropic strict error handling to retry on schema-too-complex and compiled-grammar-too-large errors.
- Added anchor-retargeting warning checks and updated edit tests to expect locator and range-locator rejections.
- Updated python tool-call fixtures by adding `title` to executed-cell payloads and adjusting related test expectations.
2026-04-26 05:22:15 +02:00
can1357 44ea247cf8 fix(coding-agent/tools): corrected json-tree inline arg truncation logic
- Updated inline argument formatting in formatArgsInline to truncate key/value pairs by width and show ellipsis when values overflow.
- Fixed JSON-tree output to hide harness-internal INTENT_FIELD and __partialJson keys from top-level tool argument rendering.
- Fixed multiline scalar rendering and empty-structure display paths in renderJsonTreeLines after tree-prefix traversal changes.
2026-04-26 04:32:27 +02:00
can1357 d24d11a274 fix: resolved AI/OAuth helper duplication via shared modules
- Standardized missing-file read errors and now return `File not found: <path>` for absent edit targets.
- Centralized AI provider, usage, and OAuth helpers into shared modules to remove duplicated logic.
- Migrated OAuth/API-key login flows to shared factory helpers and removed inline prompt/token-exchange code.
- Reused shared tools and formatter utilities for discovery, stream tails, LSP batching, and source formatting.
- Consolidated repeated test helpers and fixtures into shared modules, replacing inline helper duplicates.
2026-04-23 21:02:14 +02:00
can1357 e31f2ef6f0 refactor: simplified null checks using optional chaining across TypeScript and Rust modules
- Simplified null/empty checks across TypeScript codebase using optional chaining operator (?.) for improved readability.
- Replaced explicit null checks in validation logic with optional chaining in oauth-discovery, gemini-cli, claude, zai, and lsp modules.
- Updated error handling in Rust command invocation to use double question mark operator (??) for cmd_result.
- Consolidated null validation patterns across tools (bash-skill-urls, browser, gemini-image, resolve) and keybindings using optional chaining.
2026-03-26 14:09:14 +01:00
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