Skip to main content
Glama
JGCoelho

simple-sage-mcp

by JGCoelho

simple-sage-mcp

An MCP server that gives an agent a persistent SageMath kernel over stdio: one long-lived Sage process that code is run in, interrupted, inspected and stopped. Work outlives the call that started it, and the kernel outlives the server that spawned it.

Quickstart

SageMath must be installed, and its own interpreter must be able to import sage.all. From a checkout:

./install.sh

That finds a working interpreter, writes the sage entry into the mcp.json next to the install target, and proves the registration works by spawning the server and running 2+2 through it. Another client needs the block it prints:

{
  "mcpServers": {
    "sage": {
      "command": "/path/to/python-with-mcp/bin/python",
      "args": ["/path/to/sage-mcp/sage_mcp.py"],
      "env": {"SAGE_PYTHON": "/path/to/sage-env/bin/python", "SAGE_CWD": "/tmp"},
      "timeout": 600
    }
  }
}

pip install git+https://github.com/JGCoelho/simple-sagemath-mcp works too. Check a registration at any time with ./install.sh --check or simple-sage-mcp-check /path/to/python /path/to/sage_mcp.py. sage_mcp.py --help lists the options and the environment variables.

Related MCP server: MCP SageMath Server

Scope and safety

This is a general kernel, not a curated math API, and not a sandbox. Code sent to exec, run_file or notebook runs as you, on your machine, with your files and your network: the kernel is an ordinary Sage process, and SAGE_MCP_LIMITS bounds its resources, not its privileges. There is no allow-list, no seccomp, no filesystem or network isolation, and nothing stops a job from reading ~/.ssh or writing anywhere you can. So: run work you would run yourself, do not drive it from untrusted input, and there is no HTTP transport to expose -- it is a stdio server over a private Unix socket, and that is the whole surface. Linux only: it relies on AF_UNIX, /proc and rlimits.

Persistent state and sessions

One kernel per state directory

The kernel is a separate process that the server spawns on demand and then talks to over a private socket. Its identity is the state directory, ${SAGE_MCP_STATE:-/tmp/sage-mcp-$UID}:

kernel.sock   the listener (0700, AF_UNIX -- nothing on loopback to dial)
kernel.key    32-byte HMAC key the clients must present (0600)
kernel.json   pid, start time, log path -- what `status` reads when the kernel is busy
running.json  the job in flight, rewritten per job, so `status`/`interrupt` still work
kernel.log    the kernel's own log (rotated at `SAGE_MCP_LOG_MAX`, 8 MiB by default)

Everything that points at that directory shares one kernel: several Pi sessions, subagents, a restarted server. Jobs queue on it and run one at a time, on the kernel's main thread. The first exec starts it (status says not running until then); when the last server exits and the kernel is idle it stops itself. Point two configurations at the same SAGE_MCP_STATE and they share the kernel; point them at different ones and they cannot see each other.

Nothing lives in the package directory, and nothing is stored per call: run_file and notebook work on the files you name, in place.

Sessions are namespaces inside that kernel

A session is a name for a namespace, not a process: session="default" unless you say otherwise, any other name is created the first time it is used. Each namespace starts as a copy of from sage.all import *, and from then on holds only what that session's own code defines. So a = 1 in session work is invisible in default, and both persist between calls -- the whole point of the kernel.

reset empties one namespace (it does not wait for the session to go idle, so do not reset a session whose job you still want). drop frees the name, refusing a session with a job in flight. Neither restarts the kernel, and neither touches other sessions.

What survives what

event

variables

job in flight

counters

exec hits its timeout_s

kept

keeps running; poll collects it

live

the agent's turn ends

kept

keeps running

live

the MCP server exits or is restarted

kept -- the next server adopts the kernel

keeps running; the new server's poll collects it

live in the kernel

interrupt

kept

fails with KeyboardInterrupt

interrupted + 1

notebook / run_file

unaffected

unaffected (different process)

--

interrupt(force=true)

lost

stopped mid-flight

reset with the kernel

the kernel exits on its own, or /tmp is cleaned

lost

lost

lost

The kernel is the authority on all of it; the server keeps no state of its own except its identity and its view of the socket. That is why a poll works after a server restart, and why counts (jobs: N run, ...) reset exactly when the kernel does -- counters_since says since when.

Tools

tool

params (required → *)

returns

exec

code*, session="default", timeout_s=120 (0 = return at once), preparse=true

stdout, then the repr of the last expression; on timeout the partial output plus a job id to poll

poll

job_id*, wait_s=0, since=0

new output since the offset, ending in [job finished in 6.0s] or [still running 12s; ...]

status

--

kernel/session/job/client state, last error, limits, log path

interrupt

force=false

SIGINT the running job (it fails with KeyboardInterrupt, variables kept); force=true stops the kernel and loses them

reset / drop

session; sessions=[], all=false

empty a namespace; free idle sessions

run_file

path*, lang="auto", args=[], timeout_s=120, stdin, cwd

exit code and duration, then stdout/stderr inline; a non-zero exit is an error carrying both

notebook

action, path, out, in_place, cell_timeout_s=120, timeout_s

check / extract / clear / run; all four run in Sage's interpreter, and the tool is advertised only when that interpreter has nbformat+nbconvert+nbclient

Five short transcripts with the real output -- a job outliving its exec, an interrupt, run_file, sessions, notebooks -- are in examples/.

Jobs, and why poll exists

exec's timeout cancels nothing: a Gröbner basis or a graph enumeration that takes twenty minutes is normal work for Sage, and a timeout that threw it away would be a bug. So exec answers with a job id (or the finished output, if it got there within timeout_s), and poll(job_id, since=N) returns the output produced since offset N -- incremental, never a re-run. To cancel, do it explicitly: interrupt, then poll to collect the KeyboardInterrupt. Silence is never read as "cancel".

A wait can be longer than your client's request timeout, so exec, poll, run_file and notebook send an MCP progress notification every 30 s while they wait; a progress notification resets that timeout (pi's default is 60 s, and its mcp.json entry for sage sets "timeout": 600). A client with a tighter timeout lowers the interval with SAGE_MCP_PROGRESS_SLICE. A client that asks for no progress keeps the old behaviour exactly, which is why timeout_s=0 plus poll is still the shape for a job that may run for hours.

interrupt is owner-checked: only the client whose job is running may send it. interrupt(force=true) is not, on purpose -- it is the escape hatch for a kernel wedged past answering its owner, so it is machine-level: any client sharing the state directory can stop the kernel and destroy another's work. DESIGN.md, the tool description and a test say so, so it cannot change quietly.

Why Sage, not Python

The kernel preparses by default, so this is Sage's REPL, not Python with sage.all imported: 2^10 is 1024, 1/2 stays the rational 1/2, type(2) is Integer, and R.<x> = QQ[] works. An ipykernel with from sage.all import * silently gets all of these wrong.

Architecture

sage_mcp.py is the stdio front end, built on the official MCP Python SDK (MCPServer), so schemas, validation and the protocol come from the SDK. sage_client.py spawns and talks to the kernel: it holds a lock, presents the authkey, and distinguishes busy (alive, not answering -- a running computation holds the GIL) from gone, because respawning a busy kernel is how twenty minutes of work disappears. sage_kernel.py is the long-lived process: it runs jobs on the main thread so cysignals-based Sage (Singular, PARI, GAP) behaves, publishes the in-flight job to running.json, and refuses to let a job's failure or an idle SIGINT take the kernel with it. subproc.py is the child-process core behind run_file and notebook, limits.py the SAGE_MCP_LIMITS knob. See DESIGN.md for the reasoning and tests/conformance.py for the protocol contract.

sage://kernel/status (application/json) is the machine-readable form of status: pid, state, uptime, restarts, the jobs counters, last_error with the job and session that produced it, sessions, limits (with source: kernel when the kernel answered, configured when it did not), and the log tail -- read from the file, so it answers while a job runs.

Requirements

  • Linux.

  • SageMath installed, separately from this server. pip/uvx install the server only: the kernel is Sage's own interpreter. If it cannot be found, both status (sage interpreter: NOT FOUND -- …) and exec say so, and exec's error names the three discovery routes and the one-line fix.

  • Two interpreters, on purpose. The server is any Python 3.10+ with the mcp package, and nothing else; the kernel is Sage's own interpreter, found at runtime by SAGE_PYTHON, then sage -c 'import sys;print(sys.executable)', then the python beside sage on PATH. A Sage crash cannot take the transport down, and mcp never has to be installed into the Sage environment.

  • notebook needs nbformat+nbconvert+nbclient in the kernel's interpreter (they ship with SageMath).

Configuration

variable

default

meaning

SAGE_PYTHON

discovered

interpreter that must import sage.all -- the kernel

SAGE_MCP_STATE

/tmp/sage-mcp-$UID

the state directory above; the kernel's identity

SAGE_CWD

server's cwd

working directory for exec and run_file

SAGE_MCP_LIMITS

cpu=off,as=8G,fsize=1G,nproc=off

per-process limits, below

SAGE_MCP_GAP_LIMIT

3 (0 disables)

GAP crash-loop guard, below

SAGE_MCP_LOG_MAX

8 MiB (0 disables)

rotate kernel.log to kernel.log.1, one generation

SAGE_MCP_PROGRESS_SLICE

30

seconds between progress reports while exec/poll/run_file/notebook wait; lower it for a client whose request timeout is tighter than that

SAGE_MCP_LIMITS bounds what the kernel and the children run_file/notebook spawn may consume, so a runaway job fails instead of taking the machine down:

limit

default

scope

on trip

cpu

off

kernel, from just after Sage's import

SIGXCPU caught: the job fails with ResourceLimitError

as

8 GiB

kernel + children

MemoryError → job error

fsize

1 GiB

kernel + children

SIGXFSZ/EFBIG → job error

nproc

off

per UID, machine-wide

fork/Popen gets EAGAIN → that job or child spawn fails

core

always 0

kernel + children

no core dumps

Sizes take K/M/G; off/0 leaves the inherited limit alone; unmentioned keys keep their default, so SAGE_MCP_LIMITS="cpu=60" only adds a CPU budget. A typo is an error naming the knob. Only the soft limit is lowered, so a job that needs more room gets it by restarting the kernel (interrupt(force=true)) under a different SAGE_MCP_LIMITS. nproc is a margin over the tasks your UID already runs, armed before each job -- not a count of the kernel's children. A job that trips a limit fails with ResourceLimitError: RLIMIT_..., its text is prefixed with [limits], the kernel survives, and status says which limit applies.

SAGE_MCP_GAP_LIMIT is the GAP crash-loop guard. structure_description() on hostile input makes GAP crash-restart in a loop, forking a process per crash, and no timeout catches it because the cell never finishes. The kernel counts the Gap crashed / Restarting Gap markers as they are written and, at the limit, SIGINTs that job once -- the same signal as interrupt, so it ends as a KeyboardInterrupt, the session keeps its variables, and poll collects it. The result is prefixed with [gap-guard] interrupted after N 'Gap crashed' markers ..., the trip is logged, and status counts it. The evidence is the writer, not the text: only a write from sage/interfaces/gap.py (stdout or stderr) -- or a stderr write from a job that has already written through it -- counts, so echoing the words cannot trip it, and importing the module (which from sage.all import * does for every session) is not evidence. If the job catches the interrupt and runs on, the result says SIGINT was sent but the job continued instead of claiming an interruption that did not happen.

Tests

# 24 conformance cases, one row each, ~70 s
/path/to/sage-env/bin/python tests/conformance.py        # or --only C13,C15

# 154 checks over MCP stdio through the official client, ~220 s
/path/to/sage-env/bin/python tests/acceptance.py

# one suite instead of all of them (~7 s): the names come from --list-suites
/path/to/sage-env/bin/python tests/acceptance.py --list-suites
/path/to/sage-env/bin/python tests/acceptance.py --suite discovery_visibility_suite

# 10 checks as a Sage script, ~10 s (`singular` must be on PATH)
PATH=/path/to/sage-env/bin:$PATH sage tests/interrupt_ownership.sage

# the frozen tools/list contract (8 tools, 0 mismatches)
/path/to/sage-env/bin/python tests/snapshot_tools.py --compare

acceptance.py guards what is easy to regress: busy-vs-dead, spawn serialisation, stale state files, dead launchers, an honest stop, output caps, since offsets, log isolation, shared-kernel ownership, socket privacy and the authkey, adoption across a server restart, the three discovery routes, a cancelled exec still being recoverable, the run_file contract, both halves of the two-interpreter rule for notebook, the GAP guard (writer-based, and honest about a swallowed signal), the limits (each fails its job and leaves the kernel alive), the status resource and its counters, and --check driving a registration in the client's own environment.

sage_mcp.py --check (behind install.sh --check and simple-sage-mcp-check) is the 8-check smoke test; its last check runs the C01/C04/C08/C17/C20 subset of the matrix against the registration -- its command, args and env exactly as written -- so a registration that only works because your shell exports SAGE_PYTHON is reported as broken, the way the client would find it. Without tests/ (pip/uvx ship none) that subset is SKIP and left out of the totals; a skipped check is never a pass.

Not in this release

Honest gaps, in the order they would be closed:

  • No CI. The suites run by hand (they are in Tests above). A realistic split, written down in PLAN.md §11: a fast job on GitHub-hosted runners with no SageMath (byte-compile every module, --help/--version, --suite cli_suite, the no-Sage diagnosis, and server.json against the registry schema -- ~1 minute), and a full job inside the sagemath/sagemath image for the kernel suites (~6 minutes plus the image pull). The fast job cannot run the conformance matrix: C01 asserts the eight tools, and without Sage notebook is correctly not advertised.

  • Not on PyPI yet. The wheel and sdist build clean (python -m build, twine check) and server.json is ready, but the release is deliberately unpublished for now; install from git. A published version is permanent, and the security posture ("no sandbox") has to be right before strangers run it.

  • No Docker image. The only way to get pip/uvx users without SageMath working, and the honest way to make "reproducible" true.

  • tests/ is not shipped, so --check's conformance subset reports SKIP for an installed package (never counted as a pass).

  • One maintainer, Linux only (AF_UNIX, /proc, rlimits).

  • Two nproc checks depend on the machine-wide task count of your UID, so a heavily loaded machine can still flake them; the test margin is 16 with 56 fork attempts to make the drift irrelevant.

License

MIT -- see LICENSE. What changed in each release: CHANGELOG.md.

Available Tools

7 tools
dropA

Free idle sessions; refuses one with a queued/running job (kept, and the job id is reported). all=true drops every idle session.

NameRequiredDescriptionDefault
allNoDrop every session that has no queued/running job.
sessionsNoSession names to drop.

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and discloses the most non-obvious trait: partial-failure semantics, where busy sessions are kept and their job id is reported. It omits what happens to the underlying processes and whether the operation is reversible, which prevents a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the main action, and a qualifier that keeps the busy-session guarantee prominent. The phrasing is telegraphic ('Free idle sessions') but nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter, no-output-schema tool, the description covers the drop scope, the refusal behavior, and the job id feedback, so an agent can call it and interpret the result. It could say more about side effects on running processes, but nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and both parameters are documented there. The description restates that all=true covers every idle session, adding confirmation but no syntax or interaction detail beyond the schema. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Concrete verb plus resource: frees idle sessions and states the scope of 'idle'. The bare name 'drop' is ambiguous, but the description resolves it immediately. It does not explicitly differentiate from siblings like reset or interrupt, so it stops short of 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The condition for all=true is stated, and the refusal of busy sessions is implied as the boundary. There is no explicit routing to alternatives such as interrupt or reset when a session is busy, so the agent must infer which sibling to use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

execA

Run Sage/Python in the long-lived kernel: stdout, then the repr of the last expression (or '(no output)'). Code runs on the kernel's main thread, one job at a time, so cysignals Sage code works; state persists per session. timeout_s is NOT a computation limit: on expiry you get the partial output and a job id, and the job keeps running (collect it with poll). timeout_s=0 returns at once. A submitted job waits behind the running one ("[queued: another job is running]"); if that one holds the GIL the submit itself is refused after ~10 s ("[kernel busy ...]", the job was not submitted) -- check status or interrupt, then retry.

NameRequiredDescriptionDefault
codeYesCode to execute.
sessionNoNamespace to run in. State persists per session between exec calls.default
preparseNoApply the Sage preparser (2^10 -> 1024, R.<x> = ...). false runs raw Python (2^10 is XOR).
timeout_sNoSeconds to wait for the result; 0 = return at once. Never cancels the job.

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does it well: discloses single-threaded one-job-at-a-time execution, per-session state persistence, that timeout is never a cancellation, partial-output-plus-job-id semantics, queueing behavior, and the ~10s GIL refusal message. This is exactly the operational context an agent needs before calling.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with what the tool does and what it returns, then behavioral detail. Dense but nearly every clause carries operational information; the long em-dash-heavy paragraph is slightly harder to scan than separate sentences would be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, yet the description spells out the return format (stdout, last-expression repr, or '(no output)'). Combined with the timeout/queue/retry semantics, nothing an agent needs in order to call or recover from this tool is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds genuine meaning beyond the schema: timeout_s is explicitly not a computation limit and yields partial output plus a job id, and session persistence across calls is reinforced. It does not further explain preparse beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Run Sage/Python in the long-lived kernel') and even defines the output shape (stdout then last-expression repr). It distinguishes itself from poll/status/interrupt in the failure path, though it never contrasts itself with the run_file sibling, keeping it short of a full 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete conditions: timeout_s=0 returns at once, on expiry you get partial output plus a job id and use poll to collect, and what to do when the kernel is busy ('check status or interrupt, then retry'). It lacks explicit guidance on when to prefer run_file over exec.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

interruptA

SIGINT the running computation: the job fails with KeyboardInterrupt and the session KEEPS its state -- the first thing to try for a runaway job. Refused when the running job belongs to another MCP client; a no-op when idle: SIGINT can never kill the kernel. force=true is the last resort instead: stop the whole kernel (graceful shutdown, then SIGTERM, then SIGKILL), losing every session's variables -- for a computation that ignores SIGINT, e.g. a wedged C-level loop. The next exec starts a fresh kernel and says so; if the signal fails the state files are kept and the pid reported, so a live kernel is never orphaned.

NameRequiredDescriptionDefault
forceNoStop the kernel itself (all state is lost), not just the job.

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: it explains the signal semantics, that state survives, that SIGINT can never kill the kernel, the exact force escalation sequence (graceful shutdown, SIGTERM, SIGKILL), the loss of every session's variables, and the failure-path guarantee that state files are kept and the pid reported. This is unusually complete behavioral disclosure for a mutation-capable tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The primary action is front-loaded in the first clause, and nearly every clause adds distinct information (effects, refusals, escalation, failure path). The single dense paragraph with multiple em-dash clauses is information-rich but slightly harder to scan than a broken-out structure would be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter tool with no output schema and no annotations, the description covers the success path, the idle and cross-client edge cases, the forced escalation path, and what happens on signal failure. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, and the schema already defines force as stopping the kernel with all state lost. The description goes further by giving the escalation rationale for force=true and the precise consequence (losing every session's variables), which is real added meaning rather than restatement. It stops short of documenting the default value or any other input nuance, so it is strong but not exhaustive.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States the specific action (SIGINT the running computation) with its precise effect (job fails with KeyboardInterrupt, session keeps state), and explicitly contrasts it with the force variant that stops the kernel. An agent can distinguish this from run_file, poll, status, reset, drop, and exec without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the primary case ('the first thing to try for a runaway job') and the escalation case for force=true ('last resort... a computation that ignores SIGINT, e.g. a wedged C-level loop'). It also states two exclusion conditions -- refused when the job belongs to another MCP client, no-op when idle -- which is exactly the when/when-not guidance an agent needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

pollA

Collect a job started by exec. since = the offset the previous call printed (0 to start), so you get only new output. wait_s blocks until the job finishes or that many seconds pass (0 = return immediately, the default).

NameRequiredDescriptionDefault
sinceNoCharacter offset already consumed; the previous call prints the next value.
job_idYesJob id such as j7.
wait_sNoBlock up to this many seconds waiting for the job to finish (0 = return immediately, the default).

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does disclose key behaviors: wait_s blocks up to N seconds, 0 returns immediately and is the default, and since yields only new output. It omits what happens on polling a finished job, whether output is retained or consumed, and any permission/lifetime constraints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short clauses with the core purpose front-loaded and no filler. The 'since = the offset...' construction is slightly telegraphic, but every sentence carries information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description must convey returns; it implies new output text plus a next offset value ('the previous call printed'), which is enough to drive the polling loop. It could state the return shape more directly, but an agent has what it needs to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description largely restates the schema for 'since' and 'wait_s' (both already documented including the 0 default), adding only the operational framing of chaining offsets across calls.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Collect a job started by exec') and ties itself explicitly to the exec sibling, making clear it retrieves output rather than launching or reporting status. An agent can distinguish poll from status and exec from the description alone.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Establishes the prerequisite workflow (must follow exec) and explains the incremental-polling loop via 'since' and the blocking option via 'wait_s'. It stops short of explicit when-not guidance such as whether to prefer status for a one-shot check or when polling is unnecessary.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

resetA

Empty a session's namespace but keep the session (see drop).

NameRequiredDescriptionDefault
sessionNoNamespace to run in. State persists per session between exec calls.default

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the behavioral load. It usefully distinguishes what is destroyed (namespace contents) from what survives (the session), which is the key risk distinction for a destructive tool. It says nothing about reversibility, permissions, or confirmation requirements, leaving real gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One front-loaded sentence with zero filler: the action comes first and the retention caveat plus sibling pointer follow. Nothing could be removed without losing information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive, no-required-param tool with no annotations and no output schema, the description covers the core destroy/preserve semantics but omits irreversibility and any return indication. Adequate to call correctly, not complete enough to predict consequences.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single optional 'session' parameter is fully documented there, including its default and persistence behavior. The description adds no parameter meaning of its own, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb + resource: 'Empty a session's namespace' says exactly what the operation does to what. It implicitly contrasts with the sibling tool 'drop' by noting the session is kept, though the contrast is compressed into a parenthetical.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The clause 'but keep the session (see drop)' points the agent at the alternative and implicitly defines when to pick this over drop (you want the session retained). It does not spell out explicit exclusions or prerequisites, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_fileA

Run a file that already exists and return its output inline: exit code, duration, stdout and stderr. Nothing is copied or stored -- the file you name is the file that runs, with cwd = its own directory. lang=auto guesses from the extension and honours a #! for executables; lang=python uses Sage's own interpreter (so from sage.all import * works) and lang=sage shells out to sage. A non-zero exit or a timeout is an error, with the output attached; the timeout kills the whole process group.

NameRequiredDescriptionDefault
cwdNoWorking directory (default: the file's own directory).
argsNoArguments appended to the command line.
langNoInterpreter; auto uses the extension, then the #! line.
pathYesThe file to run (absolute or relative to this server's cwd).
stdinNoText fed to the file on stdin.
timeout_sNoSeconds before the process group is killed.

TDQS

A4.1/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the behavioral burden well. It discloses that output is returned inline, nothing is copied or stored, cwd defaults to the file's own directory, `lang=auto` and `lang=python`/`lang=sage` behave differently, non-zero exits and timeouts are treated as errors with output attached, and timeouts kill the whole process group.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action and return behavior, then adds execution semantics, interpreter details, and error handling. It is dense but every sentence contributes relevant information without obvious padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema and no annotations, so the description must cover return behavior and process execution semantics. It does this well, including output contents, error conditions, and timeout behavior, but it does not address edge cases such as output truncation, environment handling, or permission requirements.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already has 100% description coverage, so the baseline is 3. The description adds useful meaning beyond the schema, especially for `lang` behavior, cwd semantics, and the fact that timeout kills the entire process group, though it does not discuss `args` or `stdin`.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: run an existing file and return exit code, duration, stdout, and stderr. It is clearly distinct from general command execution because it emphasizes that the named file is the file that runs, with cwd defaulting to its directory, though it does not explicitly name the sibling `exec` tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the description: use this when running an existing file and wanting inline output. However, it gives no explicit when-not-to-use guidance and does not compare itself directly against sibling tools like `exec` or `poll`.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

statusB

Kernel state (running / busy / not running) with pid and uptime, one line per session with its name count, active jobs, the last error, the log path.

NameRequiredDescriptionDefault

No parameters

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It does disclose useful output detail beyond a label — the three possible state values, one line per session, and the presence of last-error and log-path fields — which implies a read-only observation tool. However, it never explicitly states that it has no side effects, does not require a session identifier, or what the output format/length looks like beyond 'one line per session'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single tight sentence that front-loads the most important element (kernel state and its enum values) and then lists the supplemental fields. Dense but every clause carries information; only the ambiguous 'its name count' phrase is slightly muddy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema and no annotations, so the description must convey return content — which it does reasonably well. But for a tool with six action siblings, the absence of any guidance on when to invoke it (e.g., before exec or after interrupt) leaves a genuine gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes no parameters, so the baseline is 4; the empty schema is fully consistent with a global status snapshot. The description reinforces this by describing a single aggregate report covering all sessions rather than anything that would need arguments.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description enumerates exactly what the tool reports: kernel state (running/busy/not running), pid, uptime, and per-session details including name count, active jobs, last error, and log path. That specificity clearly separates it from action-oriented siblings like interrupt, exec, and reset. It loses a point only because it is a noun-phrase fragment with no explicit verb such as 'reports' or 'returns'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no statement of when to call this versus the siblings, no mention of polling it before interrupt/reset, and no exclusions. The only implicit cue is that it is a state-inspection tool, which an agent must infer from the field list.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 7 tool updatesv1.0.0
    • First observeddrop
    • First observedexec
    • First observedinterrupt
    • First observedpoll
    • First observedreset
    • First observedrun_file
    • First observedstatus

TDQS

A3.9/5.0

Scored across 7 tools

Disambiguation4/5

Most tools map to distinct lifecycle operations: interrupt vs. drop, reset vs. drop, and poll vs. status are well separated. The main overlap is that exec and run_file both execute code, so an agent must rely on the descriptions to choose the right context.

Naming Consistency4/5

All names are lowercase and terse, with no casing inconsistency. However, the set mixes imperative verbs, one compound name (run_file), an abbreviation (exec), and a noun (status), so it is not a fully predictable verb_noun pattern.

Tool Count5/5

Seven tools is well within the healthy range for a kernel/session management server. Each tool earns its place by covering a distinct role: execute, run file, collect output, interrupt, inspect status, reset namespace, and drop sessions.

Completeness4/5

Core lifecycle coverage is strong: execution, async polling, interruption/kill, status, namespace reset, session drop, and file execution are all present. Minor gaps exist, such as no explicit session-creation or queued-job-cancel operation, but agents can work around them with exec, interrupt, and status.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers