MCP Cordis
Categories:
Statuses:
mcp_cordis is a standalone stdio MCP server that mounts runtime JavaScript
packages through Cordis.
It is intentionally an MCP server, not a Codex plugin bundle.
Reusable definitions are ordinary ESM files in
projects/mcp_cordis/plugins, listed by projects/mcp_cordis/cordis.yaml.
Disposable definitions use the same layout under
out/<task>/mcp_cordis/runs/<run>/. Each run writes a bounded manifest with
explicit task, run, worker, information, budget, retention, lock, and cleanup
fields. AGENT_TASK_ID, AGENT_RUN_ID, and AGENT_WORKER_ID may provide
stable identities; the launcher otherwise creates process-scoped identities.
Every
package is addressed by both scope and name, so a scratch package never
silently shadows a reusable package.
Run
The repository’s .codex/config.toml registers mcp_cordis as a
project-scoped stdio server. Codex loads that file for a trusted workspace and
finds the active Git worktree before starting the server. Separate clones and
worktrees therefore use their own source, projects/mcp_cordis packages, and
task/run-namespaced scratch packages. Trusting the repository’s root checkout
also covers its linked worktrees; a glob trust entry is neither needed nor
supported. A new Codex session is needed after the MCP registration itself is
first added; package changes after that do not require another session.
The registration calls cmd/mcp_cordis/launch.sh. With a current installed
bazel_agent, the launcher selects a content-addressed runtime from the
per-user tool cache and executes it directly. The first exact source version
is built and atomically installed under a per-key lock; subsequent worktrees
with the same inputs do not start Bazel or load a configured graph. An older
runner falls back to asking Bazel for a launch script under the task’s ignored
out directory. Either path releases Bazel’s output-base lock before the
long-lived stdio server starts, so builds and tests can run normally while
Codex remains connected.
The cached artifact contains the stable server runtime and pinned JavaScript
dependencies, but not cordis.yaml or reusable plugins. It always reads those
from the explicit active workspace, so editing a package changes live behavior
without repackaging the runtime or selecting a new cache key.
The same project configuration starts an optional asynchronous SessionStart
hook that starts the worktree’s Bazel server and warms repo_delivery. It
produces no session context, ignores failure, and never queries the whole
workspace graph. Cordis itself is already warmed by its MCP launch path, so the
hook does not start a duplicate Cordis build.
To build and run the server directly from the repository root:
bazel_agent bazel run //projects/mcp_cordis:mcp_cordis -- \
--workspace-root "$PWD" \
--task-id example-task \
--run-id example-run
The workspace root is mandatory unless BUILD_WORKSPACE_DIRECTORY is
present. The checked-in launcher resolves the current Git worktree explicitly
and supplies that path to the server.
The fixed cordis_* tools define, start, inspect, invoke, update, stop,
remove, and promote packages without reconnecting the MCP client. Package
handlers are called through cordis_invoke; this remains reliable even when
an MCP client caches its initial tool list.
Cordis configuration and plugin format
cordis.yaml uses the standard Cordis Include entry-list format:
- id: hello
name: ./plugins/hello.mjs
The referenced file is a normal ESM Cordis plugin:
const plugin = {
description: "Provide a greeting.",
apply(ctx) {
ctx.tool(
{
name: "hello_world",
description: "Return a greeting.",
inputSchema: {
type: "object",
properties: { name: { type: "string" } },
additionalProperties: false,
},
},
({ name = "world" }) => ({ greeting: `Hello, ${name}!` }),
);
},
};
plugin.apply.description = plugin.description;
export default plugin;
Cordis normalizes an object plugin to its apply callback. Attaching the
optional package description to that callback exposes it through
cordis_list and cordis_inspect; tool descriptions remain part of each
ctx.tool() definition.
The server mounts the official Cordis Loader, Include, and HMR services.
cordis_define syntax-checks and atomically persists the ordinary module.
Creating or enabling an entry refreshes any cached module through Cordis HMR,
then uses the public Include refresh API and waits for activation. Updating an
already-running entry returns activation: "pending"; poll cordis_invoke or
cordis_list_tools until the new behavior is visible.
The reproducibly pinned HMR package carries a focused pnpm patch that
serializes module reloads and drains source changes arriving during an
in-flight reload, so the latest persisted source is not lost.
Syntax errors are rejected before the file changes. Evaluation and apply()
failures follow native Cordis HMR behavior; the wrapper does not add a second
activation transaction around them. It also does not inject source markers,
inspect Loader caches, correlate watcher events, or maintain its own source
rollback/version store. Reusable history is normal Git history. Manual edits
to watched plugin files are also picked up by Cordis HMR.
Runtime modules use normal Cordis semantics, including static imports,
top-level await, and asynchronous apply(ctx, config). Package code is
trusted: a never-settling module evaluation or activation can therefore stall
Cordis lifecycle work. The stdio launcher reserves its protocol stream and
redirects package stdout to stderr, keeping accidental console.log() calls
off the JSON-RPC wire.
The package context exposes ctx.workspaceRoot, ctx.resolveWorkspace(),
ctx.readText(), and structured ctx.exec() in addition to ctx.tool().
ctx.exec() returns code, signal, stdout, stderr, truncated, and
outputLimitExceeded; maxBytes is a combined stdout/stderr budget. By
default, exceeding that budget or producing invalid UTF-8 rejects with
EXEC_OUTPUT_LIMIT or EXEC_INVALID_UTF8. Packages that explicitly set
allowTruncatedOutput: true instead receive the valid retained prefix with
truncated set; outputLimitExceeded distinguishes the byte cap from UTF-8
loss. A Fiber-owned supervisor admits each launch atomically, and results
settle only after the direct child and every live member of its original Linux
process group have stopped. Limits, timeouts, and plugin disposal use the same
cleanup path. A process that deliberately creates a new session escapes that
group and is outside this trusted-package contract. ctx.exec() therefore
fails closed with EXEC_UNSUPPORTED_PLATFORM away from Linux. Package code
also has normal Node built-ins; this host is a reliability boundary, not a
security sandbox.
cordis_invoke.timeout_ms bounds how long the gateway waits for a result; it
does not cancel an already admitted JavaScript handler. The handler keeps its
Fiber lease until it finishes, so stop, remove, and shutdown wait for it.
Cordis HMR waits for a retired Fiber to finish draining before it activates
and publishes the replacement, so a live invocation can delay a reload. Any
ctx.exec() launched by a timed-out invocation is cancelled and its process
group is confirmed stopped before the timeout response settles.
Included packages
repo_context: bounded repository reads and searches.git_worktree: read-only branch, status, log, and comparison snapshots.network_probe: DNS, TCP/TLS, and HTTP diagnostics.
These were selected from aggregate recurring task categories in recent local sessions. No transcript content, credentials, or private outputs are included.
Durable goal
Future runtime-extension work should resume from the maintained runtime extensions goal, which records acceptance criteria, decisions, failed attempts, and supporting evidence.
Dependency notices
The runtime directly uses @deepseek-ai/cordis, its official Loader, Include,
HMR, and Timer plugins, and the Model Context Protocol TypeScript SDK. Their
license texts are retained in the resolved package artifacts by the pinned
pnpm/Bazel dependency graph.