README.md
June 24, 2026 · View on GitHub
███╗ ███╗ ██████╗██████╗ ██████╗
████╗ ████║██╔════╝██╔══██╗██╔══██╗
██╔████╔██║██║ ██████╔╝██║ ██║
██║╚██╔╝██║██║ ██╔═══╝ ██║ ██║
██║ ╚═╝ ██║╚██████╗██║ ██████╔╝
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═════╝
[ m c p s e r v e r s, n a t i v e ]
[MCP SERVERS WITHOUT A RUNTIME]
"Every MCP server today drags a Node runtime or a Python venv to the target machine. This one is a single static native binary."
stryke-mcpd is the policy layer for writing MCP (Model Context Protocol) servers in stryke: validated tool specs (Mcpd::Schema), crash-isolated serving with file-only logging (Mcpd::Server), a root-jailed stock tool pack (Mcpd::Tools), and result-envelope helpers for round trips (Mcpd::Client). The protocol plumbing — mcp_server_start, mcp_connect, mcp_call, the tool fn / mcp_server desugar — is strykelang core. Write the server in a few lines, s build --release it, ship one binary. No [ffi] table, no cdylib, no helper binary — just .stk modules loaded on use Mcpd. Created by MenkeTechnologies.
strykelang · MenkeTechnologiesMeta · stryke-utils · stryke-fleet
Read the Docs · Engineering Report
Table of Contents
- [0x00] Why a Package, Not Core
- [0x01] Install
- [0x02] Quick Start
- [0x03] Sublibraries
- [0x04] What's NOT in Here
- [0x05] CLI
- [0x06] Tests
- [0x07] Layout
- [0xFF] License
[0x00] Why a Package, Not Core
The protocol is already core: mcp_server_start serves stdio JSON-RPC, mcp_connect/mcp_tools/mcp_call/mcp_close are the client side, and the tool fn / mcp_server "name" { ... } desugar exists at the source level. What core does NOT impose is discipline, and MCP servers need exactly two pieces of it:
- stdout belongs to the protocol. One stray
printcorrupts the JSON-RPC stream for every connected client.Mcpd::Server::log_toroutes diagnostics to a file;tests/repo-contract.shgrepslib/and fails CI if anything in this package ever writes to stdout. - A dying tool must not kill the server. Every
runcoderef is wrapped: adiecomes back to the client as anERROR: ...text result, the failure is logged, and the server keeps serving. The round-trip test pins this — call a tool that dies, then call another tool on the same connection.
Plus the parts everyone rewrites per server: construction-time spec validation with named diagnostics (Schema), arg checking against declared types (check_args), a root-jailed filesystem/shell tool pack (Tools), and result-envelope extraction (Client).
[0x01] Install
# From a release:
s pkg install -g github.com/MenkeTechnologies/stryke-mcpd
# From a local checkout:
git clone https://github.com/MenkeTechnologies/stryke-mcpd
cd stryke-mcpd
s pkg install -g . # installs into ~/.stryke/store/stryke-mcpd@<version>/
# Or via Makefile:
make install
No cargo step. No cdylib. The installed store directory contains only stryke.toml + lib/*.stk — no compiled artifacts, fully portable across platforms.
[0x02] Quick Start
use Mcpd
# A server is one call: validated specs in, stdio JSON-RPC out.
Mcpd::Server::log_to("$ENV{HOME}/.cache/calc.log") # stdout stays clean
Mcpd::Server::serve("calc", [
Mcpd::Schema::tool("add", "Add two numbers",
+{ a => "number", b => "number" },
fn { _->{a} + _->{b} }),
@{ Mcpd::Tools::all(+{ root => getcwd() }) }, # stock pack, jailed
])
# Client side — round trip any server over stdio:
val $h = mcp_connect("stdio:stryke calc_server.stk")
p join(", ", @{ Mcpd::Client::tool_names($h) })
p Mcpd::Client::call_text($h, "add", +{ a => 2, b => 40 }) # 42
p Mcpd::Client::is_error(mcp_call($h, "always_fails", +{})) # 1 — server still alive
mcp_close($h)
# Ship it: AOT-compile the server to a single static native binary.
s build --release # target/release/<name> — no interpreter on the target
[0x03] Sublibraries
| # | Module | File | Fns | Highlights |
|---|---|---|---|---|
| 1 | Mcpd::Schema | lib/Schema.stk | 21 | tool (validated spec) · check_args (type-checked args) · validate_all (unique names, full specs) · is_valid (non-dying boolean form) · types · param_names · param_count · has_param · param_type · params_of_type · tool_names (sorted names of a local set) · find (named-spec lookup) · to_json_schema (MCP inputSchema) · from_json_schema (inputSchema → params, inverse) · to_tool_list (MCP tools/list payload) · from_tool_list (tools/list → descriptors, inverse) · tool_map (name → spec dispatch index) · tool_summary · summaries (per-tool list) · describe (catalog) · rename |
| 2 | Mcpd::Server | lib/Server.stk | 9 | serve (validate + wrap + mcp_server_start) · wrap (die → ERROR: envelope) · wrap_all · checked (arg-check before body) · wrap_all_checked (strict serving) · audit (log successes) · log_to/log/log_path (file-only diagnostics) |
| 3 | Mcpd::Tools | lib/Tools.stk | 35 | fs_read · fs_list · fs_glob (shell-glob filter) · fs_count (entry count) · fs_grep · fs_grep_count (match count) · fs_first_match (first hit + line no.) · fs_find (recursive, capped) · fs_stat · fs_readlink (symlink target) · fs_realpath (jailed path resolution) · fs_du (recursive size) · fs_head (first N lines) · fs_tail (last N lines) · fs_slice (line range) · fs_hash (checksum) · fs_exists · fs_lines (wc) · fs_write · fs_append · fs_replace (regex s///) · fs_chmod (octal mode) · fs_mkdir · fs_touch (empty file / mtime) · fs_rmdir · fs_delete · fs_copy · fs_move · sh_exec (allowlist) · env_get · time_now · sys_info · all (readonly mode) · jail (root confinement) · names |
| 4 | Mcpd::Client | lib/Client.stk | 16 | text (envelope → string) · texts (unjoined blocks) · text_lines (split into lines) · content_types · content_of_type (full blocks of a type) · block_count · call_text (call + extract) · call_lines (call + line split) · call_texts (call + unjoined blocks) · is_error · error_message (strip prefix) · tool_names · tool_count · tool_descriptions (name → desc) · has_tool · tool_schema (a tool's advertised inputSchema) |
[0x04] What's NOT in Here
By design — these are stryke builtins, so we don't re-wrap them:
| Category | Builtins (call directly) |
|---|---|
| Server plumbing | mcp_server_start · mcp_serve_registered_tools · ai_register_tool and the tool fn / mcp_server "name" { ... } source-level desugar |
| Client plumbing | mcp_connect · mcp_tools · mcp_resources · mcp_prompts · mcp_call · mcp_resource · mcp_prompt · mcp_close |
| AI attachment | mcp_attach_to_ai · mcp_detach_from_ai · mcp_attached |
| File / process | slurp · spurt · getcwd · qx(...) · opendir/readdir |
If a function in this library can be replaced with one builtin call, it's a bug.
[0x05] CLI
s bin/mcpd.stk new myserver # write ./myserver.stk skeleton (validated, logged, ready)
s bin/mcpd.stk serve-stock /srv/data # stock pack jailed to /srv/data, served on stdio
s bin/mcpd.stk tools # list the stock tool pack
s bin/mcpd.stk version
s bin/mcpd.stk help
Wire a server into any MCP client config as { "command": "stryke", "args": ["server.stk"] } — or build it and point at the binary with no args at all.
[0x06] Tests
s test t/ # assertions across every public function
t/test_mcpd.stk asserts Schema/Tools/Client as pure functions (spec validation, arg checking, jail escapes, envelope parsing), then runs a live round trip: it generates a server script, serves it over stdio via mcp_connect, lists tools, calls one, kills one, and proves the server survived. Headless-CI safe — everything happens on the local machine.
[0x07] Layout
stryke-mcpd/
├── stryke.toml # pure-stryke package manifest (no [ffi])
├── Makefile # test / install / clean
├── LICENSE # MIT
├── lib/
│ ├── Mcpd.stk # `use Mcpd` — pulls all four sublibs
│ ├── Schema.stk # `use Mcpd::Schema` — validated tool specs
│ ├── Server.stk # `use Mcpd::Server` — crash-isolated serving
│ ├── Tools.stk # `use Mcpd::Tools` — root-jailed stock pack
│ └── Client.stk # `use Mcpd::Client` — result-envelope helpers
├── bin/
│ └── mcpd.stk # CLI front-end (new / serve-stock / tools)
├── t/
│ └── test_mcpd.stk # pure asserts + live stdio round trip
├── examples/
│ ├── calc_server.stk # minimal server (the round-trip target shape)
│ ├── round_trip.stk # client: list, call, error envelope, survival
│ ├── stock_server.stk # stock pack jailed to cwd
│ └── jailed_fs.stk # live write/read + a blocked jail escape over stdio
├── tests/ # shell gate scripts (CI lints)
└── docs/ # GitHub Pages site
[0xFF] License
MIT — see LICENSE.