Skip to content

Commit 51aaef3

Browse files
MCP server: omnimcode-mcp exposes OMC as a runtime to LLM clients
New crate omnimcode-mcp implements the MCP protocol over stdin/stdout JSON-RPC so any MCP client (Claude Desktop, Cursor, etc.) can drive OMC programmatically. The intent: turn OMC from "an LLM can write this if you hand it the manual" into "an LLM treats OMC as a callable runtime the way it treats Python via REPL." Tools exposed: omc_eval(code) — evaluate OMC source, return result omc_help(name) — signature/description/example for builtin omc_list_builtins(cat?) — enumerate documented builtins omc_categories() — list categories omc_unique_builtins() — the OMC-only surface (substrate / autograd / lazy generators / harmonic ops) omc_explain_error(message) — 259-pattern catalog lookup with fix omc_did_you_mean(name) — typo suggestions Implementation notes: - Each tools/call spins a fresh Interpreter to keep the server stateless. Sessions can layer on top. - omc_eval returns the last top-level expression value via the new Interpreter::take_last_expression_value() hook. - Display format collapses Rust's Debug{...} noise into compact LLM-readable strings; HInt surfaces value + φ-resonance + HIM so substrate metadata is visible in tool output. New OMC-side helpers: Interpreter::take_return_value() — top-level returns Interpreter::take_last_expression_value() — REPL-style result capture examples/demos/llm_showcase.omc — the canonical "this is OMC" demo running every OMC-unique primitive that an LLM would reach for OMC over NumPy: substrate-typed integers, substrate-preserving matmul, substrate-distance attention, autograd with substrate-annotated forward values, lazy Fibonacci streams. Bottom-line numbers from the showcase: 100 builtins documented, 13 OMC-unique, 259 error patterns curated, 14 categories. OMC_REFERENCE.md regenerated (1085 lines) to reflect the docs change (`resonance` → `res` to match the actual builtin name). Claude Desktop config example included in omnimcode-mcp/README.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 1b96175 commit 51aaef3

8 files changed

Lines changed: 629 additions & 9 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"omnimcode-lsp",
77
"omnimcode-codegen",
88
"omnimcode-cli",
9+
"omnimcode-mcp",
910
]
1011
# omnimcode-python kept around but excluded from the default workspace.
1112
# It was the "Python embeds OMC" wrapper (extension-module mode); now

OMC_REFERENCE.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Auto-generated from `omnimcode-core/src/docs.rs`. Run `omc --gen-docs > OMC_REFERENCE.md` to regenerate.
44

5-
**Total documented builtins**: 97
5+
**Total documented builtins**: 100
66

77
**OMC-unique**: 13 (no direct Python/NumPy equivalent — these are why you reach for OMC over numpy)
88

@@ -23,7 +23,7 @@ Auto-generated from `omnimcode-core/src/docs.rs`. Run `omc --gen-docs > OMC_REFE
2323
- [json](#json) (2 builtins)
2424
- [stdlib](#stdlib) (8 builtins)
2525
- [exceptions](#exceptions) (1 builtins)
26-
- [introspection](#introspection) (5 builtins)
26+
- [introspection](#introspection) (8 builtins)
2727

2828
---
2929

@@ -535,14 +535,14 @@ Position in Fibonacci sequence (-1 if not an attractor).
535535
fibonacci_index(13) // 7 ; fibonacci_index(14) // -1
536536
```
537537

538-
### `resonance` 🔱 *OMC-unique*
538+
### `res` 🔱 *OMC-unique*
539539

540540
**Signature**: `(n: int) -> float`
541541

542-
φ-resonance of a single value.
542+
φ-resonance of a single value (0..1, 1=on Fibonacci attractor).
543543

544544
```omc
545-
resonance(8) // 1.0 ; resonance(7) // <1.0
545+
res(8) // 1.0 ; res(7) // <1.0
546546
```
547547

548548
### `harmony` 🔱 *OMC-unique*
@@ -1051,5 +1051,35 @@ Builtins flagged as unique to OMC (no clean Python equivalent).
10511051
omc_unique_builtins() // [is_attractor, arr_substrate_attention, ...]
10521052
```
10531053

1054+
### `omc_explain_error`
1055+
1056+
**Signature**: `(msg: string) -> dict`
1057+
1058+
Pattern-match an error message against the curated catalog. Returns {matched, pattern, category, explanation, typical_cause, fix}.
1059+
1060+
```omc
1061+
try { arr_softmx([1.0]); } catch e { print(dict_get(omc_explain_error(e), "fix")); }
1062+
```
1063+
1064+
### `omc_error_categories`
1065+
1066+
**Signature**: `() -> string[]`
1067+
1068+
All distinct error categories in the catalog.
1069+
1070+
```omc
1071+
omc_error_categories() // [dispatch, arrays, linalg, ...]
1072+
```
1073+
1074+
### `omc_error_count`
1075+
1076+
**Signature**: `() -> int`
1077+
1078+
Number of curated error patterns. The knowledge base size.
1079+
1080+
```omc
1081+
omc_error_count() // 42+
1082+
```
1083+
10541084
---
10551085

examples/demos/llm_showcase.omc

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# The OMC-unique surface: things an LLM would reach for OMC for that
2+
# Python/NumPy/JAX simply can't do.
3+
#
4+
# Each section starts with a one-line "why this is OMC-only" so an LLM
5+
# reading the output understands the differentiator.
6+
7+
fn show(label, v) {
8+
print(concat_many(label, " = ", to_string(v)));
9+
}
10+
11+
fn main() {
12+
print("=== OMC unique-primitive showcase ===");
13+
print("Run via: omc examples/demos/llm_showcase.omc");
14+
print("");
15+
16+
# ----------------------------------------------------------------
17+
# 1. Substrate metadata on every integer.
18+
# Python's i64 is a raw value. OMC's HInt carries φ-resonance
19+
# and HIM scores reflecting Fibonacci-attractor alignment.
20+
# ----------------------------------------------------------------
21+
print("[1] Substrate metadata on integers (no NumPy equivalent)");
22+
show(" is_attractor(8) ", is_attractor(8)); # 1 (Fib)
23+
show(" is_attractor(7) ", is_attractor(7)); # 0
24+
show(" attractor_distance(7) ", attractor_distance(7)); # 1
25+
show(" res(8) ", res(8)); # 1.0
26+
show(" res(7) ", res(7)); # < 1.0
27+
print("");
28+
29+
# ----------------------------------------------------------------
30+
# 2. Substrate-typed array library. Resonance/HIM survive every op.
31+
# ----------------------------------------------------------------
32+
print("[2] Substrate-typed array ops");
33+
h fib = [0, 1, 2, 3, 5, 8, 13, 21];
34+
show(" arr_resonance_vec(fib) ", arr_resonance_vec(fib));
35+
show(" arr_fold_all([7,100,9,22])", arr_fold_all([7, 100, 9, 22]));
36+
print("");
37+
38+
# ----------------------------------------------------------------
39+
# 3. Matrix multiplication preserves substrate metadata.
40+
# Every output cell of an int matmul carries its own resonance.
41+
# ----------------------------------------------------------------
42+
print("[3] Substrate-preserving matmul");
43+
h A = [[1, 2], [3, 5]]; # Fibonacci-shaped
44+
h B = [[2, 3], [5, 8]];
45+
h C = arr_matmul(A, B); # 2x2 result, each cell HInt
46+
show(" A @ B[0] ", arr_get(C, 0));
47+
show(" A @ B[1] ", arr_get(C, 1));
48+
print("");
49+
50+
# ----------------------------------------------------------------
51+
# 4. Substrate-distance attention. The attention KERNEL itself
52+
# uses Fibonacci-distance scoring instead of dot products.
53+
# ----------------------------------------------------------------
54+
print("[4] Substrate-distance attention");
55+
h Q = [[1, 2]]; # single query
56+
h K = [[1, 2], [100, 200]]; # close key + far key
57+
h V = [[5.0, 5.0], [99.0, 99.0]]; # close V + far V
58+
h out = arr_substrate_attention(Q, K, V);
59+
show(" attention(Q,K,V)[0] ", arr_get(out, 0));
60+
print(" → strongly weighted toward V[0] because Q matches K[0] in substrate-space");
61+
print("");
62+
63+
# ----------------------------------------------------------------
64+
# 5. Reverse-mode autograd that preserves substrate metadata on
65+
# forward values. Python autograd hands you plain f64.
66+
# ----------------------------------------------------------------
67+
print("[5] Autograd that keeps substrate metadata on forward path");
68+
tape_reset();
69+
h x = tape_var(3);
70+
h y = tape_var(5);
71+
h s = tape_add(x, y); # 8 — Fibonacci attractor
72+
h sq = tape_mul(s, s); # 64 — close to 55 attractor
73+
tape_backward(sq);
74+
show(" forward value at s ", tape_value(s)); # substrate-annotated 8
75+
show(" forward value at sq ", tape_value(sq));
76+
show(" d(sq)/dx ", tape_grad(x)); # 2*8 = 16
77+
show(" d(sq)/dy ", tape_grad(y)); # 2*8 = 16
78+
print("");
79+
80+
# ----------------------------------------------------------------
81+
# 6. Lazy substrate Fibonacci generator — streams forever in O(1)
82+
# memory. Each yielded value already carries resonance=1.0.
83+
# ----------------------------------------------------------------
84+
print("[6] Lazy substrate Fibonacci stream (O(1) memory)");
85+
h collected = [];
86+
gen_substrate_fib(
87+
fn(v) {
88+
arr_push(collected, v);
89+
return 1;
90+
},
91+
100
92+
);
93+
show(" Fibs <= 100 ", collected);
94+
print("");
95+
96+
# ----------------------------------------------------------------
97+
# 7. The introspection layer itself. LLMs read this at runtime.
98+
# ----------------------------------------------------------------
99+
print("[7] Self-describing runtime");
100+
show(" builtin count ", arr_len(omc_list_builtins()));
101+
show(" category count ", arr_len(omc_categories()));
102+
show(" OMC-unique builtin count ", arr_len(omc_unique_builtins()));
103+
show(" curated error pattern count", omc_error_count());
104+
print("");
105+
print(" An LLM can call omc_help(\"<name>\") for any of these.");
106+
print(" Errors come with did_you_mean suggestions + omc_explain_error.");
107+
108+
print("");
109+
print("=== These are the primitives Python/NumPy cannot replicate. ===");
110+
}
111+
112+
main();

omnimcode-core/src/docs.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,10 @@ pub const BUILTINS: &[BuiltinDoc] = &[
388388
unique_to_omc: true,
389389
},
390390
BuiltinDoc {
391-
name: "resonance", category: "substrate",
391+
name: "res", category: "substrate",
392392
signature: "(n: int) -> float",
393-
description: "φ-resonance of a single value.",
394-
example: "resonance(8) // 1.0 ; resonance(7) // <1.0",
393+
description: "φ-resonance of a single value (0..1, 1=on Fibonacci attractor).",
394+
example: "res(8) // 1.0 ; res(7) // <1.0",
395395
unique_to_omc: true,
396396
},
397397
BuiltinDoc {

omnimcode-core/src/interpreter.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ pub struct Interpreter {
4444
/// `tape_value(id)` to get the substrate-annotated forward value
4545
/// alongside `tape_grad(id)` for the derivative.
4646
autograd_tape: Vec<TapeNode>,
47+
/// Value of the most recently evaluated top-level
48+
/// `Statement::Expression`. The MCP server and any REPL frontend
49+
/// read this to surface "what did the last line evaluate to"
50+
/// without re-running side effects.
51+
last_expression_value: Option<Value>,
4752
/// Stack of yield callbacks for LAZY generators. When set, the
4853
/// active generator's yield statements invoke the topmost callback
4954
/// with the yielded value rather than appending to a Vec. Memory
@@ -144,9 +149,16 @@ impl Interpreter {
144149
autograd_tape: Vec::new(),
145150
yield_callbacks: Vec::new(),
146151
gen_stop_requested: false,
152+
last_expression_value: None,
147153
}
148154
}
149155

156+
/// Read (and clear) the most recent top-level expression value.
157+
/// Used by the MCP server to return the result of `omc_eval`.
158+
pub fn take_last_expression_value(&mut self) -> Option<Value> {
159+
self.last_expression_value.take()
160+
}
161+
150162
/// Register a host-side builtin that OMC code can call by name.
151163
/// The closure receives the evaluated argument values and returns
152164
/// either a Value (success) or an error message that propagates
@@ -877,6 +889,14 @@ impl Interpreter {
877889
}
878890
}
879891

892+
/// Take ownership of the current top-level return value. Used by
893+
/// the MCP server (and tooling) to read what the last `return`
894+
/// produced after `execute` finished. None when the program didn't
895+
/// return — equivalent to "no expression result".
896+
pub fn take_return_value(&mut self) -> Option<Value> {
897+
self.return_value.take()
898+
}
899+
880900
pub fn execute(&mut self, statements: Vec<Statement>) -> Result<(), String> {
881901
for stmt in statements {
882902
self.execute_stmt(&stmt)?;
@@ -1356,7 +1376,12 @@ impl Interpreter {
13561376
Ok(())
13571377
}
13581378
Statement::Expression(expr) => {
1359-
self.eval_expr(expr)?;
1379+
// Save the result so the MCP / REPL paths can read
1380+
// "what did the last top-level expression evaluate to"
1381+
// without re-running. Empty/silent expressions still
1382+
// leave the prior value in place.
1383+
let v = self.eval_expr(expr)?;
1384+
self.last_expression_value = Some(v);
13601385
Ok(())
13611386
}
13621387
Statement::VarDecl {

omnimcode-mcp/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "omnimcode-mcp"
3+
version.workspace = true
4+
edition.workspace = true
5+
authors.workspace = true
6+
license.workspace = true
7+
description = "MCP server exposing OMC eval / introspection / error explainer to LLM clients."
8+
9+
[[bin]]
10+
name = "omnimcode-mcp"
11+
path = "src/main.rs"
12+
13+
[dependencies]
14+
omnimcode-core = { path = "../omnimcode-core", default-features = false }
15+
serde = { version = "1.0", features = ["derive"] }
16+
serde_json = "1.0"

omnimcode-mcp/README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# omnimcode-mcp
2+
3+
MCP server for OMC. Lets an LLM client (Claude Desktop, Cursor, any
4+
JSON-RPC capable agent) call OMC as a runtime — eval code, look up
5+
builtins, get structured error explanations.
6+
7+
Built so an LLM can write idiomatic OMC without it being in training
8+
data: the introspection + error catalog tools give it everything it
9+
needs to discover the language at runtime.
10+
11+
## Tools exposed
12+
13+
- `omc_eval(code)` — evaluate OMC source, return result value
14+
- `omc_help(name)` — signature + description + example for a builtin
15+
- `omc_list_builtins(category?)` — enumerate documented builtins
16+
- `omc_categories()` — list builtin categories
17+
- `omc_unique_builtins()` — OMC-only primitives (no NumPy equivalent)
18+
- `omc_explain_error(message)` — pattern-match an error against the
19+
259-entry knowledge base; returns explanation + cause + fix
20+
- `omc_did_you_mean(name)` — typo suggestions over the known surface
21+
22+
## Build
23+
24+
```bash
25+
cargo build --release -p omnimcode-mcp
26+
# Binary lands at target/release/omnimcode-mcp
27+
```
28+
29+
## Claude Desktop config
30+
31+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`
32+
(macOS) or the equivalent on your platform:
33+
34+
```json
35+
{
36+
"mcpServers": {
37+
"omc": {
38+
"command": "/absolute/path/to/target/release/omnimcode-mcp"
39+
}
40+
}
41+
}
42+
```
43+
44+
Restart Claude Desktop. The LLM can now call `omc_eval`, `omc_help`,
45+
etc. directly.
46+
47+
## Why this matters for LLMs
48+
49+
OMC has ~200+ builtins, many added recently. Without a discoverable
50+
surface, an LLM will hallucinate `numpy.dot` or invent `arr_multiply`.
51+
With the MCP server wired in, the LLM:
52+
53+
1. Calls `omc_categories()` to see what's available
54+
2. Calls `omc_list_builtins("substrate")` to find OMC-unique primitives
55+
3. Calls `omc_help("arr_substrate_attention")` for signature + example
56+
4. Writes code, calls `omc_eval`
57+
5. On error, calls `omc_explain_error(msg)` for a one-line fix
58+
59+
The OMC-unique primitives — substrate-typed arrays, autograd that
60+
preserves φ-resonance, native lazy generators, harmonic ops — are
61+
the reason an LLM would pick OMC over NumPy/PyTorch. The MCP server
62+
makes those discoverable.
63+
64+
## Protocol
65+
66+
Line-delimited JSON-RPC 2.0 over stdin/stdout. Implements:
67+
- `initialize` (returns server info + capabilities)
68+
- `tools/list` (returns the tool catalog above)
69+
- `tools/call` (dispatches to a tool by name)
70+
71+
Notifications (no `id` field) are accepted silently. Anything else
72+
gets a "Method not found" error.
73+
74+
## Example manual session
75+
76+
```
77+
$ ./target/release/omnimcode-mcp
78+
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
79+
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
80+
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"omc_eval","arguments":{"code":"is_attractor(8);"}}}
81+
```
82+
83+
Returns:
84+
85+
```json
86+
{"jsonrpc":"2.0","id":3,"result":{"content":[{"text":"HInt { value: 1, resonance: 1.000, him: 0.382 }","type":"text"}],"isError":false}}
87+
```
88+
89+
Notice the substrate metadata in the response — that's the part Python
90+
can't give you.

0 commit comments

Comments
 (0)