This page is the companion to MCP, Permissions & Sandbox. That page covers the safety story (who can call what, why). This page covers the catalog (what exists, how it’s wired, where parity holds and where it breaks).

The five tool categories

Every tool an Orion agent can call falls into one of five buckets:
#CategoryWhere definedAvailable on Claude SDKAvailable on Pi
1SDK built-insInside @anthropic-ai/claude-agent-sdkNative (Read, Edit, Write, Bash, Grep, Glob, WebSearch, WebFetch, Task, AskUserQuestion, TodoWrite)Re-implemented via pi-sandbox wrappers + a few direct Pi tools
2Internal MCP servershandler.mjs per query4 servers (orion, orion-tools, canvas, terminal)3 servers bridged (terminal is Claude-only)
3External MCPUser’s .mcp.jsonVia loadMcpConfig() + OAuth singletonVia the same mcpManager, brokered through proxy on strict routes
4Pi-direct toolsengine/pi-tools.mjsn/a8 hardcoded tools (knowledge_search, spawn_subagent, agents_list, agent_info, ask_user_question, TodoWrite, switch_model, mcp proxy)
5CCW delegateccw/orion-tools-mcp-server.mjsAs mcp__orion-tools__delegate_to_cliSame name (lenient routes) or via proxy (strict routes)
The dual-engine framing: most tools work on both runtimes, but the exposure mechanism differs by route. Read on.

Tool surface at a glance

The four internal MCP servers (Claude side)

Created per query inside handler.mjs, wrapped via trackingCreateServer() which adds each server name to internalServers: Set<string> — that set is what canUseTool consults to auto-trust the server’s tools (no user prompt).
The “everything memory + PARA” server. Defined in memory/tools.mjs, instantiated at handler.mjs:1900-1919.
ToolPurpose
knowledge_searchHybrid ANN + FTS5 search across all indexed memory
recallBackward-compat alias for knowledge_search
user_profileReturns the active user’s profile facts
select_projectSwitches active PARA project for the session
knowledge_insightsReturns recently-generated consolidation insights
query_paraStructured PARA queries (status, dates, links)
manage_task / manage_entityCRUD against PARA tasks + entities
link_entities / knowledge_graphEdge traversal + graph queries
manage_inboxTriage Inbox items
proactive_contextPulls relevant context for the current turn
list_sessions / session_transcript / session_summary / peek_sessionCross-session introspection
cross_project_feedActivity feed across PARA
manage_task_runAgent task-run CRUD (see Agents page)
create_task_commentThreads against a task run
spawn_sessionProgrammatic session creation — gated on sessionType === 'chat' AND spawnDepth === 0 (at memory/tools.mjs:2221-2230)

Bridging Claude MCP → Pi

When a Pi agent runs, the four (well, three — terminal is skipped) internal MCP servers must become Pi ToolDefinitions. The flow lives in engine/mcp-bridge.mjs: Schema conversion uses z.toJSONSchema(zodSchema, { target: "draft-2020-12" }) at mcp-bridge.mjs:82openapi-3.0 is explicitly avoided because it emits legacy boolean exclusiveMinimum: true which OpenAI’s function-calling validator rejects (comment at :78-82). terminal and codexlens are NOT extracted. extractMcpToolDefs only instantiates the three factories createMemoryMcpServer / createCanvasMcpServer / createOrionToolsMcpServer (mcp-bridge.mjs:220-272). So Pi sessions never see those tool surfaces. There is exactly one tool name that collides between the direct Pi surface and the bridged MCP surface: knowledge_search.
  • Direct Pi tool: pi-tools.mjs:166-227createKnowledgeSearchTool registers knowledge_search (bare name)
  • Bridged MCP tool: would have name mcp__orion__knowledge_search
The dedup loop at pi-tools.mjs:125-141 keeps a Set<baseName> of already-registered direct tools, strips the mcp__server__ prefix from each bridged tool, and skips any bridged tool whose base name is already taken. In practice this fires for exactly one toolknowledge_search. The bridged variant is silently dropped.
Author rule (2026-05-17): In config.yaml, command frontmatter, or skill references, always use the bare name knowledge_search — never mcp__orion__knowledge_search. The prefixed form resolves on Claude but vanishes on Pi. A CI lint at scripts/lint-no-prefixed-orion-tools.mjs blocks reintroduction across prompts/{agents,commands,skills}/.
There is also a feature gap behind this name. The Claude-side mcp__orion__knowledge_search exposes 7 parameters: query, maxResults, source, entityId, minImportance, topics, includeConsolidations, includeRelated. The bare Pi knowledge_search exposes only query and maxResults (pi-tools.mjs:178-220). Advanced filtering (importance, topics, edge traversal) is Claude-only today.

The Phase 5b proxy gate

The most consequential dual-engine divergence. Lives at engine/model-router.mjs:96-123:
const OPENAI_RESPONSES_API_PROVIDERS = new Set([
  'openai',
  'openai-codex',
  'azure-openai-responses',
]);

export function isOpenAiResponsesModel(route) {
  return route.engine === ENGINE_PI
    && OPENAI_RESPONSES_API_PROVIDERS.has(route.provider);
}
In mcp-bridge.mjs:370-377, when the caller omits proxyInternalMcp, the default is derived from this function:
if (typeof proxyInternalMcp !== "boolean") {
  const { isOpenAiResponsesModel } = await import("./model-router.mjs");
  proxyInternalMcp = isOpenAiResponsesModel(route);
}
Result:
RouteproxyInternalMcp defaultTool surface seen by Pi agent
Pi openai/* (Responses API)trueInternal MCP hidden behind a single mcp proxy tool — search → describe → call
Pi openai-codex/*trueSame
Pi azure-openai-responses/*trueSame
Pi google/* (Gemini)falseFull 37 mcp__server__tool names exposed upfront
Pi openrouter/*falseSame
Pi anthropic-via-Pi, deepseek, groq, mistral, etc.falseSame
Claude SDKn/abuildPiCustomTools is Pi-only
Why the split exists. OpenAI’s strict Responses-API validator rejects context_cache’s discriminatedUnion schema. Phase 5 (PEX-extend-mcp-proxy-internal-2026-04-28) defaulted the proxy ON unconditionally, but that forced lenient providers (Gemini, OpenRouter) to pay an extra search/describe/call round-trip for no validator benefit. Phase 5b (2026-04-29) made the default route-conditional. Escape hatch: explicit proxyInternalMcp: <bool> overrides.

Parity matrix

CategoryClaude SDKPi lenient (Gemini, OpenRouter, etc.)Pi strict (OpenAI Responses)Divergence
Read/Write/Edit/Grep/Glob/Bash/NotebookEditNative SDK built-inspi-sandbox wrappers — bash, read, write, edit, grep, find, ls (pi-session.mjs:419-422)SamePi has no NotebookEdit, no Glob (uses find/ls)
Delegation (Task / Agent)Native SDKspawn_subagent direct Pi tool — modes single / chain / parallel (pi-tools.mjs:319-368)SamePi gains chain + parallel modes natively
AskUserQuestionNative SDKask_user_question direct Pi tool — reuses same questionRequest IPCSameFunctional parity
TodoWriteNative SDKTodoWrite direct Pi tool — exact name match for ActivityStream detectionSameParity by intentional naming
WebSearch/WebFetchNative SDK; gated by web_search_enabledNot in Pi sandbox toolset; reachable only via Exa MCPSameAsymmetric — Pi lacks SDK primitive
mcp__orion__knowledge_searchAuto-trusted (7 params)Dropped by dedup; bare knowledge_search available (2 params)Hidden behind mcp proxy; bare knowledge_search still directFeature gap on Pi
mcp__orion__* (other ~19)Auto-trustedBridged as prefixed namesBehind mcp proxyTool-name surface differs by route
mcp__canvas__* (3)Auto-trustedBridgedBehind proxySame pattern
mcp__orion-tools__* (13, incl. delegate_to_cli)Auto-trustedBridgedBehind proxySame pattern
run_in_terminalPer-command user promptNot registeredNot registeredClaude-only
mcp__codexlens__*Optional, project-conditionalNot extracted by bridgeNot extractedClaude-only (unverified whether reachable via external mcpManager)
switch_modelNot presentDirect Pi tool (pi-tools.mjs:1062), gated on capabilitySamePi-exclusive
agents_list / agent_infoNot present (SDK auto-loads via folder)Direct Pi tools (pi-tools.mjs:1361, :1408)SamePi-exclusive — replaces ~120K-token agent catalog injection

Three permission layers

Quick reference — full treatment lives on the MCP, Permissions & Sandbox page.
LayerClaude SDKPi
L1 — OS sandboxSDK’s additionalDirectories allowlist, sandbox-exec on macOSpi-sandbox/ module: assertSandboxPath + path-policy + bash-wrapper via @carderne/sandbox-runtime (same primitive Claude uses internally)
L2 — Permission middlewarecreateCanUseTool(ctx) closure factory (permissions/canUseTool.mjs:117) — internalServers Set auto-trusts internal MCPSame closure passed in; cron mode (sessionType === 'cron' OR !canUseTool) sets noUserAvailable: true → all sandbox prompts auto-deny
L3 — Capability prefsapplyCapabilityPreferences() adds tools to disallowedToolspi-session.mjs:425-438 filters the tools array before createAgentSession
L3 toggles that apply on both engines:
PreferenceEffect on ClaudeEffect on Pi
web_search_enabled === 'disabled'Adds WebSearch/WebFetch to disallowedToolsPi has no native WebSearch — gating relies on external MCP visibility
file_edit_mode === 'read_only'Adds Write/Edit/NotebookEdit to disallowedToolsFilters write, edit from tool array (pi-session.mjs:427, :432)
file_edit_mode === 'auto_accept'Sets permissionMode: 'acceptEdits'n/a
code_execution_mode === 'disabled'Adds Bash to disallowedToolsFilters bash (pi-session.mjs:426, :431)
model_switch_mode === 'disabled'n/aDrops switch_model tool (pi-tools.mjs:119)

Delegation: delegate_to_cli

Lives at ccw/orion-tools-mcp-server.mjs:725-750. Available to agents on both engines (as mcp__orion-tools__delegate_to_cli on Claude and lenient Pi; via the mcp proxy on strict Pi). Schema:
{
  tool: 'gemini' | 'codex' | 'claude' | 'qwen' | 'opencode',
  prompt: string,
  mode: 'analysis' | 'write',           // default 'analysis'
  model?: string,
  dir?: string                          // defaults to session cwd
}
Behavior: fires startDelegation(input, requestId) and returns immediately with a delegationId. The user gets a toast and an inline result when the delegated CLI completes. Sister tools execute_cli_inprocess (synchronous), cancel_delegation, and check_delegations round out the lifecycle.
The delegated CLIs are NOT sandboxed. Bash runs under sandbox-exec / sandbox-runtime. But delegate_to_cli calling out to Codex / Gemini / Claude CLI / Qwen / opencode spawns those child processes directly — they inherit sidecar permissions, not sandbox restrictions. Whether this is a deliberate trade-off (so delegated CLIs can do real work) or a hardening gap depends on your threat model. Filed as the architectural sibling of GitHub issue #78. See MCP, Permissions & Sandbox for the full discussion.

Honest gaps

#GapSeverityWhere
T1run_in_terminal is Claude-only. Pi sessions cannot use the embedded PTY.Mediummcp-bridge.mjs:220-272 (terminal not extracted)
T2mcp__codexlens__* listed as optional but not bridged to Pi. Reachable via external mcpManager? Unverified.LowAbsence in extractMcpToolDefs
T3knowledge_search feature gap — Pi version takes 2 params, Claude version takes 7. Advanced filters (importance, topics, edges) Claude-only.Mediumpi-tools.mjs:178-220 vs memory/tools.mjs
T4delegate_to_cli spawns external CLIs unsandboxed.Mediumccw/orion-tools-mcp-server.mjs:725-750, related to #78
T5Pi’s noPromptTemplates: true blocks the Pi SDK’s own /compact and other native slash commands. Orion routes /command via handler.mjs BEFORE the engine split.Acceptedpi-session.mjs:500, documented in cron-engine-routing.md Known Constraints §1
T6Layer-1 sandbox asymmetry on Windows — both engines fall through to per-command canUseTool prompts.AcceptedPlatform constraint
T7mcp_tool_permissions DB lookup runs only on Claude’s canUseTool — Pi’s permission middleware classifies all mcp__* as safe (issue #77).Medium-HighGitHub issue #77; permission middleware gap

See also

  • MCP, Permissions & Sandbox — the three layers in depth, OAuth singleton, sandbox runtime
  • Two engines — engine routing, routeModel(), model preferences
  • Agents — how agents_list / agent_info (Pi) and the agents map (Claude) work
  • Memory — the knowledge_search indexing pipeline