Metadata-Version: 2.3
Name: jupymcp
Version: 0.3.2
Summary: Local-first Jupyter notebook editing and execution through MCP
Author: Ziya Tang (唐梓涯)
Author-email: Ziya Tang (唐梓涯) <tcztzy@gmail.com>
Classifier: Framework :: Jupyter
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: GraalPy
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
Classifier: Typing :: Typed
Requires-Dist: ipykernel>=7.3.0
Requires-Dist: jupyter-client>=8.9.1
Requires-Dist: jupytext>=1.19.5
Requires-Dist: mcp>=2,<3
Requires-Dist: nbformat>=5.10.4
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/tcztzy/jupymcp
Project-URL: Changelog, https://github.com/tcztzy/jupymcp/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/tcztzy/jupymcp/issues
Project-URL: Source, https://github.com/tcztzy/jupymcp
Description-Content-Type: text/markdown

# JupyMCP

JupyMCP is a local-first Model Context Protocol server for editing and executing Jupyter notebooks. It starts and manages kernels itself, so a separate Jupyter Server, URL, or token is not required.

## Quick start

Configure an MCP client to launch JupyMCP over stdio:

```json
{
  "mcpServers": {
    "jupymcp": {
      "command": "uvx",
      "args": ["jupymcp", "--workspace-root", "/path/to/project"]
    }
  }
}
```

All notebook paths passed to tools are relative to `--workspace-root`, which defaults to the process working directory. Absolute paths and paths that escape this root are rejected.

## Tools

| Area | Tool | Purpose |
| --- | --- | --- |
| Kernel | `start_kernel` | Start a kernel, optionally selecting a kernelspec. |
| Kernel | `restart_kernel` | Restart a managed kernel. |
| Kernel | `shutdown_kernel` | Shut down a kernel and release bound clients. |
| Kernel | `shutdown_all` | Shut down every managed kernel. |
| Kernel | `interrupt_kernel` | Interrupt a running kernel. |
| Session | `get_notebook_session` | Return the kernel bound to one notebook. |
| Session | `interrupt_notebook_session` | Interrupt one notebook's kernel. |
| Session | `restart_notebook_session` | Restart one notebook's kernel. |
| Session | `shutdown_notebook_session` | Release one notebook and stop its unshared kernel. |
| Notebook | `create_notebook` | Create a notebook with the requested kernelspec. |
| Notebook | `read_notebook` | Read a validated notebook as structured data. |
| Notebook | `get_notebook_revision` | Return the SHA-256 revision used for optimistic writes. |
| Execution | `execute` | Execute code in a notebook-scoped kernel and optionally persist a new cell. |
| Execution | `execute_cell` | Execute an existing code cell and replace its saved outputs. |
| Cell | `append_cell` | Append a code, Markdown, or raw cell. |
| Cell | `insert_cell` | Insert a cell at a specific index. |
| Cell | `read_cell` | Read one cell by stable ID or index. |
| Cell | `update_cell` | Replace cell source and/or metadata without changing its ID. |
| Cell | `delete_cell` | Delete one cell. |
| Cell | `move_cell` | Move one cell to a new index. |
| Cell | `clear_cell_outputs` | Clear outputs and execution count from a code cell. |
| Metadata | `get_notebook_metadata` | Read notebook metadata. |
| Metadata | `set_notebook_metadata` | Replace notebook metadata. |
| Metadata | `get_cell_metadata` | Read cell metadata. |
| Metadata | `set_cell_metadata` | Replace cell metadata. |

Cell operations that target an existing cell require exactly one of `cell_id` or `index`. IDs remain stable across source edits, metadata changes, moves, and execution.

Mutation tools accept an optional `expected_revision`. Obtain it with `get_notebook_revision`; if the file changes before the atomic replace, the mutation fails with a revision conflict instead of overwriting the newer file. `execute_cell` always applies this check internally across the execution window. JSON-lines `open` responses include `revision`, which clients should return in `save.params.revision`.

`execute(type="py:percent")` persists cells as a Jupytext percent script with `# %%` markers. The script format stores source and metadata, but not rich execution outputs.

## Resources

| URI | Content |
| --- | --- |
| `jupymcp://capabilities` | Versioned server capability and runtime-status JSON. |
| `jupyter://kernelspecs` | Available kernel names. |
| `jupyter://kernels` | Running kernel IDs. |
| `notebook:///{+path}` | Validated notebook JSON for a percent-encoded workspace-relative path. |

Notebook resource URIs use an empty authority and an RFC 3986 path. Directory
slashes remain separators while spaces, Unicode, and reserved characters are
UTF-8 percent-encoded. For example,
`nested folder/分析.ipynb` is
`notebook:///nested%20folder/%E5%88%86%E6%9E%90.ipynb`. URI decoding is
reversible; absolute paths, any `..` component, malformed encodings, and paths
that resolve outside the workspace are rejected before file access.

`jupymcp://capabilities` has a stable `schema_version: "1.0"` object schema.
It reports `output_schema_version`, server name/version, the effective `limits`,
`notebook_formats`, active and supported `transport` modes, `sessions`,
`cancellation`, and sorted `kernelspecs`. Timeout and idle values are seconds.
The advertised MCP request cancellation stops the server request; it does not
automatically interrupt code already running in a kernel. Use
`interrupt_kernel` or `interrupt_notebook_session` for that explicit action.
JSON-lines requests do not support in-flight cancellation.

## Transports

| Mode | Command | Status |
| --- | --- | --- |
| `stdio` | `jupymcp` | Default and recommended for local MCP clients. |
| `sse` | `jupymcp --transport sse` | Experimental local HTTP transport. |
| `streamable-http` | `jupymcp --transport streamable-http` | Experimental local HTTP transport. |
| `json-lines` | `jupymcp --json-lines` | Trusted local desktop protocol on stdin/stdout. |

Execution is bounded by server-wide defaults: 120 seconds per call (maximum requested timeout 3600 seconds), 1 MiB or 128 captured output blocks, 8 managed kernels, and 15 minutes of notebook-session idle time. Configure these with `--default-timeout`, `--max-timeout`, `--max-output-bytes`, `--max-output-blocks`, `--max-kernels`, and `--idle-timeout`.

`execute` and `execute_cell` publish output schema `1.0` and return an MCP
`CallToolResult`. Rich, human-readable output remains in `content`; clients
should read the following machine contract from `structuredContent`:

```json
{
  "schema_version": "1.0",
  "execution_count": 1,
  "truncation": {
    "truncated": false,
    "omitted_blocks": 0,
    "omitted_bytes": 0
  },
  "error": null,
  "user_expressions": null
}
```

`omitted_blocks` counts complete captured output payloads omitted by the block
or byte limit, including a requested user-expressions payload.
`omitted_bytes` is exact: it sums each omitted payload's compact UTF-8 JSON
size and any byte difference when an error block is normalized. Truncation no
longer inserts a synthetic `stderr` stream, so clients cannot confuse a server
limit with notebook stderr.

Kernel failures return `isError: true` and populate `error` with normalized
`ename`, `evalue`, and `traceback`; the failed cell and bounded error output are
still persisted for file-backed execution. `ename` is limited to 128
characters. `evalue` and at most four traceback lines are limited according to
`max_output_bytes` (32–1024 characters per field, shrinking further if needed
to fit). Human-readable error text in `content` supplements these fields and
does not replace them.

HTTP transports bind only to `127.0.0.1` and enable Host/Origin validation plus DNS-rebinding protection. They do not yet provide user authentication, so do not expose them through a reverse proxy or public network.

## Security model

JupyMCP deliberately provides arbitrary code execution through Jupyter kernels. Run it only for trusted MCP clients and use a dedicated `--workspace-root`. The workspace boundary limits notebook file access; it is not a process sandbox, and executed code retains the permissions of the JupyMCP process.

Notebook sessions get separate kernels by default. Repeated execution in the same notebook preserves state, while different notebooks do not share variables unless the caller explicitly supplies the same `kernel_id`.

When neither `kernel_id` nor `kernel_name` is supplied, JupyMCP uses the existing notebook's `metadata.kernelspec.name` before falling back to the system default. Notebook-session controls affect only the normalized notebook path; a deliberately shared kernel remains alive until its final notebook binding is released.

Local Jupyter kernel messaging can use unencrypted loopback TCP depending on the installed kernelspec and `jupyter_client` configuration. Do not expose kernel connection files or ports to untrusted users.

## Development

```bash
uv sync --dev
uv run pytest --cov=src/jupymcp
uv run ruff check .
uv run ruff format --check .
uv build
uv run python scripts/check_distribution_contents.py dist/*
```

Generated notebook schema models live in `src/jupymcp/model.py`; regenerate them with `uv run python scripts/generate-model.py` instead of editing that file manually.

## Alternatives

- [jupyter-mcp-server](https://github.com/datalayer/jupyter-mcp-server)
- [jupyter-mcp](https://pypi.org/project/jupyter-mcp/)
- [mcp-jupyter](https://pypi.org/project/mcp-jupyter/)

JupyMCP focuses on a lightweight local workflow that does not require an already-running Jupyter Server.
