Skip to main content
Glama

loommux

CI PyPI Python >=3.13 License

loommux is a Model Context Protocol (MCP) server for persistent, inspectable IPython work. A loommux server owns logical kernel resources on behalf of MCP sessions. Each resource has an independent namespace, execution history, client lease set, and replaceable kernel process. Python variables, imports, and definitions survive from one submitted cell to the next inside that selected resource.

The project is for MCP clients and agents that need more than a one-shot subprocess. It makes a running cell observable without losing it: callers can wait later, inspect its state, read a selected output stream by line range, search retained output, interrupt the active cell, or restart the kernel.

What It Provides

  • One main kernel per MCP session, private by default, shared by name when clients need one collaborative namespace, and child kernels that belong to it alone.

  • One persistent IPython session, execution sequence, and retained history per logical resource.

  • Activity, standard MCP-ping, or connection-bound leases with automatic orphan reclamation; a connection lease follows the client's open stream and alerts when the client stops behaving that way.

  • A strictly increasing positive integer execution coordinate for every accepted cell during its logical resource's lifetime.

  • In-memory output retained separately as combined, stdout, stderr, result, and traceback streams.

  • IOPub-order combined output, including IPython-style Out[execution]: labels for display results.

  • Terminal-formatted IOPub text normalized into ordinary append-only text transcripts before it reaches public output.

  • A non-blocking execution model: a tool-call timeout ends only that MCP call; it does not terminate the Python cell.

  • Explicit interrupt and kernel-reset operations, with preserved historical execution records after a reset.

  • A single MCP entrypoint with content-only defaults and an explicit structured result mode.

loommux is intentionally not a multi-user notebook service, a durable job queue, or a sandbox. Kernel state and execution records are memory-only and belong to their logical resource; server shutdown retires every resource.

Related MCP server: kernel-mcp

Requirements And Installation

loommux requires Python 3.13 or newer. The installed package brings the runtime dependencies needed to launch an IPython kernel.

python -m pip install loommux

Windows

Native Windows support covers Windows 10 and Windows 11 with CPython 3.13 or newer. Install into the interpreter that the MCP host will use:

py -m pip install loommux

The installed command is loommux.exe. Point an MCP host directly at that executable rather than through a shell wrapper, and use an absolute Windows workspace path for cwd:

{
  "mcpServers": {
    "loommux": {
      "command": "C:\\workspace\\.venv\\Scripts\\loommux.exe",
      "args": ["--result-mode", "structured"],
      "cwd": "C:\\workspace"
    }
  }
}

loommux launches the kernel with the same interpreter as loommux.exe, keeps its IPython and Jupyter state in a private temporary directory, and uses a Windows Job Object so restart and server shutdown also end child processes launched by the kernel. A submitted cell remains arbitrary Python: commands inside that cell must target the operating system on which the kernel is running. WSL is a separate Linux deployment, not a substitute for native Windows coverage. Because IPython kernels do not accept Ctrl+C through this entry point on Windows, interrupt replaces the private kernel after marking the active cell interrupted; later cells use the fresh kernel.

The package installs one console command:

loommux                   content-only results over Studio stdio
loommux --server          content-only results over Streamable HTTP

loommux uses an MCP Studio or host's child-process stdio connection by default. --server starts a Streamable HTTP service with configurable --host, --port, and --path. Both forms return only content by default. --result-mode structured is the explicit opt-in that additionally returns structuredContent.

For development, use uv:

git clone https://github.com/MichengLiang/loommux.git
cd loommux
uv sync --locked --group dev

Default Studio Connection

loommux defaults to MCP stdio transport and returns model-oriented content only. This is the Studio-compatible default and prevents a client from preferring raw structuredContent over the presentation intended for the model.

The server process's working directory is the default kernel workspace. A generic MCP configuration therefore assigns the desired project directory as the command's cwd:

{
  "mcpServers": {
    "loommux": {
      "command": "loommux",
      "cwd": "/absolute/path/to/your/workspace"
    }
  }
}

The exact enclosing configuration shape depends on the MCP host. The material facts are that the host starts loommux, the process runs in the intended workspace, and the Python environment running loommux can import ipykernel.

On startup, loommux resolves its workspace, builds a kernel launch from the server interpreter, and starts the kernel before accepting MCP tools. Server startup fails rather than exposing a partially configured execution service.

HTTP Server And Structured Opt-In

loommux --server exposes the same tools, input schemas, execution behavior, and model-readable text over Streamable HTTP. It remains content-only unless --result-mode structured is explicitly supplied.

Start a loopback-only content-only HTTP service from the workspace you want the kernel to use:

cd /absolute/path/to/your/workspace
loommux --server --host 127.0.0.1 --port 8801 --path /mcp

Its MCP endpoint is http://127.0.0.1:8801/mcp. --result-mode structured is available only when a client genuinely needs the raw status object:

loommux --server --result-mode structured --host 127.0.0.1 --port 8801 --path /mcp

MCP Studio, Inspector, and other Streamable HTTP clients use the same endpoint URL. There is no separate Studio protocol. The complete matrix, subprocess configuration examples, and security guidance are in MCP Connection Guide.

HTTP is a deployment boundary, not a different execution model: the tools, resource-local execution sequences, output streams, workspace resolution, and presentation rules are the same as the stdio server. The HTTP application also serves a resource console at / and operational JSON APIs under /api. Binding it beyond the local machine exposes arbitrary Python execution and requires network controls and authentication outside loommux.

If a server stops answering (the console, new connections, and Ctrl+C all go quiet), run kill -USR1 <loommux pid> on Linux or macOS. Every thread's Python stack is printed to the server's stderr and the server keeps running. The handler runs below the event loop, so it works while the loop is frozen; include its output when reporting the hang.

On Linux with glibc, a long-running server with many concurrent clients holds memory in per-thread malloc arenas that it has already freed. Starting it with MALLOC_ARENA_MAX=2 keeps that bounded: under the same two-minute load the server's resident memory levelled off at 277 MB instead of growing past 600 MB.

Kernel Resources And Client Leases

An ordinary MCP connection selects a kernel without changing the eight-tool surface. Each MCP session works in one main kernel: without an additional header, a private one named after its MCP Session ID; with X-Loommux-Resource, a named main kernel shared by every session configured with that name, where each participating session still holds an independent lease. Kernels that a kernel opens by name are child kernels of that home (see Main Kernel Binding Design).

The server chooses how a lease proves that its client is still there with the lease_mode setting of its configuration directory:

Mode

The lease holds while

Fits

activity (default)

tool calls keep arriving within 80 minutes (24 hours for named main kernels)

any client

heartbeat

the client pings within 60 seconds

clients that ping on their own, such as LeaseAwareClient

connection

the session keeps its standalone Streamable HTTP GET stream open; stdio sessions for the life of the process

clients that hold that stream while they run, such as Claude Code

A connection lease survives any amount of idleness. About a minute after the client exits (connection_grace_seconds, default 60), the session counts as gone and its lease is released. A named main kernel then keeps itself for the named window (named_activity_timeout_seconds, 24 hours) after the last call, so a new session can attach to it by name; the console shows how long it has left and can retire it at any time. An unnamed private-<session> kernel is retired with the lease, since nobody comes back to a generated name. If a session calls tools without that stream, or keeps calling after it closed, the lease falls back to the activity window and raises an alert: every tool result of that session ends with a Lease alert line, and the console at / turns its lease-signal row red. For Claude Code over HTTP:

loommux --server --host 127.0.0.1 --port 8801 --path /mcp

started in a directory whose .loommux/config.py holds:

lease_mode = "connection"

The server publishes the current lease policy at /api/lease-policy. The included loommux.client.LeaseAwareClient discovers and pins that policy generation before initialization and sends standard MCP ping while a heartbeat lease is active; under a connection policy its transport's own standalone stream holds the lease:

from loommux.client import LeaseAwareClient


async with LeaseAwareClient(
    "http://127.0.0.1:8801/mcp",
    "analysis-agent",
    resource_name="shared-analysis",
) as client:
    result = await client.call_tool(
        "run_cell",
        {"freeform": "value = 1\nprint(value)"},
    )

The operator label is optional. Omitting it or passing None leaves the X-Loommux-Operator header absent, so the server uses its session-derived fallback display name.

The complete runnable client-cooperation example is in examples/lease-aware-client.

The complete identity, lifecycle, policy, orphan-execution, control API, and source-ownership contract is documented in Kernel Resource Daemon Design.

Controlling Kernels From A Kernel

Every kernel knows which resource it is and where its daemon listens (LOOMMUX_KERNEL_ID, LOOMMUX_KERNEL_NAME, LOOMMUX_CONTROL_URL), and holds a client of that daemon as loommux. The daemon keeps the registry, so it lists, finds, opens, renames and retires kernels; a kernel handle has the eight operations of the MCP tools, with their names and parameters:

# the daemon's buttons
loommux.kernels()                  # every kernel: name, id, state, busy, idle, pid, executions, leases, kept, scope, home
loommux.kernels(timeout=2)         # the same, giving up with DaemonError after 2 seconds
loommux.self                       # this kernel
lab = loommux.create("lab")        # a child kernel of this main kernel, opened if missing
loommux["lab"]                     # find by name, path or id; never opens
loommux["/other-task/lab"]         # another main kernel's child kernel
loommux.rename(loommux.self, "report-cleanup")   # returns the name received; report-cleanup-2 if taken
loommux.recycle("lab", force=True)

# a kernel's buttons
n = lab.run_cell("train()", wait_seconds=0)["execution"]
lab.wait(n, timeout_seconds=1450, silence_seconds=140)
lab.read_output(n, line_range="-20:")["text"]
lab.search_output("loss", n)
lab.interrupt(); lab.restart()

loommux.notify("⚠ memory is low")  # to the agent, apart from this cell's output

A kernel opened this way is a child kernel of the asking kernel's main kernel. It lives as long as that main kernel is kept and is released with it. Its name is unique only among that main kernel's child kernels, so a name is a path: /main/child from the root, a bare name from the caller's main kernel, and an id anywhere. Names contain no /. Only an MCP session opens a main kernel (--attach NAME --create), because only its lease keeps one. A kernel already running a cell refuses run_cell() with DaemonError whose status is busy; cells are never queued. %%loommux --kernel build is create, run_cell and following its output in the forwarding cell; see the Kernel Control Surface Design.

loommux.notify(text) is how code in a kernel, an alarm in a post_run_cell hook for instance, tells the agent something about this moment. A notice is not output: no output stream holds it and the 5,000-token limit never omits it. The next tool result ends with every notice no result has delivered, each once, labeled In [N] · when another cell spoke it and Startup · when a startup file did. They come in one envelope that starts a line, with the Kernel: line and the Lease alert when a result has them:

In [7] · 54.3 s:
done
<loommux-notice>
⚠ 体征|memory 2.1 GiB, past 2 GiB
Kernel: report-cleanup (opened)
</loommux-notice>

The tag is the boundary between what the cell produced and what the environment says; a client joins a result's text blocks end to end, so a block of its own would not be one. A notice is cut at 500 characters and a cell keeps ten; a cell forwarded with --kernel build speaks build's notices again as build · …. The idle column, and idle_since in each snapshot, say how long a kernel has run nothing.

The client speaks the HTTP control plane, so anything it does stays outside the daemon process. Outside a kernel, loommux.daemon.Daemon() connects to LOOMMUX_CONTROL_URL or http://127.0.0.1:8801 and acts for the person running loommux: it lists every kernel and resolves a main kernel by name or any kernel by id, opening none, since nothing there can hold a new one. A stdio server has no HTTP control plane; its kernels still know who they are, and every request explains that no control plane exists.

Workspace And Interpreter

Workspace selection occurs when the server process starts. loommux does not provide a runtime tool that changes the workspace or Python interpreter.

By default, the server's current working directory is the workspace and the interpreter that launched loommux launches the kernel. This preserves the same virtual environment that imported loommux and avoids an ambiguous second Python-selection mechanism.

A config.py in the configuration directory that defines resolve_workspace(launch_cwd: Path) -> Path | str chooses another workspace; it is the only way to. loommux does not treat .git, .codex or any other tool's marker as its own. Resolver failures prevent startup before tools are available.

The Codex configuration directory is an example. See workspace configuration and the canonical Coding Agent Control Plane Design for the complete contract.

Configuration Directory

loommux is configured in one directory, shaped like an IPython profile directory: the nearest .loommux at or above the directory it starts in, as git finds .git, or the one --config DIR names. The start logs which one it uses.

.loommux/
    config.py        settings and resolve_workspace, read once when the daemon starts
    startup/         *.py and *.ipy files every kernel runs before its first cell

config.py is an ordinary Python module whose plain names are the settings:

Name

Default

lease_mode

"activity"

private_activity_timeout_seconds

4800

named_activity_timeout_seconds

86400

heartbeat_interval_seconds

15

heartbeat_timeout_seconds

60

connection_grace_seconds

60

sweep_interval_seconds

10

orphan_grace_seconds

30

It may import modules and define helpers. Any other public name stops the start with config_unknown_setting, so a misspelled setting is reported rather than left at its default. A relative DIR is read from the directory loommux starts in. The environment does not configure loommux.

The files of startup/ run in name order in every kernel process: when a main or child kernel opens, after restart, and after the replacement that stands for an interrupt on Windows. They run as loommux's own requests with store_history=False, so they take no execution number, and in the user namespace, so what they define is there for the first cell; an .ipy file may use magics such as %run. A file that succeeds is silent. One that raises, or runs past 60 seconds and is interrupted, becomes a notice, Startup · NAME failed: …, and the next file still runs. The directory is read at each start, so a changed file applies to the next kernel. It is the place for environment, things that work only if nobody has to remember to start them; tools are one line to load when needed. See Kernel Environment Design, and the tutorial that builds a small environment step by step; examples/kernel-environment is that environment, with a tour that runs it.

Execution Model

Each accepted run_cell submission creates an execution record with one public identity:

execution: positive integer

The sequence begins at 1 for a newly provisioned logical resource and increases only when a cell is accepted. Loommux accepts one running cell at a time inside each resource; separate resources may execute concurrently. A second run_cell call against the same busy resource is rejected with status="busy"; it is not queued.

An execution can be running, completed, error, interrupted, or killed. Python errors are recorded execution states, not MCP transport failures. The error summary identifies the exception while the collected traceback remains available from the execution's traceback stream.

The integer is the number IPython gives the same cell: In [5] in a result is In[5] and _i5 inside the kernel. restart starts the count again, as a notebook does; the records of the replaced process go with it, and what an agent saw of them is in its own transcript. When a cell yields a text/plain display result, the combined log carries the same coordinate:

Out[5]: 42

After a reset, the replacement IPython kernel may have restarted its internal counter, but the next loommux execution number remains consecutive and prior records remain readable.

MCP Tools

All tools below are exposed by the single loommux entrypoint. Calls that take an optional execution share one selection rule: an explicitly supplied positive integer selects that record; otherwise loommux selects the current running record, then the most recently accepted record. With neither, the tool returns execution_not_found.

Tool

Purpose

run_cell(freeform)

Submit one loommux IPython cell to the persistent kernel and wait for its initial result.

status()

Inspect the main kernel's name, the workspace, its authored source category, interpreter, kernel PID, busy state, and current or recent execution.

execution_status(execution=None)

Inspect lifecycle and diagnostic metadata without returning the full output body.

read_output(...)

Read a selected execution stream, optionally by line range and with per-line clipping.

search_output(...)

Search a selected output stream using literal text or regular expressions.

wait(execution=None, timeout_seconds=10, silence_seconds=10)

Wait for an execution without interrupting it, until it ends, the time runs out, or it has printed nothing for silence_seconds.

interrupt()

Send an interrupt signal to the current running execution.

restart()

Restart the kernel.

Submitting A Cell

run_cell accepts one freeform loommux IPython cell and submits it exactly as written. Its variables, imports, and other namespace state are available to later cells in the same selected logical resource.

import math
radius = 3
math.pi * radius**2

The Cell Header

A cell that needs its own control begins with a %%loommux header, written in ordinary IPython cell-magic syntax:

%%loommux --wait 260 --silence 35
%%bash
pnpm exec playwright test

The wait has two timers, as an HTTP client has an overall timeout and a read timeout. --wait SECONDS is the most the call waits. It only changes how long that run_cell call waits: it does not limit Python runtime, interrupt the cell when time expires, modify later calls, or add a variable to the kernel. --full-output is described under Requesting Complete Output. The options apply to that one cell; the next cell starts from the defaults again.

--silence SECONDS is the second timer: the call also returns once the cell has printed nothing for that long, still running, saying how long it has been silent and showing its last lines. Whichever comes first ends the call; neither interrupts the cell. Both default to 10 seconds, which suits a cell that ends in a few; for a longer run, write the two estimates you hold as whole seconds worked out from the run at hand rather than round numbers: --wait a little above how long it took last time, --silence the longest gap between its lines, such as its slowest test. The example's suite last ran 212 seconds and its slowest case 23. A cell that writes only a long --wait still returns after ten quiet seconds.

Every run_cell and wait result says on its first line how long the execution has run, from submission to its end or to now: In [7] · 54.3 s:, In [9] · 270 ms:. It is the measurement the next estimate starts from (Waiting Until It Goes Quiet, section 4.6).

In the example, a healthy run returns when it finishes; one stuck at second 40 returns at second 75 instead of 260. The wait tool takes silence_seconds, counted from the later of the call and the last output, and so do Kernel.run_cell and Kernel.wait; Kernel.run_cell leaves an argument it is not given to the cell's header. Any running result shows its last few lines. Python processes a cell starts are unbuffered (PYTHONUNBUFFERED=1), so uv run pytest under %%bash streams its lines rather than holding them until it exits. See Waiting Until It Goes Quiet.

The header is IPython, not transport metadata. The daemon reads its options and submits the cell unchanged, and the kernel interprets the header itself, so another cell magic may follow and IPython history keeps the header:

%%loommux --wait 0.5
%%bash
uv run pytest

A malformed header returns invalid_cell_header before an execution is allocated or source is submitted. A %%loommux line anywhere but first raises a UsageError saying so. See Cell Header Design for the grammar and its reasons.

--kernel NAME in the header hands the cell to that kernel:

%%loommux --kernel build --wait 75 --silence 25
%%bash
uv run pytest

The kernel named build is this main kernel's child kernel, opened on first use and retired with the main kernel (see Controlling Kernels From A Kernel). Its combined output is relayed into this cell as it arrives; an error there ends this cell with a one-line ForwardedCellError beneath the relayed traceback, and since this cell waits on it, interrupting this cell interrupts it as well. Writing the receiving kernel's own name is the same as writing none. Relayed output is text; rich displays such as images stay in the target's execution record.

Naming And Switching The Main Kernel

Each MCP session works in one main kernel. --attach NAME --create names it: the session works in the main kernel NAME from this cell on, which is opened when it does not exist and found by name when it does, whoever else works in it. A phrase describing the task makes a good name, and the same first cell returns to the kernel after the host was closed. Naming is what keeps a kernel after its host closes:

%%loommux --attach report-cleanup --create
import pandas as pd

Without --attach, a session's first call opens private-<session>, and --kernel always forwards as described above. --attach NAME alone moves the session to an existing main kernel at any time, for example after a first cell that ran before the agent remembered its kernel, and releases its lease on the one it leaves:

%%loommux --attach report-cleanup
df.head()

Without --create a missing name returns kernel_not_found, listing the main kernels, and opens nothing. The call that changes the session's main kernel ends with a line such as Kernel: report-cleanup (attached; 12 earlier executions) in its <loommux-notice> envelope, and status begins with the kernel's name. A kernel renames itself with loommux.rename(loommux.self, "new-name"). The rules, and the terminal multiplexers and notebook servers they come from, are in Main Kernel Binding Design.

When the call returns while the cell is still running, use wait, execution_status, read_output, search_output, interrupt, or restart to continue observing or controlling the same execution.

Output Streams And Long Output

Each execution retains five append-only text projections:

Stream

Contents

combined

stdout, stderr, display results, and tracebacks in IOPub arrival order.

stdout

Python stdout stream events, and help IPython sends to a frontend's pager (obj?, obj??, %%loommux?), placed after the cell's other output.

stderr

Python stderr stream events.

result

text/plain from IPython execute-result and display-data events.

traceback

Traceback text from Python error events.

Completed combined output of at most 5,000 o200k_base tokens is returned by run_cell and wait beneath an In [execution]: header. A display result then keeps its IPython-style Out[execution]: line; a silent cell returns only the input header, and stdout or traceback remains in its original combined order. For an execution that is still running, or for an unmarked terminal execution whose combined output exceeds 5,000 tokens, the response retains the record but omits the full body. Its omission notice reports the combined output's total lines, Unicode code point characters, and UTF-8 size using one binary unit (B, KiB, MiB, and so on). The structured run_cell, wait, and execution_status surfaces expose the corresponding exact counts as output_total_lines, output_total_characters, and output_total_utf8_bytes. The output is not discarded; the notice itself names the recovery paths, and the caller reads or searches the retained text through read_output and search_output, or requests the complete body with the %%loommux --full-output header. Token counting is required for this automatic delivery policy; if the o200k_base tokenizer cannot be loaded, the call fails instead of silently changing to another limit.

read_output takes start:stop coordinates shaped like a slice: both endpoints are line numbers, both are inside the range, and either may be omitted, which leaves that end at the boundary. So : is the whole stream:

:      the whole stream
:10    first 10 lines
-10:   final 10 lines
20:40  lines 20 through 40
3:3    only line 3

Negative endpoints count line numbers from the end and stay inclusive, so :-3 stops at the third-from-last line; it does not drop the last three. Every selected line comes back in one response, so there is no need to divide the read into consecutive small ranges.

max_chars clips each returned line without changing stored text or line coordinates. search_output supports literal, regex, and auto matching. In auto mode, loommux treats the query as a regular expression when it compiles and falls back to literal matching when it does not. Search results preserve original line numbers, mark matching lines with M, and mark selected context lines with C.

Requesting Complete Output

When a cell's entire terminal combined output is the intended result, include --full-output in its header:

%%loommux --full-output
build_report()

The option applies only to that execution. Once the execution is terminal, it bypasses the normal 5,000-token delivery threshold and makes run_cell or a later wait return the complete collected combined output. It does not cause partial running output to be returned and does not alter the input or behavior of read_output and search_output.

The full-output and wait options are independent and may be combined:

%%loommux --wait 38 --full-output
build_report()

Interrupting And Resetting

interrupt requests an interrupt for the current running cell. An interrupt_sent response only confirms signal delivery; the execution reaches its final state after the kernel reports IOPub idle.

restart is stronger: it stops the existing kernel and starts a replacement in the same workspace with the same interpreter, which runs the startup files again. A running cell is stopped. The execution records go with the old process and the next cell is In [1], as IPython numbers it.

Stopping the loommux server retires all resources. Their kernels, namespaces, execution-record tables, output streams, and sequences are not persisted to disk. Recycling one resource has the same persistence boundary for that resource; a subsequently provisioned resource begins a fresh sequence at 1.

Security

Arbitrary Python execution is loommux's central capability. Treat the MCP client, its process account, installed packages, the selected workspace, and any reachable network endpoint as parts of the same security boundary. Give the server access only to files, environments, and network resources the MCP client is authorized to use.

The stdio server is generally the least exposed deployment mode. The HTTP server must never be placed directly on an untrusted network. For a vulnerability in loommux itself, use the private reporting process in SECURITY.md, not a public issue.

Architecture And Documentation

The runtime is deliberately divided into narrow responsibilities:

loommux/
    session.py
        Owns one persistent IPython namespace, execution identity, selection,
        waiting, control operations, execution records, startup files, and
        the notices not yet delivered.
    kernel/
        launch.py
            Builds the interpreter command, child environment, and private root.
        runtime.py
            Owns Jupyter kernel process lifecycle and platform containment.
        session.py
            Correlates IOPub messages with the active execution record.
    execution/
        record.py
            Stores one execution's lifecycle facts and normalized projections.
        events.py
            Names visible text, image, and delivery-failure events.
        logs.py
            Provides append-only streams, line ranges, clipping, and search.
        terminal.py
            Removes terminal controls while preserving chunk boundaries.
    cell_header.py
        Parses the %%loommux cell header for the daemon and the kernel.
    config.py
        Finds the nearest .loommux/ at or above the launch directory, or the one
        --config names, and reads its settings, resolve_workspace, startup/.
    workspace_resolver.py
        Resolves the kernel workspace before tools are exposed.
    notice.py
        The notice channel's MIME type and bounds, shared by kernel and daemon.
    kernel_extension.py
        IPython extension every kernel loads: interprets the cell header and
        places the daemon client in the namespace as ``loommux``.
    daemon.py
        HTTP client of the daemon's control plane.
    identity.py
        Names the kernel identity variables the daemon passes to its kernels.
    mcp/
        factory.py
            Registers MCP tools that consume the protocol-neutral session.
        result.py
            Projects session facts into MCP text, structured, and image content.
        presentation.py
            Renders MCP-facing model-readable text.
        entrypoints.py and server.py
            Select MCP transport and expose the installed command.

The top-level loommux package imports only the protocol-neutral Python session API. FastMCP is entered explicitly through loommux.mcp; the runtime does not depend on its transport consumer.

The current public contract is documented in Coding Agent Control Plane Design. Focused references cover the cell header, main kernel binding, kernel environment, freeform cell input, complete-output control, workspace configuration, and the changelog.

Features are frozen until the rebuild onto jupyter_client's async API, AnyIO and Hypermorph live objects; Rebuild Brief is its inventory, and spikes holds the experiments it stands on.

Development And Release Checks

Run the Python checks from the repository root:

uv run pytest
uv run ruff check src tests examples scripts
uv run basedpyright src
uv build --out-dir dist
uv run twine check dist/*

The project metadata declares this README as the package readme:

[project]
readme = "README.md"

Consequently, the same document is rendered on PyPI when a new release is built and uploaded. The explicit source-distribution allowlist includes this file, the runtime package, tests, and public documentation while excluding workspace-only material.

See CONTRIBUTING.md for contribution expectations.

License

Copyright 2026 MichengLiang.

loommux is licensed under the Apache License, Version 2.0.

Available Tools

8 tools
execution_statusA

返回一个 execution 的状态与元数据。

选择规则

提供 execution 时,只选择该正整数记录。省略时,先选择当前 running 记录;不存在 running 记录时,选择最近一次被接受的记录; 两者都不存在时返回 execution_not_found。

NameRequiredDescriptionDefault
executionNo要查看的正整数执行编号。

TDQS

A3.6/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 usefully discloses fallback selection behavior and the `execution_not_found` outcome, but it does not state whether the tool is strictly read-only, what permissions are required, or what the returned status/metadata structure looks like.

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 tool purpose and then presents compact selection rules. Every sentence contributes useful information, with no redundant or filler text.

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 one-parameter read-status tool, the selection and error behavior are reasonably covered. However, with no output schema and no annotations, the description leaves the return shape and behavioral constraints underspecified, and it does not address sibling-tool differentiation.

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 already 100%, so the schema documents the integer parameter. The description adds meaningful semantics beyond the schema by explaining that providing `execution` selects only that record and omitting it triggers running/recent-accepted fallback behavior.

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: returning an execution's status and metadata. It is clear what the tool does, though it does not explicitly distinguish itself from the sibling tool `status` or explain when to use this instead of that one.

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 selection rules explain how the optional `execution` parameter affects which record is returned, including the running/recent-accepted fallback. However, this is primarily parameter selection behavior, not guidance on when to use this tool versus sibling tools such as `status` or `read_output`.

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

interruptA

向当前正在运行的 execution 发送 kernel 中断信号。

中断语义

kernel 到达 IOPub idle 后,记录才会报告 interrupted、 error 或其他最终状态。kernel 空闲时返回 idle。

Returns: 已发送信号时返回目标 execution;kernel 空闲时返回 idle。

NameRequiredDescriptionDefault

No parameters

TDQS

A3.8/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 it does disclose meaningful behavior: the interrupt is asynchronous, the record only reports interrupted/error/other final status after the kernel reaches IOPub idle, and an idle kernel returns idle. It omits whether partial output is preserved or permissions required, keeping it below 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?

The description is front-loaded with the purpose and then a clearly separated semantics section. The Returns block repeats the already-stated idle behavior, which is minor redundancy, but the structure is otherwise efficient.

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?

With no annotations, no output schema, and zero parameters, the description covers the safety-relevant behavior (async completion, final status reporting, idle handling) that an agent needs before invoking it. Only minor details such as side effects on buffered output are absent.

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 zero parameters, so the baseline is 4. There is nothing further for the description to disambiguate, and it adds no misleading parameter claims.

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 gives a specific verb and resource: sending a kernel interrupt signal to the currently running execution. It is clear about what happens, but it does not name the sibling it differs from, so an agent cannot tell interrupt from restart purely from this text.

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 phrase about the currently running execution implies when the tool applies, but there is no explicit instruction on when to use it instead of restart or wait, and no exclusions. The agent must infer that this cancels a running execution rather than restarting the kernel.

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

read_outputA

读取一个 execution 的指定输出流。

选择与流

execution 的选择规则与 execution_status 相同。stream 只能为 combined、stdout、stderr、result 或 traceback。

行坐标

line_range 形如切片 start:stop:两端都是行号,都含在范围内, 端点都可以省略,省略即到边界,所以 : 就是整个流。:10 读前 10 行,-10: 读后 10 行,2:4 读第 2 到第 4 行,3:3 只读第 3 行。

负数按行号从尾部数,两端照样都含::-3 是到倒数第三行为止,不是去掉 末三行。所选范围的全部行一次返回,不必切成连续的小段。

NameRequiredDescriptionDefault
streamNo输出流;默认 ``combined``,其余值为 ``stdout``、``stderr``、 ``result`` 与 ``traceback``。combined
executionNo要读取的正整数执行编号。省略时使用当前记录,随后 使用最近记录。
max_charsNo每个返回行允许显示的最大字符数;必须为正数。超出 部分只在响应中裁切,不改变已保存文本或行坐标。
line_rangeNo行坐标 ``start:stop``,与切片同形;两端都含, ``:`` 是整条流。

TDQS

A3.7/5.0
Behavior3/5

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

There are no annotations, so the description must disclose the tool's behavior in full, and it does disclose the valid stream values, the inclusive-endpoint slicing, how negative indices are counted from the end, and the fact that the whole selected range is returned in a single call rather than in chunks. It omits important behavior: whether line numbers appear in the response, the shape of the output, the limits on how much can be returned, and whether the tool requires permissions or errors out when the execution is missing.

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 a one-line purpose, then split into clearly headed sections for stream selection and line coordinates. The line-coordinate section is long, but every sentence carries the coordinate semantics, and the examples prevent misuse.

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 four-parameter read tool with no annotations and no output schema, the description covers how to select the execution and the stream, the line-range grammar, and the max_chars truncation rule. It does not describe what the response itself looks like, nor what happens when the execution is invalid, so a small gap remains in understanding the input, but not enough to affect the decision to call.

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 a baseline of 3 would be acceptable, but the description goes well beyond the schema for the most error-prone parameter: line_range. It gives concrete examples (:10, -10:, 2:4, 3:3), clarifies that both endpoints are inclusive, and specifically corrects the common mistake that :-3 means 'drop the last three lines' rather than 'up to and including the third-from-last line'. This adds real meaning.

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 first line states a concrete verb and resource: read the specified output stream of an execution, and it enumerates the five valid streams (combined/stdout/stderr/result/traceback), so the agent understands what is returned. It does not explain how this differs from the sibling tool search_output, so the agent is left to infer the split between reading a range and searching.

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?

It says the ``execution`` argument is resolved by the same rules as ``execution_status``, and it documents the fallback from current record to most recent record, which implies how to call the tool. However, it gives no explicit guidance on when to use this tool rather than search_output, run_cell, or execution_status, and it states no exclusions or prerequisites.

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

restartA

重启 IPython kernel。

重置边界

若存在 running execution,它会先被终止;随后旧 kernel 停止并创建替代 kernel。

Returns: 新 kernel 的状态与 PID;重启失败时返回 workspace 或 kernel 启动错误。

NameRequiredDescriptionDefault

No parameters

TDQS

A3.9/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 well: it discloses the destructive side effect (terminating any running execution, stopping the old kernel) and describes what is returned (new kernel status and PID, or workspace/kernel startup error). It omits that in-memory variables/state are lost, which is the key consequence an agent should know.

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 the core action, then the boundary condition, then return values — good ordering. The '重置边界' section divider is a docstring formatting artifact that adds little, keeping it from a 5.

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 zero-parameter tool with no output schema, the description covers action, side effects, and return values including the failure case. Nothing essential for correct invocation 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?

The tool takes no parameters, so there is nothing to disambiguate; the baseline of 4 applies. The schema is empty and correctly so.

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: restarting the IPython kernel, with the exact sequence (stop old kernel, create replacement). It does not name the closest sibling (interrupt) to differentiate the two, so it stops short of a 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?

Usage is only implied: the note that a running execution is terminated first hints at when this matters versus interrupt, but there is no explicit 'use this when…', no exclusions, and no reference to the interrupt or status siblings.

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

run_cellA

向持久 IPython kernel 提交一个 IPython cell。

请你使用 IPython 的思想来优雅使用本系列工具。

输入

一格 IPython cell,变量、导入和其他 namespace 状态与当前持久 IPython 会话中的后续 cell 共享。

loommux cell magic

cell 第一行写 %%loommux,给这一次调用加参数;选项只作用于这一个 cell, 下一行可以接另一个 cell magic,例如 %%bash::

%%loommux --wait 260 --silence 35
%%bash
pnpm exec playwright test

等待有两个计时器,对应 HTTP 客户端的总超时与 read timeout(CI 里是 job timeout 与 no_output_timeout):

--wait SECONDS:这次调用最多等多久; --silence SECONDS:cell 连续这么久没有任何输出就提前返回,从提交起计,每来一条输出就重新计时。

cell 跑完立即返回;计时器先到时,结果写明 running 与原因,附最后几行输出。 到点只交还控制权,cell 照常运行:接着用 wait 等(同样两个计时器),或 read_output、interrupt。

不写时两个都是 10 秒,几秒内结束的 cell 不用写。要跑更久,就按这次具体在跑的东西估两个整数秒, 不写 30、120、600 这类凑整的数:--wait 是略高于预期、超过就该回来看看的上限; --silence 是健康运行里两行输出之间最长的间隔。上面的 e2e 上次跑了 212 秒,最慢的用例 23 秒; 训练 12 个 epoch、每个约 280 秒才打一行时,写 --wait 3500 --silence 320。 只写 --wait 时静默仍是 10 秒,安静的长任务会在 10 秒处先回来。

每个结果第一行写着这次执行跑了多久,如 In [7] · 54.3 s:,还在跑时是从提交到现在; 下一次估计照着它修正。

--full-output:让本次 execution 在终态时返回完整 combined 正文,突破默认 5,000-token 阈值限制; 在明确需要完整阅读某些文件、资料时使用,避免反复阅读的开销。

--kernel NAME 写另一个 kernel 的名字,把这个 cell 交给它::

%%loommux --kernel build --wait 75 --silence 25
%%bash
uv run pytest

build 是这个会话主 kernel 名下的子 kernel,不存在时首次使用就开出, 跟着主 kernel 活、跟着它回收;子 kernel 的名字只在各自的主 kernel 名下唯一, 别的主 kernel 名下的写全称 /主kernel名/build。 这个 cell 就是 loommux.create("build") 再在它上面 run_cell,在这里等它、接着它的输出; 所以中断这个 cell,build 的这次执行也被中断。 每个 kernel 里都有 loommux:守护进程的客户端,好比 docker 命令之于 dockerd。 守护进程管 kernel 的生死与名字:列出、查找、开子 kernel、改名、回收; loommux["build"] 取到的 kernel 有与这组工具同名同参的方法,你对主 kernel 做的事,对任何一台都能做。 细节在对象上:loommux?、help(loommux)。

什么时候用呢?

  1. 同时想利用Kernel本身(如果Shellwork合适优先使用)去跑长任务的同时,还想要使用主Kernel去做别的事情使用。

  2. 想要保持主Kernel命名空间干净,临时去子Kernel做探索、尝试、测试……完成后回收临时Kernel。 如果你要做的事情本身就是阻塞当前的这个任务,那么请只使用主Kernel.

主 kernel

第一个 cell 用 --attach 名字 --create 给它命名意味着向 loommux 申请持久资源,最长24小时动态刷新。 名字必须使用描述这次任务的一个短语,不带空格::

%%loommux --attach 主kernel绑定设计 --create
import pandas as pd

--attach 名字 可重新切换会曾命名到的持久资源。 任务变了,在任意 cell 里顺手写 loommux.rename(loommux.self, "新名字")。

图像展示

使用 IPython display() 即可看图, 对 PNG、JPEG、WEBP 或单帧 GIF 图像有效

执行编号与后续操作

提交成功可以获得一个 execution。后续工具使用它定位当前持久 IPython 会话中的这次执行。

NameRequiredDescriptionDefault
freeformYes原始 IPython cell 源码文本。

TDQS

A4.5/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 so well: it states the two timers (--wait and --silence, both defaulting to 10 seconds), that reaching a timer only returns control while the cell keeps running, that --full-output raises the default 5,000-token combined-body cap, that interrupting this cell interrupts the child kernel's execution, and that a named resource lives up to 24 hours. It even describes the first line of each result (e.g. "In [7] · 54.3 s:") so the agent can recalibrate estimates. Remaining gaps, such as error/exception formatting or concurrency limits across kernels, keep it from 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.

Conciseness3/5

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

Purpose is front-loaded in the first sentence and the sections are labeled, which helps navigation. However, the text is very long and includes material not needed to invoke run_cell, such as the docker-versus-dockerd analogy, renaming conventions, and pointers to help(loommux), which dilute the operational content.

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?

There is no output schema and no annotations, yet the description covers what an agent needs: return shape on completion versus timeout, the execution handle used by follow-up tools, output size limits, kernel targeting and lifecycle, and image display via display(). Nothing essential for a correct call 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?

The schema documents the single freeform parameter at 100% coverage as raw IPython cell source, so the baseline is 3. The description goes well beyond that by specifying what belongs inside the cell: the %%loommux cell magic on the first line, its options (--wait, --silence, --full-output, --kernel, --attach --create), and that a second cell magic such as %%bash may follow. It never names the parameter itself, 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?

The first line states a specific verb and resource: submit an IPython cell to a persistent IPython kernel, with namespace state shared across subsequent cells. It further distinguishes the tool from siblings by explaining that continuation goes through wait, read_output, or interrupt, so an agent can tell which tool owns which phase of execution.

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?

The "什么时候用呢?" section gives explicit when-to-use conditions for child kernels versus the main kernel, including an exclusion rule: if the work simply blocks the current task, use only the main kernel. It also routes continuation and cancellation to named siblings (wait, read_output, interrupt) with the conditions that select each.

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

search_outputA

在一个 execution 的指定输出流中搜索文本或正则表达式。

选择与匹配

execution 的选择规则与 execution_status 相同;可选的 stream 值与 read_output 相同。query_mode="literal" 按字面文本匹配,query_mode="regex" 要求 query 是有效正则, query_mode="auto" 先按正则解释,仅在编译失败时回退到字面匹配。

命中上下文

每条命中保留原始所选流行号,并以 M 标记;前后上下文行以 C 标记。

NameRequiredDescriptionDefault
queryYes要匹配的字面文本或正则表达式。
streamNo输出流;默认 ``combined``,其余值为 ``stdout``、``stderr``、 ``result`` 与 ``traceback``。combined
executionNo要搜索的正整数执行编号。省略时使用当前记录,随后 使用最近记录。
max_charsNo每个返回行允许显示的最大字符数;必须为正数,且只 裁切响应文本。
query_modeNo匹配解释方式:``literal``、``regex`` 或 ``auto``; 默认 ``auto``。auto
ignore_caseNo为 true 时忽略大小写。
context_afterNo每个命中之后附加的相邻行数;必须大于或等于 0。
context_beforeNo每个命中之前附加的相邻行数;必须大于或等于 0。

TDQS

A3.9/5.0
Behavior3/5

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

The description does not include any annotations, so it carries the full burden of disclosing behavioral traits. It explains how the tool selects executions (current or most recent when omitted), how matching modes work, and how context lines are added. However, it does not disclose potential side effects (e.g., it is read-only), whether it modifies any state, or any error conditions. The description is mostly about operation mechanics, not behavioral caveats, so it partially covers transparency but leaves 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?

The description is well-structured with clear section headers for '选择与匹配' and '命中上下文', and the prose is concise. Each sentence provides useful information without redundancy. The description is appropriately sized for the complexity of the tool, covering key aspects without unnecessary verbosity.

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?

Given the complexity of this tool (search with modes, context lines, stream selection) and no output schema, the description is quite complete. It provides enough information for an agent to understand how to use it correctly. It references sibling tools for parameter selection, which reduces the need for repeating those rules. However, it could mention that this tool is read-only (non-destructive) and what the return format looks like (a list of matches with context), but the absence of an output schema is somewhat compensated by the description. Minor gaps exist but overall it's sufficient.

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 covers 100% of the parameters with descriptions. The description adds further semantics by explaining the selection rules for 'execution' (same as execution_status) and 'stream' (same as read_output), which are not fully apparent from the schema alone. It also clarifies the behavior of query_mode with examples (literal vs regex vs auto fallback). However, some parameters like max_chars have a schema description already, and the additional description of truncation is consistent but not substantially new. Overall, the description adds meaningful context 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?

The description clearly states the tool's purpose: searching text or regex in specified output streams of an execution. It names the resource ('execution'), the action ('search'), and the target (output stream), which distinguishes it from sibling tools like read_output (which reads without searching) and execution_status (which retrieves status). However, it does not explicitly name a sibling tool for contrast, though the references to read_output and execution_status in the description imply differentiation.

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 description provides explicit usage guidance: it explains how to select the 'execution' parameter (same rules as execution_status), how to choose the 'stream' parameter (same values as read_output), and how query_mode behaves with literal vs regex vs auto. It also explains context lines and max_chars truncation. However, it does not explicitly state when to use this tool instead of alternatives like read_output, though the contrast is implied by describing search-specific features.

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

statusA

返回 workspace、kernel 与最近执行记录的观察状态。

状态范围

还返回当前 IPython 工作台启动时解析的 workspace、其 workspace_resolution 来源类别、Python 解释器、kernel PID、kernel 是否已启动,以及 kernel 是否正忙。 workspace_resolution 只能是 launch_cwd 或explicit_config; 忙碌时 current_execution 是正在运行的正整数执行编号; 空闲时 recent_execution 是最近一次被接受的执行编号。

IPython 通过原生 ZMQ 协议连接,拥有完整能力。

Returns: 当前 IPython 工作台与 kernel 的状态快照。

NameRequiredDescriptionDefault

No parameters

TDQS

A3.5/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 burden. It discloses the returned fields, the enum-like invariant for workspace_resolution (launch_cwd | explicit_config), and the busy-vs-idle execution-number rule, which is genuinely helpful. It never states explicitly that the call is side-effect free or non-blocking, and the closing 'IPython 通过原生 ZMQ 协议连接,拥有完整能力' line describes the environment rather than this tool's behavior.

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

Conciseness3/5

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

It is front-loaded with a clear one-line summary and organized under a 状态范围 heading, but the trailing 'Returns:' sentence repeats the opening sentence verbatim in substance, and the ZMQ 'full capability' note does not earn its place in a status tool's description.

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, so the description must describe the return payload and it does enumerate the key fields and their invariants. The remaining gap is that a sibling (execution_status) overlaps in apparent purpose and the description offers no boundary between them, which an agent would need in order to choose correctly.

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 zero parameters, so per the baseline this dimension starts at 4. No parameter meaning is required from the description and none is missing.

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 opening sentence names a concrete deliverable (a snapshot of workspace, kernel and recent-execution state) and the body enumerates the specific fields returned: workspace_resolution, Python interpreter, kernel PID, started/busy flags, and current vs recent execution ids. It does not, however, distinguish itself from the sibling execution_status, which on name alone sounds like the same operation.

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 only implied: an agent can infer this is the read-only state probe to call before or after run_cell/interrupt/restart, but the description never says when to use it, when not to, or how it relates to execution_status. The current_execution/recent_execution distinction is useful context but is framed as data semantics rather than selection guidance.

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

waitA

等待一个 execution 结束,或两个计时器之一到点。

两个计时器

与 cell 头的 --wait / --silence 相同,对应 HTTP 客户端的总超时与 read timeout: timeout_seconds 是这次调用最多等多久;silence_seconds 是 execution 连续这么久 没有新输出就提前返回,从这次调用开始计。默认都是 10 秒。

到点只结束这次调用,cell 照常运行,结果写明 running 与原因,附最后几行输出; 之后可以再调用本工具,或用 read_output、interrupt。 结果第一行写着它从提交到现在跑了多久;对照预期估出剩下的整数秒,例如 wait(timeout_seconds=170, silence_seconds=35)。

选择

execution 的选择规则与 execution_status 相同。

完整输出交付

当所选 execution 的 cell 头含 --full-output 且已达到终态时,本工具 直接返回完整 combined 正文,可以突破 5,000-token 省略;仍在运行的 execution 继续返回 running。

NameRequiredDescriptionDefault
executionNo要等待的正整数执行编号。省略时使用当前记录,随后 使用最近记录。
silence_secondsNoexecution 连续这么久没有新输出就返回,正数;从这次调用开始计。
timeout_secondsNo这次调用最多等待的秒数,正数。

TDQS

A4.7/5.0
Behavior5/5

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

No annotations are provided, so the description carries the full burden and does so well: it discloses the partial-return semantics (result marked running plus reason and trailing output), the elapsed-time header, and the non-obvious rule that --full-output cells bypass the 5,000-token truncation only once terminal. These are exactly the behaviors an agent cannot infer from the name.

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 a one-sentence summary followed by headed sections that each earn their place, and the '选择' section is an efficient one-line delegation. Minor redundancy: the two timers are introduced in the opening line and then re-explained in the first section.

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?

With three optional params, no annotations and no output schema, the description fills every gap an agent needs: it describes the response shape (elapsed-time first line, running plus reason, trailing output), the truncation override, and the retry path. Nothing essential for calling it correctly is missing.

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

Parameters5/5

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

Schema coverage is already 100%, yet the description adds real meaning beyond it by framing timeout_seconds as a total/HTTP-client timeout and silence_seconds as a read timeout measured from call start, both defaulting to 10. The worked example `wait(timeout_seconds=170, silence_seconds=35)` shows how to size values against the reported elapsed time, which the schema alone does not convey.

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 — wait for an execution to finish — and immediately names the two independent mechanisms (total timeout vs. silence) that bound the call. It also distinguishes itself from siblings by naming read_output and interrupt as follow-up options, so an agent can tell it apart 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 Guidelines4/5

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

Clearly explains that a timer expiry only ends this call while the cell keeps running, and tells the agent what to do next (call again, read_output, interrupt). It defers the `execution` selection rule to execution_status explicitly, which is a useful pointer, but there is no guidance on error or not-found cases.

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. 3 tool updatesv0.4.0
    • Changedexecution_status1 field changed
      • changedInput schema / properties / execution / description
        Previous value: -"要查看的正整数执行编号。省略时使用当前记录,随后\n使用最近记录。"New value: +"要查看的正整数执行编号。"
    • Changedread_output1 field changed
      • changedInput schema / properties / line_range / description
        Previous value: -"``start:stop`` 行范围;已确定需要完整消费所选流时省略,\n工具会一次返回全部行,无需拆分为多个小范围。"New value: +"行坐标 ``start:stop``,与切片同形;两端都含,\n``:`` 是整条流。"
    • Changedwait3 fields changed
      • addedInput schema / properties / silence_seconds
        Added value: +{
        +  "default": 10,
        +  "description": "execution 连续这么久没有新输出就返回,正数;从这次调用开始计。",
        +  "type": "number"
        +}
      • changedInput schema / properties / timeout_seconds / default
        Previous value: -30New value: +10
      • changedInput schema / properties / timeout_seconds / description
        Previous value: -"本次调用最多等待的正数秒数;默认 30 秒。"New value: +"这次调用最多等待的秒数,正数。"
  2. 16 tool updatesv0.1.13
    • Addedexecution_status
    • Addedinterrupt
    • Removedinterrupt_python
    • Removedpython_execution_status
    • Removedpython_status
    • Addedread_output
    • Removedread_python_output
    • Removedreset_python
    • Addedrestart
    • Addedrun_cell
    • Removedrun_python
    • Addedsearch_output
    • Removedsearch_python_output
    • Addedstatus
    • Addedwait
    • Removedwait_python
  3. 8 tool updatesv0.1.11
    • First observedinterrupt_python
    • First observedpython_execution_status
    • First observedpython_status
    • First observedread_python_output
    • First observedreset_python
    • First observedrun_python
    • First observedsearch_python_output
    • First observedwait_python

TDQS

A3.9/5.0

Scored across 8 tools

Disambiguation3/5

Most tools are clearly distinct (run_cell, wait, interrupt, restart, read_output, search_output), but execution_status and status overlap heavily — both report status about the kernel/executions, with fuzzy boundaries that force the agent to reason about which one answers its question. The verbose descriptions help clarify but don't fully resolve this confusion.

Naming Consistency4/5

All tools use snake_case and a readable action-oriented convention (run_cell, read_output, search_output, interrupt, restart, wait). The main deviation is the pair 'status' vs 'execution_status', where one is a bare noun and the other a noun_noun compound, creating slight inconsistency.

Tool Count5/5

Eight tools is well-scoped for a persistent IPython kernel session manager, covering execution, waiting, output inspection, search, interrupt, restart, and status without bloat. Each tool earns its place in the workflow.

Completeness4/5

The execution lifecycle is well covered (submit, wait, read, search, interrupt, status, restart). However, kernel lifecycle operations mentioned in the docs — create/attach, rename, list kernels, and recycle/kill — are exposed only via the Python loommux object, not as tools, leaving gaps an agent must work around through run_cell code.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides persistent IPython shell sessions per conversation with DataFrame-centric architecture, enabling stateful data analysis, CLI tool execution, and integration of external MCP servers within the same workspace context.
    23
    Apache 2.0
  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides a persistent Jupyter kernel for executing code, inspecting variables and dataframes, and checking SQL query plans, enabling agents to work with stateful Python sessions.
    -
  • A
    license
    B
    quality
    B
    maintenance
    Provides a persistent, stateful IPython execution environment for MCP clients, allowing agents to run Python code, define functions and classes, and maintain state across calls. It includes tools for execution, inspection, dynamic tool registration, and robust timeout/recovery handling.
    11
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables LLM clients to run Python code in a persistent, user-selected interpreter via MCP, with tools for data loading, summaries, regressions, diagnostics, and plotting, while keeping sessions alive between calls and isolating crashes.
    MIT