macaulay2-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. macaulay2_mcp-0.1.0/.dockerignore +14 -0
  2. macaulay2_mcp-0.1.0/.github/workflows/ci.yml +57 -0
  3. macaulay2_mcp-0.1.0/.github/workflows/release.yml +44 -0
  4. macaulay2_mcp-0.1.0/.gitignore +38 -0
  5. macaulay2_mcp-0.1.0/.mcp.json +8 -0
  6. macaulay2_mcp-0.1.0/AGENTS.md +128 -0
  7. macaulay2_mcp-0.1.0/CITATION.cff +10 -0
  8. macaulay2_mcp-0.1.0/LICENSE +674 -0
  9. macaulay2_mcp-0.1.0/PKG-INFO +254 -0
  10. macaulay2_mcp-0.1.0/README.md +237 -0
  11. macaulay2_mcp-0.1.0/e2e/Dockerfile +33 -0
  12. macaulay2_mcp-0.1.0/e2e/README.md +105 -0
  13. macaulay2_mcp-0.1.0/e2e/assert.py +93 -0
  14. macaulay2_mcp-0.1.0/e2e/docker-compose.gpu.yml +11 -0
  15. macaulay2_mcp-0.1.0/e2e/docker-compose.yml +31 -0
  16. macaulay2_mcp-0.1.0/e2e/opencode.native.json +28 -0
  17. macaulay2_mcp-0.1.0/e2e/results-14b/run.log +143 -0
  18. macaulay2_mcp-0.1.0/e2e/run_e2e.sh +132 -0
  19. macaulay2_mcp-0.1.0/e2e/soak.sh +45 -0
  20. macaulay2_mcp-0.1.0/e2e/task-latex.txt +7 -0
  21. macaulay2_mcp-0.1.0/e2e/task.txt +9 -0
  22. macaulay2_mcp-0.1.0/examples/example-prompts.md +398 -0
  23. macaulay2_mcp-0.1.0/examples/groebner-demo.md +72 -0
  24. macaulay2_mcp-0.1.0/examples/official-tutorial-run.md +203 -0
  25. macaulay2_mcp-0.1.0/pyproject.toml +44 -0
  26. macaulay2_mcp-0.1.0/src/macaulay2_mcp/__init__.py +3 -0
  27. macaulay2_mcp-0.1.0/src/macaulay2_mcp/__main__.py +4 -0
  28. macaulay2_mcp-0.1.0/src/macaulay2_mcp/cli.py +91 -0
  29. macaulay2_mcp-0.1.0/src/macaulay2_mcp/config.py +155 -0
  30. macaulay2_mcp-0.1.0/src/macaulay2_mcp/gatekeep.py +150 -0
  31. macaulay2_mcp-0.1.0/src/macaulay2_mcp/journal.py +146 -0
  32. macaulay2_mcp-0.1.0/src/macaulay2_mcp/kernel.py +621 -0
  33. macaulay2_mcp-0.1.0/src/macaulay2_mcp/scanner.py +61 -0
  34. macaulay2_mcp-0.1.0/src/macaulay2_mcp/server.py +447 -0
  35. macaulay2_mcp-0.1.0/tests/conftest.py +10 -0
  36. macaulay2_mcp-0.1.0/tests/data/golden.jsonl +17 -0
  37. macaulay2_mcp-0.1.0/tests/test_gatekeep.py +128 -0
  38. macaulay2_mcp-0.1.0/tests/test_golden.py +52 -0
  39. macaulay2_mcp-0.1.0/tests/test_journal.py +139 -0
  40. macaulay2_mcp-0.1.0/tests/test_kernel.py +311 -0
  41. macaulay2_mcp-0.1.0/tests/test_mcp_client.py +223 -0
  42. macaulay2_mcp-0.1.0/uv.lock +1083 -0
@@ -0,0 +1,14 @@
1
+ .git
2
+ .venv
3
+ __pycache__
4
+ .pytest_cache
5
+ .ruff_cache
6
+ htmlcov
7
+ e2e/results
8
+ notes
9
+ PUBLISHING.md
10
+ battery_run.py
11
+ .m2-mcp
12
+ m2-mcp-binder
13
+ .DS_Store
14
+ *.egg-info
@@ -0,0 +1,57 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: tests (${{ matrix.os }})
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [macos-latest, ubuntu-latest]
15
+ runs-on: ${{ matrix.os }}
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install Macaulay2 1.26 (latest stable)
20
+ if: runner.os == 'macOS'
21
+ run: |
22
+ brew tap Macaulay2/tap
23
+ brew trust --tap Macaulay2/tap 2>/dev/null || true # newer Homebrew requires explicit tap trust
24
+ brew install macaulay2
25
+ M2 --version
26
+
27
+ - name: Install Macaulay2 1.26 (latest stable)
28
+ if: runner.os == 'Linux'
29
+ run: |
30
+ sudo add-apt-repository -y ppa:macaulay2/macaulay2
31
+ sudo apt-get update
32
+ sudo apt-get install -y macaulay2
33
+
34
+ - name: Install uv
35
+ run: curl -LsSf https://astral.sh/uv/install.sh | sh
36
+ shell: bash
37
+
38
+ - name: Show M2 version
39
+ run: |
40
+ export PATH="$HOME/.local/bin:$PATH"
41
+ M2 --version
42
+ uv run macaulay2-mcp selftest
43
+
44
+ - name: Install dependencies
45
+ run: |
46
+ export PATH="$HOME/.local/bin:$PATH"
47
+ uv sync
48
+
49
+ - name: Lint
50
+ run: |
51
+ export PATH="$HOME/.local/bin:$PATH"
52
+ uv run ruff check src tests
53
+
54
+ - name: Test
55
+ run: |
56
+ export PATH="$HOME/.local/bin:$PATH"
57
+ uv run pytest -q
@@ -0,0 +1,44 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI when a version tag (vX.Y.Z) is pushed, using PyPI
4
+ # TRUSTED PUBLISHING (OpenID Connect) — no API token stored anywhere.
5
+ # One-time setup: https://docs.pypi.org/trusted-publishers/
6
+ on:
7
+ push:
8
+ tags:
9
+ - "v*"
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ run: curl -LsSf https://astral.sh/uv/install.sh | sh
19
+
20
+ - name: Build sdist + wheel
21
+ run: |
22
+ export PATH="$HOME/.local/bin:$PATH"
23
+ uv build
24
+
25
+ - uses: actions/upload-artifact@v4
26
+ with:
27
+ name: dist
28
+ path: dist/
29
+
30
+ publish:
31
+ needs: build
32
+ runs-on: ubuntu-latest
33
+ permissions:
34
+ contents: read
35
+ id-token: write # required for PyPI trusted publishing (OIDC token minting)
36
+ environment: pypi # PyPI trusted publisher is bound to this environment
37
+ steps:
38
+ - uses: actions/download-artifact@v4
39
+ with:
40
+ name: dist
41
+ path: dist/
42
+
43
+ - name: Publish to PyPI (trusted publishing, OIDC)
44
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,38 @@
1
+ # Python
2
+ dist/
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.egg-info/
6
+ .eggs/
7
+ build/
8
+ .venv/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
13
+
14
+ # uv
15
+ uv.lock.bak
16
+
17
+ # Editors / OS
18
+ .vscode/
19
+ .idea/
20
+ .DS_Store
21
+
22
+ # Local secrets / env
23
+ .env
24
+
25
+ # Local (developer) opencode config; the published form lives in the README
26
+ opencode.json
27
+
28
+ # Generated E2E outputs
29
+ e2e/results/
30
+
31
+ # Private contributor/review notes (not part of the published repo)
32
+ notes/
33
+
34
+ # journal artifacts (local audit trail)
35
+ .m2-mcp/
36
+
37
+ # private publishing runbook (kept on disk, stripped from history by filter-repo)
38
+ PUBLISHING.md
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "macaulay2": {
4
+ "command": "uvx",
5
+ "args": ["macaulay2-mcp"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,128 @@
1
+ # AGENTS.md — working on macaulay2-mcp
2
+
3
+ ## What this is
4
+
5
+ An MCP (Model Context Protocol) server exposing a persistent Macaulay2 1.26
6
+ session as tools. Python, `uv`-managed. Entry point: `macaulay2-mcp`
7
+ (console script) → `src/macaulay2_mcp/cli.py`.
8
+
9
+ ## Commands
10
+
11
+ ```sh
12
+ uv sync # install/refresh deps
13
+ uv run pytest -q # full test suite (needs M2 1.26 installed)
14
+ uv run ruff check src tests # lint
15
+ uv run macaulay2-mcp selftest# one-command install verification
16
+ uv run macaulay2-mcp # run the MCP server on stdio
17
+ ```
18
+
19
+ ## Hard constraints (do not break)
20
+
21
+ 1. **Stdio discipline:** when running as an MCP server, stdout carries the
22
+ JSON-RPC protocol. NEVER print to stdout from server code — logging goes
23
+ to stderr (`logging` is already configured that way). `selftest` may use
24
+ stdout (it is not a server).
25
+ 2. **M2 protocol — marker handshake:** M2 in pipe mode prints NO bare prompt
26
+ while stdin stays open; it prints `iN : <input>` only together with the
27
+ next input. M2's errors go to its **stderr**, which we merge into stdout
28
+ at the OS level (`stderr=STDOUT`) so error text keeps true stream order
29
+ in the returned output — do NOT separate them again (async drains race).
30
+ Completion of an evaluation is detected by the void marker in `kernel.py`
31
+ (`scan({}, i -> m2MCP<12hex>)`: read until a line ending with the unique
32
+ marker text). The marker must be a COMPLETE statement (blank/comment
33
+ lines dangle and absorb the next line) AND produce no `oN` output —
34
+ an assignment marker polluted M2's `oo`/`ooo` history, breaking the
35
+ official tutorial's `4*5; oo` workflow (regression test:
36
+ `test_oo_history_survives_calls`). Blank or comment-only lines do NOT
37
+ terminate a logical M2 input. SIGINT (m2_interrupt) works in pipe mode:
38
+ M2 prints `error: interrupted`, prompt indices stay in sync, the
39
+ buffered marker still executes (the in-flight evaluate() returns
40
+ normally), and state survives. SIGINT while idle only emits a bare
41
+ `iN :` line — filtered from evaluation blocks.
42
+ `M2Session.interrupt()` is LOCK-FREE by design (evaluate() holds the
43
+ session lock while running); never make it take the lock.
44
+ 3. **Golden outputs:** `tests/data/golden.jsonl` pins observable M2 1.26
45
+ behaviour (rendering quirks included). If M2 output changes, review the
46
+ dataset deliberately instead of letting it rot.
47
+ 4. **Version pin:** v0.1 supports M2 1.26.x only (`config.py`). Do not add
48
+ version branches; that's a later-version item.
49
+ 5. **Timeouts are author-set safety limits** (default 120s, max 3600s).
50
+ Timeout messages must keep saying it is NOT an M2 error and that the
51
+ session was restarted (state lost → retry self-contained).
52
+ 6. **Concurrency semantics are by design:** `m2_evaluate` serializes on the
53
+ session lock (one shared kernel = consistent state; concurrent requests
54
+ from subagents are safe, just queued), while `m2_run_script` takes no
55
+ lock (each job is an independent M2 process — that IS the parallelism
56
+ story until the planned job pool). Never "optimize away" the serialization.
57
+ Tests and docs pin outputs, NEVER timings.
58
+ 7. **No new user-facing config knobs.** The complete v0.1 set is `M2_BIN`
59
+ (binary location), `MACAULAY2_MCP_JOURNAL` (journal dir / `off`), and
60
+ `MACAULAY2_MCP_OS_ALLOW` (gate unblock list). Pinned flags and limits
61
+ live in `config.py` (`M2_KERNEL_FLAGS`, timeouts).
62
+ 8. **Messages inform, never direct.** Every user-facing string (install
63
+ hints, errors, tool outputs, README) states what the suggested action
64
+ does and whether it is reversible — and how to undo it. We do not tell
65
+ users to blindly agree/answer yes; we explain the decision and leave it
66
+ to them. Applies equally to LLM-facing text (INSTRUCTIONS, docstrings):
67
+ the assistant relays information, not pressure.
68
+ 9. **Error-continuation semantics are a pinned contract.** M2 continues
69
+ running inputs after an error (no rollback; even mid-input side effects
70
+ persist) — golden cases `error_then_continue`/`partial_input_effect` pin
71
+ this. The continue/restart/inspect options NOTE is composed ONLY in the
72
+ `m2_evaluate` handler for runtime errors (never for interrupted,
73
+ restart, or timeout results, which have their own messages).
74
+ `stop_on_error` uses `split_logical_inputs` (blank/comment lines attach
75
+ forward; trailing uncompletable lines dropped; documented limitation:
76
+ dangling trailing operators). `_marker_handshake` matches the marker
77
+ TEXT anywhere in a line and tracks the index from any `iN :` line —
78
+ do NOT re-anchor to `^iN : <marker>`: a code block ending in a comment
79
+ absorbs the marker as a continuation line and the handshake would hang
80
+ to timeout (regression test: `test_trailing_comment_does_not_swallow_marker`).
81
+ 10. **Gate + journal contracts.** The OS-call gatekeeper (`gatekeep.py`)
82
+ refuses process/file/network/env/session symbols BEFORE sending anything,
83
+ at all four entry points (evaluate, run_script, import_file,
84
+ load_package path check); it matches on `scanner.mask()` output —
85
+ strings/comments never false-positive; `value()` is deliberately NOT
86
+ blocked (documented bypass; friction not sandbox). The journal
87
+ (`journal.py`) defaults ON at `./.m2-mcp/`, writes lazily so the header
88
+ carries clientInfo (via `ctx.request_context.session.client_params` —
89
+ SDK v2 stdio path), truncates fields at 1 MiB, and MUST NEVER raise into
90
+ a tool call (self-disables with one stderr warning) and never touches
91
+ stdout. Tool handlers take a hidden `ctx: Context = None` param — the SDK
92
+ excludes it from the JSON schema; keep that pattern.
93
+
94
+ ## Layout
95
+
96
+ ```
97
+ src/macaulay2_mcp/
98
+ config.py binary discovery, version gate, pinned flags/timeouts
99
+ scanner.py offset-preserving string/comment mask (shared by kernel+gate)
100
+ kernel.py M2Session (persistent kernel), M2ScriptRunner (batch), messages
101
+ gatekeep.py OS-call blocklist + rejection messages (mask-based matching)
102
+ journal.py JSONL audit journal (lazy header w/ clientInfo, never raises)
103
+ server.py the 8 MCP tools + INSTRUCTIONS (LLM-facing, keep accurate)
104
+ cli.py entry point: server mode | selftest | --version
105
+ tests/
106
+ test_kernel.py protocol tests (skip if M2 1.26 missing)
107
+ test_gatekeep.py masking / enforcement / live blocking
108
+ test_journal.py journal units + live clientInfo-in-header round trip
109
+ test_mcp_client.py client-level tests over stdio
110
+ e2e/ Docker + Ollama + opencode demo (opt-in: run_e2e.sh)
111
+ ```
112
+
113
+ ## M2 1.26 idioms relevant to this codebase
114
+
115
+ * `gb I` returns a GroebnerBasis; see polynomials via `print generators (gb I)`.
116
+ * A trailing `;` SUPPRESSES a statement's result display (`betti G;` prints
117
+ nothing); `A; B` on one line shows only B. Use newlines + explicit `print`.
118
+ * Family loops: `for k from 1 to n list (J := ideal(...); <expr>)` with `:=`
119
+ for per-iteration locals. `I_k = ...` is ONE symbol named "I_k", not
120
+ indexing. `print (a | b)` needs parens — `print a | b` is `(print a) | b`.
121
+ * M2 strings use double quotes; single quotes are invalid.
122
+ * `unloadPackage` and `importFile` do not exist in 1.26 — package "unload" =
123
+ session reset; file import = evaluate the file's contents (m2_import_file).
124
+ * Preloaded packages error with "not reloaded; try Reload => true";
125
+ `m2_load_package` turns that into an "already loaded" note and NEVER
126
+ force-reloads — M2's own reload machinery breaks on packages whose source
127
+ has dependency `needsPackage` lines (verified with PrimaryDecomposition).
128
+ `reload=true` is the caller's explicit opt-in for that fragile path.
@@ -0,0 +1,10 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ title: "macaulay2-mcp: a Model Context Protocol server for a persistent Macaulay2 session"
4
+ authors:
5
+ - family-names: Kim
6
+ given-names: Youngsu
7
+ repository-code: https://github.com/youngsu-Kim/macaulay2-mcp
8
+ license: GPL-3.0-or-later
9
+ version: 0.1.0
10
+ date-released: 2026-09-06