Add MCP server to AST tool

Integrates the MCP server functionality into the AST tool binary. This
allows the tool to act as a server for LLM integration, enabling
communication over stdio. The server can list available bindings and
execute scripts.
This commit is contained in:
2026-03-25 14:03:22 +01:00
parent 6042415dfc
commit 641a19e736
6 changed files with 386 additions and 2 deletions
Generated
+235
View File
@@ -468,6 +468,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -660,6 +666,7 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-link 0.2.1",
]
@@ -849,6 +856,40 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
[[package]]
name = "darling"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "dispatch"
version = "0.2.0"
@@ -906,6 +947,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "ecolor"
version = "0.33.3"
@@ -1236,12 +1283,48 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.32"
@@ -1272,6 +1355,12 @@ dependencies = [
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
version = "0.3.32"
@@ -1284,9 +1373,13 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"slab",
]
@@ -1631,6 +1724,12 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.1.0"
@@ -1918,6 +2017,9 @@ dependencies = [
"fastrand",
"insta",
"regex",
"rmcp",
"serde",
"tokio",
]
[[package]]
@@ -2379,6 +2481,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pastey"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec"
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -2598,6 +2706,26 @@ dependencies = [
"bitflags 2.11.0",
]
[[package]]
name = "ref-cast"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "regex"
version = "1.12.3"
@@ -2633,6 +2761,41 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]]
name = "rmcp"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba6b9d2f0efe2258b23767f1f9e0054cfbcac9c2d6f81a031214143096d7864f"
dependencies = [
"async-trait",
"base64",
"chrono",
"futures",
"pastey",
"pin-project-lite",
"rmcp-macros",
"schemars",
"serde",
"serde_json",
"thiserror 2.0.18",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "rmcp-macros"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab9d95d7ed26ad8306352b0d5f05b593222b272790564589790d210aa15caa9e"
dependencies = [
"darling",
"proc-macro2",
"quote",
"serde_json",
"syn",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
@@ -2686,6 +2849,32 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schemars"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
dependencies = [
"chrono",
"dyn-clone",
"ref-cast",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@@ -2747,6 +2936,17 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_derive_internals"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
@@ -3058,6 +3258,41 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tokio"
version = "1.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
dependencies = [
"bytes",
"pin-project-lite",
"tokio-macros",
]
[[package]]
name = "tokio-macros"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
+3
View File
@@ -10,6 +10,9 @@ clap = { version = "4.5", features = ["derive"] }
chrono = "0.4"
regex = "1.10"
fastrand = "2.3"
rmcp = { version = "1.2", features = ["server", "transport-io"] }
tokio = { version = "1", features = ["rt", "io-std", "macros"] }
serde = { version = "1", features = ["derive"] }
[dev-dependencies]
insta = { version = "1.39", features = ["yaml"] }
+122 -2
View File
@@ -147,11 +147,11 @@ The Myc runtime environment provides a collection of built-in functions and macr
(do
;; Create a series with a typed layout (Struct-of-Arrays under the hood)
(def my_ticks (series {:price :float :volume :int :msg :text}))
(push my_ticks {:price 10.5 :volume 100 :msg "A"})
(push my_ticks {:price 11.2 :volume 200 :msg "B"})
(push my_ticks {:price 15.5 :volume 300 :msg "C"})
;; Extract the price series specifically
(def prices (.price my_ticks))
@@ -159,3 +159,123 @@ The Myc runtime environment provides a collection of built-in functions and macr
(+ (prices 0) (prices 1)) ;; Output: 26.7
)
```
## 8. Common Mistakes & Pitfalls
This section is especially relevant for LLMs generating Myc code.
### Wrong keywords from other Lisps
| Wrong (Clojure/Scheme) | Correct Myc |
|---|---|
| `(let [x 1] ...)` | `(do (def x 1) ...)` |
| `(begin ...)` | `(do ...)` |
| `(lambda [x] ...)` | `(fn [x] ...)` |
| `(define x 1)` | `(def x 1)` |
| `(set! x 2)` | `(assign x 2)` |
| `(cond ...)` | nested `(if ...)` |
| `(not= a b)` | `(<> a b)` |
### Series indexing is reversed
Index `0` is the **most recently pushed** item, not the oldest. This is the opposite of normal array indexing:
```clojure
(push s {:v 1})
(push s {:v 2})
(push s {:v 3})
;; ((.v s) 0) => 3 (newest)
;; ((.v s) 1) => 2
;; ((.v s) 2) => 1 (oldest)
```
### Record keys must be keywords, not strings
```clojure
;; WRONG:
{"price" 10.5}
;; CORRECT:
{:price 10.5}
```
### `again` is only valid inside `fn`
`(again ...)` jumps back to the enclosing `fn`. Using it outside a function is a compile error.
```clojure
;; WRONG — top-level again:
(again 1 2)
;; CORRECT — inside fn:
(fn [n] (if (= n 0) "done" (again (- n 1))))
```
### `check_syntax` accepts only a single expression
The `check_syntax` tool uses the single-expression compiler pass. Wrap multiple expressions in `(do ...)`:
```clojure
;; WRONG:
(def x 1) (+ x 2)
;; CORRECT:
(do (def x 1) (+ x 2))
```
### Field accessors are functions, not property syntax
```clojure
;; WRONG — not valid syntax:
user.name
;; CORRECT — field accessor called as a function:
(.name user)
;; Also correct — extract accessor first, then call:
(def get-name .name)
(get-name user)
```
**Recursive Function with Tail-Call Optimization:**
```clojure
(do
;; Factorial using explicit TCO via (again ...) — like Clojure's recur
(def factorial
(fn [n acc]
(if (= n 0)
acc
(again (- n 1) (* acc n)))))
(factorial 10 1) ;; Output: 3628800
)
```
**Macro Definition:**
```clojure
(do
;; Define a macro that inverts a condition
(macro unless [c body]
`(if ~c ... ~body))
(unless false "executed") ;; Output: "executed"
)
```
**Series with while Loop:**
```clojure
(do
(def ticks (series {:price :float}))
(def i 0)
;; Push 5 prices into the series
(while (< i 5)
(do
(push ticks {:price (* i 1.5)})
(assign i (+ i 1))))
;; Read back the two most recent prices
(def p (.price ticks))
(+ (p 0) (p 1)) ;; newest + second-newest
)
```
+13
View File
@@ -600,6 +600,19 @@ impl Environment {
}
/// Returns the names of all bindings registered in the fixed RTL scope.
/// Useful for introspection (e.g., MCP server listing available built-ins).
pub fn list_bindings(&self) -> Vec<String> {
let root_scopes = self.root_scopes.borrow();
let mut names: Vec<String> = root_scopes[0]
.locals
.keys()
.map(|sym| sym.name.to_string())
.collect();
names.sort();
names
}
pub fn dump_ast(&self, source: &str) -> Result<String, String> {
self.preload_dependencies(source, None)?;
let compiled = self.compile(source).into_result()?;
+1
View File
@@ -3,6 +3,7 @@ pub mod compiler;
pub mod diagnostics;
pub mod environment;
pub mod lexer;
pub mod mcp_server;
pub mod nodes;
pub mod parser;
pub mod rtl;
+12
View File
@@ -39,10 +39,22 @@ struct Cli {
/// Disable optimization
#[arg(long)]
no_opt: bool,
/// Start MCP server (stdio transport, for LLM integration)
#[arg(long)]
mcp: bool,
}
fn main() {
let cli = Cli::parse();
if cli.mcp {
if let Err(e) = myc::ast::mcp_server::run() {
eprintln!("MCP server error: {}", e);
std::process::exit(1);
}
return;
}
let mut env = Environment::new();
env.optimization = !cli.no_opt;