MCP — Model Context Protocol¶
Cortex ships privilege-scoped and platform-scoped MCP (Model Context Protocol) tools that give agents access to remote machines, subagents, costs, waitpoints, team workers, boards, and platform integrations. This document explains what each server provides, how they are composed, and how to add third-party MCP servers.
What MCP is¶
MCP is an open protocol that lets LLM applications expose tools to agents through a standardized JSON-RPC interface over stdio, HTTP, or an in-memory transport pair. Cortex uses MCP to bridge between the agent (which reaches no agent-server internals of its own) and the server's capabilities. MCP support varies by backend — see the feature matrix in backends.md.
Claude Code reads MCP server configurations from JSON files and spawns each
stdio server as a child process. The agent then calls MCP tools just like
built-in tools (Bash, Read, Edit, etc.), with the tool names prefixed by
mcp__<server-name>__. PI connects MCP servers through Cortex's own bridge:
the bundled Cortex tools keep their bare names, and a plugin server's tools are
named plugin_tool_plugin_server_<server>_<tool>.
Why Cortex ships its own MCP servers¶
Cortex's agent-server maintains state that the agent process cannot reach directly: WebSocket connections to remote machines, running subagents and team workers, waitpoints, the chat platforms, cost records, and the execution registry. The Cortex MCP tools are a controlled bridge — the agent calls a tool, the tool asks the server (over the loopback webhook, or by reading the server's stores), and the result flows back to the agent.
The bundled MCP servers¶
The names below are logical capability bundles, not separate processes. Unless
a session's MCP composition is empty, the session gets exactly one Cortex-owned
server built from agent-server/src/domain/mcp/bundled-server.ts, registering
only the bundles selected for it. Claude Code starts that server as a stdio
child; PI runs it inside the agent-server process over an in-memory transport
pair, bound to a tool context built for that one session. Under Claude the server
key is cortex-core, so every bundled tool carries that prefix — the remote
tools are mcp__cortex-core__remote_*, and the context tool is
mcp__cortex-core__cortex_context. PI exposes the same tools under their
unprefixed names.
User-provided MCP is outside this bundle. Portable plugin servers, browser MCP, remote HTTP/SSE servers, and Claude-native MCP from an assigned legacy plugin keep independent configurations, transports, and failure boundaries.
cortex-core¶
Exposes remote-machine operations, the read-only clock, and subagent
delegation. Every session whose MCP composition is not none loads it,
subagents included.
| Tool | Parameters | Description |
|---|---|---|
remote_bash |
device, command, timeout? (seconds, 1–600, default 120), description?, run_in_background? |
Execute a shell command on a remote device via cortex-client |
remote_read |
device, file_path (absolute), offset?, limit? |
Read a file from a remote device (supports images and PDFs) |
remote_write |
device, file_path, content |
Write content to a file on a remote device |
remote_edit |
device, file_path, old_string, new_string, replace_all? |
Edit a file on a remote device by string replacement |
remote_glob |
device, pattern, path? |
Find files matching a glob pattern on a remote device |
remote_grep |
device, pattern, path?, glob?, type?, output_mode?, -A?, -B?, -C? (alias context?), -i?, -n?, head_limit?, offset?, multiline? |
Search file contents on a remote device using ripgrep |
current_time |
timezone? |
Get the current date/time; optional IANA timezone (defaults to server local). Returns Unix epoch, UTC ISO, and localized wall-clock with offset |
agent |
single (description, prompt, subagent_type) or parallel[] or chain[] (each item description, prompt, subagent_type, model?, backend?), plus model?, backend?, run_in_background? |
Delegate work to an isolated subagent on either backend |
agent_stop |
agent_id |
Stop a running subagent and discard whatever it had produced |
The remote tools are registered by
agent-server/src/domain/mcp/tools/remote-ops.ts; agent and agent_stop by
agent-server/src/domain/mcp/tools/subagent.ts.
The remote tools share some behavior:
- The first remote tool in a session's surface carries a catalog of the devices
registered in
machines.jsonin its description, Windows machines marked, so the agent knows whichdevicevalues exist. A call to a device that is not connected fails with the list of devices that are online. remote_read,remote_writeandremote_editadd the target'sAGENTS.mdancestor chain on that device to their result, once per file per session (see cross-machine.md).remote_readreturns images and PDFs as media; the server downscales large images whensharpis available.- Output above 30,000 characters (
remote_bash), 20,000 (remote_grep) or 100,000 (remote_glob) is saved under$CORTEX_HOME/tmp/tool-results/, and the tool returns the path with a 2 KB preview.
agent replaces Claude Code's built-in Agent tool, which Cortex strips from
every Claude spawn. It runs on the same role table as PI's own native agent,
can place a child on either backend, and is visible to the daemon — so a
delegated run appears in the parent's transcript, is billed to the parent, and
can be stopped from the UI. A foreground call that is still running after 25
minutes is handed to the background and its result arrives later. See
backends.md.
PI sessions never receive the MCP agent and agent_stop: PI registers its
own agent, and two tools of that name cannot coexist. A subagent receives
neither on either backend. That is enforced by the tool allowlist rather than
by the prompt, so a child cannot fan out further no matter what it is asked to
do.
cortex-workers¶
The lead's side of team mode. A session selects this bundle when teamEnabled is
on and it is a web direct session that is not itself a worker, on Claude and PI alike. Workers and
subagents never load it. Every tool returns at once; worker reports and questions reach the lead
as new messages.
| Tool | Parameters | Description |
|---|---|---|
worker_spawn |
name, prompt, backend?, model? |
Start a persistent worker session with its first task; it runs the lead's current model unless backend/model (same format as agent) name another |
worker_send |
worker, message |
Deliver a message to a worker, folded into its running turn or opening a new one |
worker_list |
worker? |
The lead's roster, or one worker with its full last report |
worker_stop |
worker |
Stop a worker's current turn |
worker_answer |
request_id, answers?, approved?, feedback? |
Resolve a worker's pending question or plan approval |
The bundle is loaded by agent-server/src/domain/mcp/bundled-server.ts; the tools are in
agent-server/src/domain/mcp/tools/workers.ts and are served by the /webhook/team route.
cortex-worker-report¶
A worker's side of team mode. Every worker session loads it, on Claude and PI alike; leads, ordinary sessions and subagents never do.
| Tool | Parameters | Description |
|---|---|---|
mark_routine |
none | Mark the current turn as routine: its final reply is recorded for the lead as an unread update and does not wake it. A failed or stopped turn reports anyway |
The tool is in agent-server/src/domain/mcp/tools/workers.ts and is served by the
/webhook/team route, which refuses it for a session that is not a worker.
cortex-ext¶
Exposes Cortex management tools: cost queries, execution records, context
resolution, and waitpoints. Every top-level session whose MCP composition is
not none loads it, on Claude and PI alike. Claude subagents load it too; PI
subagents do not.
| Tool | Parameters | Description |
|---|---|---|
cost_query |
projectId? |
Query current cost: today/month spending, budget limits, remaining budget, API/plan split, source breakdown, token usage; with projectId, scoped to that project |
query_executions |
execution_id?, status? (running, completed, failed, cancelled, stale, all), project?, limit? |
Query execution records — filter by status, project, or look up by ID |
cortex_context |
(none) | Return the current execution context: sessionId, project, backend, model, thinking, mode, and for a scheduled or callback-triggered run scheduleTaskId and callbackSource; in a Web session also board, the board it is bound to (id, title, dir) or null |
wait_create |
label, intent, members?, quorum?, fail_fast?, max_signals?, expires_in_hours?, device? |
Arm a waitpoint and return the id, a one-time secret, and ready-to-paste signal commands; then end the turn |
wait_check |
id? |
Inspect one waitpoint, or list everything this session is waiting on |
wait_cancel |
id |
Cancel a waitpoint so it can no longer fire |
wait_create is how a session waits for something outside Cortex — a training
run, a build, an eval — without polling it. The external program reports back
with the cortex-signal CLI, a loopback POST /webhook/signal, or a file
dropped in a spool directory, and the session is woken with the result. See
waitpoints.md.
Schedules are not MCP tools: agents manage them with the cortex schedule CLI
through the schedule skill, resolving the project with cortex_context and
passing it with --project (see Scheduling).
The bundle is loaded by agent-server/src/domain/mcp/bundled-server.ts.
Individual tools are in agent-server/src/domain/mcp/tools/.
cortex-slack¶
Platform-specific MCP server for Slack. Loaded only when the session originates from Slack, providing platform-specific file upload and messaging capabilities.
| Tool | Parameters | Description |
|---|---|---|
slack_send_file |
file_path, file_name?, title?, comment?, device? |
Upload a file to Slack; with device, the file is read on that remote device |
The bundle is loaded by agent-server/src/domain/mcp/bundled-server.ts; the tool is in
agent-server/src/domain/mcp/tools/slack.ts.
cortex-feishu¶
Platform-specific MCP server for Feishu/Lark. Loaded only when the session originates from Feishu. It exposes a single tool — sending files to a chat.
| Tool | Parameters | Description |
|---|---|---|
feishu_send_file |
file_path, file_name?, title?, channel?, device? |
Upload a file to a Feishu chat; with device, the file is read on that remote device |
Document, table, spreadsheet, and knowledge-base operations are not MCP
tools. They run through the official Lark/Feishu CLI (@larksuite/cli), driven
by the feishu-doc skill of the built-in cortex-feishu plugin, which handles
native tables and block-level edits. See the feishu-doc skill for the
install/auth preflight and how to delegate to the CLI's embedded lark-doc /
lark-sheets / lark-base skill guides.
The bundle is loaded by agent-server/src/domain/mcp/bundled-server.ts.
The tool is in agent-server/src/domain/mcp/feishu/file.ts.
cortex-web¶
Platform-specific MCP server for the Web workbench. Loaded only when the session
originates from the Web UI (its channel carries the web: prefix), so a Slack-
or Feishu-originated session never sees these tools.
| Tool | Parameters | Description |
|---|---|---|
send_file |
file_path or files[] ({file_path, file_name?}), file_name?, caption?, device? |
Send one file, or several as one grouped message with a single caption, into the chat as downloadable cards (images and video preview inline); with device, the files are read on that remote device |
send_view |
title, html or file_path, caption?, height? (160–900, default 360), width? (narrow, wide, full) |
Render an HTML view inline in the chat as a live, interactive card |
send_decision |
decisions[] — each title, decision, context, reasoning |
Record decisions the agent just made and show them as cards in the chat |
board_open |
title or board (exactly one) |
Create a board, or attach to an existing one by id or slug, and bind the session to it |
send_file, send_view and send_decision proxy their payload to the daemon over the loopback webhook rather
than returning it as tool output: the normalized event stream carries tool
results as flat strings, and the PI backend flattens rich MCP content to text,
so anything richer than a string has to travel out of band. The daemon copies
the bytes into workspace/outputs/<sessionId>/ (views land one level deeper, in
views/), records an assistant message carrying the attachment, and publishes
the live event. Only the path travels — a large document never enters the
transcript or the event stream.
send_view takes either an inline html string (up to 256KB) or the path to an
.html file the agent already wrote (up to 2MB). The view renders in a frame
sandboxed to allow-scripts with no allow-same-origin, so the document has an
opaque origin: no access to the page, storage, cookies, the Cortex API, or (in
the desktop shell) the auth token and Tauri IPC. It may still load libraries and
data over https. That isolation is why the rendering intent is carried by the
attachment bucket the server mints and never by a file extension — an .html
file that a user uploads or that an agent sends with send_file opens as source
text, not as a running document.
width picks one of three inline card widths: narrow (the default) sits in the
chat's text column, wide extends to about one and a half columns (up to
1040px), and full spans the whole chat pane. wide and full shrink with
the pane, so on a narrow window both end up as wide as the pane. The tier is
stored with the message, so a view keeps its width across reloads. Only the
inline card on the desktop chat honours it; the expanded and docked views and
the mobile chat always fill the space they have.
send_decision is non-blocking: the agent announces choices it made on the
user's behalf and keeps working, rather than putting a question to the user the
way cortex_ask_user does. Each decision (up to 10 per call, title ≤120 chars, other fields ≤1000)
renders as a collapsed card in the chat; opening it shows context, decision, and
reasoning. The user can approve (recorded only — nothing is sent to the agent),
request an explanation, or propose a revision; the latter two compose a
templated message that is both recorded on the decision and delivered to the
agent as an ordinary user message. Decisions and their action log persist in the
conversation history, so cards keep their state across reloads and devices.
board_open opens the status page of a long task: who is bound to it, how
agents write it, and how it shows in the chat and the workbench are described
under Boards.
The bundle is loaded by agent-server/src/domain/mcp/bundled-server.ts.
The tools are in agent-server/src/domain/mcp/tools/ui-file.ts,
agent-server/src/domain/mcp/tools/ui-view.ts,
agent-server/src/domain/mcp/tools/ui-decision.ts, and
agent-server/src/domain/mcp/tools/ui-board.ts.
cortex-interaction-bridge¶
Direct Claude TUI sessions, user-initiated direct Claude print sessions, and
user-initiated direct PI sessions select this interaction bundle in their
single Cortex MCP server. Where it is loaded, Claude's native
AskUserQuestion, EnterPlanMode and ExitPlanMode are removed, so every
question and plan goes through these tools.
| Tool | Parameters | Description |
|---|---|---|
cortex_plan_enter |
reasoning? |
Enters the shared read-only planning protocol: investigate, write the plan under plan/, implement only after approval |
cortex_plan_exit |
plan_file_path, summary? |
Reads the plan file, submits it for human approval, and blocks until resolved |
cortex_ask_user |
questions[] (each question, header?, options?[] of {label, description?}, multiSelect?), level? (info, warning, error; warn is an alias), blocking? |
Asks one or more free-text or multiple-choice questions through the session platform, waiting for the answers or continuing without them |
cortex_ask_user posts its question card the same way in either mode; the
blocking parameter decides how the answer comes back. Blocking is the default:
the tool call waits up to the 30-minute interaction TTL and returns the answers
in its own tool_result, so the agent cannot proceed without them. With
blocking: false the call returns as soon as the card is posted and the agent
keeps working; when the human answers, the answer is delivered to the session as
an ordinary user message — folded into the running turn if one is still live,
otherwise opening a fresh turn. An unanswered non-blocking card expires on the
same TTL and simply never produces a message. In the workbench, only a blocking
question marks a session as awaiting user action; a session that asked without
blocking keeps rendering as running.
How the cards look on each platform, and how plan approval and questions route
in team mode, is described in
safety-and-approvals.md.
The plan and question handlers are in
agent-server/src/domain/mcp/tools/interaction-plan.ts and
agent-server/src/domain/mcp/tools/interaction-ask.ts.
MCP configuration files¶
Cortex generates the Claude MCP config files at startup (via
agent-server/src/core/config-generator.ts and the ensureMcpConfig() call
in agent-server/src/entry/startup-helpers.ts). A sealed home
(CORTEX_CONFIG_IMMUTABLE=1) keeps the files it was given.
| File | Purpose | Server key | Logical bundles |
|---|---|---|---|
$CORTEX_HOME/config/mcp-config.json |
The direct composition every ordinary Claude session uses |
cortex-core |
core + ext |
$CORTEX_HOME/config/mcp-config-empty.json |
The none composition: no MCP servers |
— | — |
$CORTEX_HOME/config/mcp-config-core.json |
Explicit restricted composition | cortex-core |
core |
$CORTEX_HOME/config/mcp-config-interaction.json |
Explicit interaction composition | cortex-interaction-bridge |
interaction |
$CORTEX_HOME/config/mcp-config-slack.json |
Explicit Slack composition | cortex-slack |
Slack |
$CORTEX_HOME/config/mcp-config-feishu.json |
Explicit Feishu composition | cortex-feishu |
Feishu |
$CORTEX_HOME/config/mcp-config-web.json |
Explicit Web composition | cortex-web |
Web |
Claude sessions load mcp-config.json or mcp-config-empty.json; the other
five serve callers that pass explicit config paths. Each file follows Claude
Code's standard MCP config format:
{
"mcpServers": {
"cortex-core": {
"command": "node",
"args": [
"<install>/agent-server/dist/domain/mcp/bundled-server.js",
"[\"cortex-core\",\"cortex-ext\"]"
],
"cwd": "<install>/agent-server"
}
}
}
The config files are rewritten on every agent-server startup, so manual edits to them do not persist. To customize MCP configuration, use a plugin (see Plugin-provided MCP servers).
A spawn that runs under a tool allowlist — a subagent, or an agent role that
declares one — gets a merged config written to
$CORTEX_HOME/config/mcp-tool-gates/mcp-composition-<digest>.json, carrying
the allowlist to the bundled server.
How the right config gets selected¶
In agent-adapter/claude/spawn-args.ts, a session on the direct composition
loads mcp-config.json, and one on none loads mcp-config-empty.json with
--strict-mcp-config. The Claude adapter writes the final logical selection
to CORTEX_MCP_BUNDLES for the stdio child it spawns: core and ext, plus the
interaction bundle for user-initiated sessions, the workers bundle for a team
lead, the worker-report bundle for a worker, and the platform bundle matching
the session's channel (slack:, feishu: or web:). The plugin MCP
configuration and, for a browser-enabled direct session, the browser MCP
config are added as separate config files.
PI's bridge computes the same logical selection from the session's own
environment and hands it directly to the in-process bundled server, alongside
one independent connection per plugin server; on PI the interaction, workers
and worker-report bundles require the direct composition. Subagents run
under an allowlist: Claude subagents receive core (without agent and
agent_stop) and ext, and PI subagents receive core alone, with no plugin
servers. Allowlists are validated against the selected bundles before the
bundled server registers tools.
How MCP tools communicate with agent-server¶
User-provided stdio MCP entries are separate child processes under both backends, and Claude Code sessions run the Cortex-owned MCP server as a child too. Cortex MCP tools never reach agent-server in-process state (WebSocket connections, running sessions, waitpoints) directly, wherever their server runs. They communicate through two paths:
-
Loopback webhook — most tools POST to the server's webhook at
http://127.0.0.1:<WEBHOOK_PORT>(default 3001), authenticated with theCORTEX_WEBHOOK_TOKENbearer token in thex-cortex-tokenheader. Remote machine tools use/webhook/remote-command, whose handler inagent-server/src/orchestration/routing/webhook.tsforwards the request toclient-manager.sendCommand()and on over WebSocket to the device. The subagent, team, board, waitpoint, Web delivery (ui-file,ui-view,ui-decision) and interaction (/hook/ask-user-question,/hook/exit-plan-mode) tools each have their own route. -
Store reads —
cost_queryreads cost records (data/costs.jsonl) through the same repository layer as the main server, andquery_executionsreads the execution registry (data/executions.json). Neither writes.
MCP calls and loopback HTTP requests share a 30-minute-30-second infrastructure
deadline. Tool-specific business deadlines remain authoritative: shell commands
use their timeout argument, while human interactions retain their 30-minute
response window. The extra 30 seconds lets a business timeout return
its result before an outer transport deadline closes the request.
Plugin-provided MCP servers¶
Environment-scoped third-party MCP belongs in a portable Agent Plugins package under $CORTEX_HOME/plugins/<plugin-id>/. The package declares servers in root mcp.json, and the Plugins tab of an execution environment (Settings → Environments) assigns the package to it. The schema supports stdio, streamable-http, and legacy sse; Skills and Plugins documents the complete package and trust model.
A legacy plugin directory is passed through to its backend as it is. Claude can load a Claude-native root .mcp.json from such a directory, but Cortex does not inventory, summarize, or acknowledgment-gate those native servers, and PI does not receive them. The guarantees in this section apply to portable root mcp.json only (agent-server/src/domain/plugins/runtime.ts; agent-server/src/agent-adapter/claude/spawn-args.ts).
Cortex validates and normalizes the package once at spawn time. Claude receives a private supplemental configuration layered after the normal Cortex files. Stdio entries remain separate processes; each remote entry becomes a local stdio proxy whose private configuration holds its URL and headers. PI receives the normalized server list with its session request, and its bridge opens each entry's own transport — a stdio child for a stdio entry, a direct HTTP or SSE connection for a remote one. Both remote paths use the same manual-redirect fetch and reject every redirect before a configured header or request body can be replayed. Connection and tool registration are isolated per server. Materialization follows declared dependencies: unavailable plugin-scoped PLUGIN_DATA omits its stdio dependents while preserving remote MCP, skills, and bundled tools (agent-server/src/domain/plugins/runtime.ts; agent-server/src/agent-adapter/claude/mcp-config.ts; agent-server/src/agent-adapter/claude/remote-mcp-proxy.ts; agent-server/src/agent-adapter/pi/mcp-bridge.ts).
Portable MCP is omitted when the resolved MCP composition is none, and subagents load no plugins at all. Top-level Claude and PI sessions receive it only through an assigned plugin (agent-server/src/domain/plugins/runtime.ts; agent-server/src/agent-adapter/pi/adapter.ts; agent-server/src/agent-adapter/pi/mcp-bridge.ts).
The plugin catalog and Settings API expose sanitized summaries. Stdio summaries contain the executable basename, argument count, and environment key names; remote summaries contain the origin and header names. Environment values, full remote URLs, and header values remain server-side (agent-server/src/domain/plugins/mcp.ts; agent-server/src/domain/ui-service/plugins-shared.ts). Installed stdio commands and their working directories remain administrator-trusted package inputs; the private config and per-server isolation are not a code sandbox.
Global custom MCP servers¶
The files under $CORTEX_HOME/config/mcp-config*.json describe Cortex's global and session-composed MCP layers. They are rewritten at server startup, so direct edits are temporary. A persistent global server requires an explicit builder and privilege-composition change in agent-server/src/core/config-generator.ts; it does not belong in an assignment-scoped plugin.
This distinction keeps global Cortex privileges separate from administrator-installed plugin capabilities. Use a portable plugin when the server should follow environment assignment, and change the global builders only when every eligible session composition should receive the server.
Permission model¶
MCP tools cross the trust boundary from the agent process into agent-server
internals and remote machines. Installed plugins are administrator-trusted code. For portable root mcp.json,
the assignment confirmation makes the capability addition explicit, but it is
not a sandbox or a separate authorization boundary. Legacy Claude-native MCP
configuration remains outside that confirmation. Cortex applies the following
controls:
-
Registration-level availability — the bundled Cortex server registers only the logical surfaces selected for that session, and an optional canonical tool allowlist filters them further. Top-level sessions receive core and ext tools, user-initiated sessions add the interaction tools, a team lead adds the worker tools, a worker adds
mark_routine, and the session's platform adds its delivery tools. Subagents receive no delegation tools. -
A separate Claude configuration — every Claude process Cortex starts runs with
CLAUDE_CONFIG_DIR=$CORTEX_HOME/data/claude, so MCP servers and plugins configured for your terminal's Claude Code (~/.claude) are not part of a Cortex session. An execution environment'ssettingSourcescan narrow which settings files Claude reads further. Claude-native.mcp.jsoninside an explicitly assigned legacy plugin directory is still loaded by Claude. -
Bypass permissions — Claude Code is spawned with
--dangerously-skip-permissions --permission-mode bypassPermissions, meaning it won't prompt for each MCP tool call. Access control happens at the MCP tool implementation level, throughagent:pre-toolhooks, and through the server's webhook token. -
Network boundary — MCP tools that talk to remote machines go through the client-manager's WebSocket layer. Only devices registered in
machines.jsoncan connect, and only devices with an active WebSocket connection can receive commands.
Environment variables passed to MCP servers¶
The bundled Cortex server builds its per-session context from these variables. Under Claude they are set in the environment of the stdio child; under PI the same values are passed to the in-process server directly.
| Variable | Source | Used by |
|---|---|---|
SLACK_CHANNEL, FEISHU_CHANNEL |
The session's channel | Platform-bundle selection, interaction tools, file delivery, subagent and team routing |
SLACK_BOT_TOKEN |
Server environment | cortex-slack (slack_send_file) |
CORTEX_SESSION_ID |
Session context | Interaction tools, cortex_context, waitpoints, Web delivery |
CORTEX_PROJECT, CORTEX_BACKEND, CORTEX_MODEL, CORTEX_PROVIDER, CORTEX_THINKING, CORTEX_MODE |
Session context | cortex_context |
CORTEX_SCHEDULE_TASK_ID, CORTEX_CALLBACK_SOURCE |
Set for scheduled or callback-triggered runs | cortex_context |
CORTEX_WEBHOOK_TOKEN, WEBHOOK_PORT |
Server environment | Every tool that calls the loopback webhook |
CORTEX_MCP_BUNDLES |
Backend process composition | Bundle selection in the bundled server |
CORTEX_MCP_TOOL_ALLOWLIST |
Agent role or subagent spawn | Tool registration filter |
CORTEX_CLAUDE_MODEL, CORTEX_SUBAGENT_PI_MODELS, CORTEX_SUBAGENT_CLAUDE_MODELS |
Session model and model catalog | The agent and worker_spawn model descriptions |
PLUGIN_ROOT |
Resolved selected plugin root | Portable stdio plugin servers |
PLUGIN_DATA |
Private persistent per-plugin data directory | Portable stdio plugin servers |
Security considerations¶
MCP tools give the agent the ability to execute shell commands on remote machines, read and write files, delegate to subagents and workers, and post to the chat platforms. The security posture assumes:
- The
cortex-clientWebSocket port (3002) requiresCORTEX_CLIENT_TOKENon every connection, and is still best kept off the public internet: use Tailscale, a VPN, a tunnel, or localhost-only binding (see cross-machine.md for network topology options). - The webhook HTTP port (3001) is bound to
127.0.0.1only and requiresCORTEX_WEBHOOK_TOKEN— MCP servers talk to it via loopback, not over the network. - The agent operates within the same blast-radius safety boundaries documented in safety-and-approvals.md. MCP tools do not exempt a high-privilege operation from that boundary.