// STRYKE-MCPD — ENGINEERING REPORT

Four sublibs · stdout discipline · crash isolation · root jail · pure stryke (no [ffi], no cdylib)

>_EXECUTIVE SUMMARY

stryke-mcpd turns stryke's core MCP plumbing into shippable servers. The artifact story is the point: every mainstream MCP server requires a Node runtime or Python venv on the target machine; a stryke server is a script during development and a single static native binary after s build --release. The package contributes the two disciplines stdio servers live or die by — a protocol-clean stdout and crash isolation per tool — plus the parts every server rewrites: spec validation, arg checking, a root-jailed stock tool pack, and client-side envelope extraction for round-trip tests.

Boundary rule, same as every pure-stryke package: protocol mechanism (JSON-RPC framing, transport, tool registry) is strykelang core (mcp.rs, ai_sugar.rs); policy (what a valid spec is, what happens when a tool dies, where diagnostics go) lives here and versions independently.

4
sublibraries
81
public fns
0
cdylib / helper binaries
283
assertions in t/
4
runnable examples
22
CI gate scripts

~SUBLIBRARY BOUNDARIES

SublibOwnsDoesn't own
Mcpd::SchemaConstruction-time validation: identifier names, non-empty descriptions, wire-type params (string/number/boolean/array/object), coderef runs; check_args for declared-type arg checking inside a tool; validate_all for whole-set checks (full specs, unique names); introspection (param_names, tool_summary, describe) and rename.Serving. Schema builds and checks hashrefs; it never touches a transport.
Mcpd::ServerThe two disciplines: file-only logging (log_to/log/log_path — stdout carries JSON-RPC) and crash isolation (wrap converts a tool's die into an ERROR: text result — with the stryke die location stripped so the client sees only the message — and the server keeps serving; wrap_all applies it across a set; audit adds a success log). serve = validate + wrap_all + mcp_server_start.Protocol framing, transport, request dispatch — all mcp_server_start (core).
Mcpd::ToolsThe stock pack: fs_read/fs_list/fs_glob/fs_count/fs_grep/fs_grep_count/fs_first_match/fs_find/fs_stat/fs_readlink/fs_realpath/fs_du/fs_head/fs_tail/fs_slice/fs_hash/fs_exists/fs_lines/fs_write/fs_append/fs_replace/fs_chmod/fs_mkdir jailed under a configurable root (lexical .. resolution, escapes die), sh_exec with optional command allowlist, env_get/time_now/sys_info. all returns the pack as one list (32 specs); all(+{readonly => 1}) drops the 11 mutating tools (fs_write, fs_append, fs_replace, fs_chmod, fs_mkdir, fs_touch, fs_rmdir, fs_delete, fs_copy, fs_move, sh_exec), leaving 21.Anything not generally useful. Domain tools belong in the consumer's server script, composed alongside the stock pack.
Mcpd::ClientEnvelope policy: text joins text blocks from +{content => [...]}, texts returns them unjoined, content_types lists distinct block types, call_text is the call-then-extract composite, is_error/error_message recognize and unwrap the Server envelope, tool_names/has_tool query the discovery list.Connection plumbing — mcp_connect/mcp_tools/mcp_call/mcp_close are builtins, never re-wrapped.

=PUBLIC SURFACE

81 public functions across four sublibs, plus the bin/mcpd.stk CLI front-end. Every function is a fn Mcpd::<Sublib>::<name> in lib/; counts are the top-level definitions in each file.

SublibFileFnsSurface
Mcpd::Schemalib/Schema.stk21Spec builder + validation (tool, check_args, validate_all, is_valid), introspection (types, param_names, param_count, has_param, param_type, params_of_type, tool_names, find), wire-format conversion (to_json_schema/from_json_schema, to_tool_list/from_tool_list, tool_map), catalogs (tool_summary, summaries, describe), rename.
Mcpd::Serverlib/Server.stk9serve, wrap, wrap_all, checked, wrap_all_checked, audit, log_to, log, log_path.
Mcpd::Toolslib/Tools.stk3532 stock tool generators (18 read + 11 mutating + env_get/time_now/sys_info — 32 specs in the full served pack, 21 read-only) plus all, jail, names.
Mcpd::Clientlib/Client.stk16Envelope unpacking (text, texts, text_lines, content_types, content_of_type, block_count), call composites (call_text, call_lines, call_texts), error handling (is_error, error_message), discovery (tool_names, tool_count, tool_descriptions, has_tool, tool_schema).

The bin/mcpd.stk CLI front-end is pure stryke (no helper binary): new NAME [DIR] writes a validated server skeleton, serve-stock [ROOT] serves the stock pack jailed to ROOT (default getcwd()) over stdio, tools lists the stock tool names, version prints the package version, help prints usage. Wire a built or scripted server into any MCP client as { "command": "stryke", "args": ["server.stk"] }.


$THE TWO STDIO DISCIPLINES

DisciplineMechanismEnforcement
stdout is protocolMcpd::Server::log_to(PATH) routes every diagnostic to an append-only file; Mcpd::Server::log is a no-op until configured. Nothing in lib/ writes to stdout.tests/repo-contract.sh greps lib/*.stk for p /print /say  (file-handle prints excepted) and fails CI on any hit.
a dying tool is a result, not an outageMcpd::Server::wrap evals every run coderef: die → logged + returned as ERROR: <msg> text. The client sees a result; the connection stays up.Round-trip assertion in t/: call kaboom (dies), assert error envelope, then call add on the same connection and assert 42/2.

&THE ROOT JAIL

Stock filesystem tools are exposed to a language model on the other end of the wire; unbounded paths are how an fs_read becomes an exfiltration primitive. Mcpd::Tools::jail($root, $path) is a lexical resolver: relative paths anchor under root, ./// collapse, .. resolves but dies the moment it would climb above root, and absolute paths outside root die. The die propagates into Server::wrap's envelope — the client gets ERROR: path escapes root, the server keeps serving. sh_exec gets the same treatment via an optional first-word allowlist.


/WHY A PACKAGE, NOT CORE


#TEST SURFACE

t/test_mcpd.stk uses stryke's native assertions (assert_eq, assert_ok, assert_match, assert_dies) and exits TAP-style via test_run. Headless-CI safe — the round trip serves a generated script on the same machine over stdio.

AreaAssertion families covered
Schematool accepts a full spec and rejects non-identifier names, empty descriptions, unknown param types, non-coderef runs; check_args accepts exact args and rejects missing / mistyped / extra params; validate_all counts, rejects non-arrayrefs, duplicates, partial specs; param_names sorted, tool_summary signature, describe catalog + validation, rename preserves fields and re-validates.
Serverwrap passes results through and converts die to the ERROR: envelope; log_to + log + log_path manage the configured file; wrap_all preserves every spec key while wrapping (pins strykelang BUG-305); audit logs successful calls with a byte count.
Toolsjail resolves inside, collapses interior .., dies on dotdot and absolute escapes; stock pack validates as 32 specs (21 in readonly mode); fs_read/fs_list/fs_glob/fs_count/fs_grep/fs_grep_count/fs_first_match/fs_find/fs_stat/fs_readlink/fs_realpath/fs_write/fs_replace/fs_chmod all exercised through the jail incl. escape rejection, depth caps, dotfile listing; sh_exec runs an allowlisted command and rejects everything else.
Clienttext joins multi-block content and returns "" on empty; texts unjoined; content_types distinct + sorted; is_error detects the wrap envelope; error_message strips the prefix (undef on clean); has_tool queries discovery over the live round trip.
Round tripGenerate server script → mcp_connect("stdio:stryke ...") → tool list matches → add(2,40) returns 42 → dying tool returns the envelope → same connection still serves.

%CI GATES

22 shell gate scripts in tests/ are wired into .github/workflows/ci.yml, plus a stryke job that installs the latest strykelang release binary, syntax-checks every .stk file, installs the package into the global store, and runs s test t/. Every gate is reproducible locally with bash tests/<gate>.sh.

BucketGates
repo-contractPure-stryke (no Cargo.toml, no [ffi]). lib/ has exactly 5 files. Every sublib declares package Mcpd::<Name>. stdout discipline: lib/ never prints to stdout. Examples shebang. bin/mcpd.stk shebang. Makefile has test/install/clean. LICENSE is MIT.
docs gates<h1> present, <body> tag, closing </html>, no deprecated tags, no inline event handlers, no placeholder hrefs, no plaintext http://, target="_blank" always paired with rel="noopener noreferrer".
README gatesAt least one shields.io badge, at least one h2 section, at least one https link, ends with final newline.
polish gatesWorkflow files have no tabs (YAML), every tests/*.sh has +x and a bash shebang, man pages (if any) have synopsis section + final newline + no trailing whitespace.

!EXTENDING

Adding a stock tool means three edits:

  1. Add the fn Mcpd::Tools::name generator to lib/Tools.stk returning a Mcpd::Schema::tool(...) spec; jail any path input, allowlist any exec input. Add it to Mcpd::Tools::names and all.
  2. Assert it in t/test_mcpd.stk: spec validates, happy path runs through $spec->{run}->(...) directly, every guard dies.
  3. Mention it in the README sublib table and docs/index.html.

Custom servers don't touch this repo: mcpd new NAME emits a skeleton that pulls the package from the store.


@PROJECT METADATA

ItemValue
LicenseMIT
AuthorMenkeTechnologies
Repositorygithub.com/MenkeTechnologies/stryke-mcpd
Parent languagestrykelang
Meta umbrellaMenkeTechnologiesMeta
Issuesgithub.com/MenkeTechnologies/stryke-mcpd/issues