12 Commits

Author SHA1 Message Date
test0r b81b021b5b v0.5.4: ship examples/lmcp.service template
Companion to lmcp-hub.service. Gives a copy-and-edit starting point for
per-host lmcp instances (foo-tools style). Handles the Arch-vs-Debian
/usr/bin/lua vs /usr/bin/lua5.4 split via a comment pointing users to
override ExecStart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.5.4
2026-04-21 20:12:43 +00:00
test0r 17af91a99b v0.5.3: hub hardening — hard ssh timeout, parallel probes, sticky DOWN cache
Three fixes addressing the recurring "hub wedges on offline backends" class
of failures (2 incidents in 24h, root-caused to single-threaded Lua + an
uninterruptible os.execute ssh call):

1. Hard wall-clock cap on ssh fallback via GNU `timeout --kill-after=2 30`.
   ConnectTimeout alone only bounds TCP connect; a half-dead sshd (auth
   stall, remote bash-s hang) used to freeze the whole event loop
   indefinitely. Configurable via LMCP_HUB_SSH_HARD_TIMEOUT. Also adds
   ServerAliveInterval=5/Count=2 so an established-but-dead tunnel dies.

2. Parallel lmcp probes for remote_list_hosts. Shells out a single bash
   fan-out of curl -m 3 calls, bounded by PROBE_BUDGET. Wall clock for a
   full 12-backend probe went from ~28 s (sum of per-host ssh connect
   timeouts) to ~3 s.

3. Probe is lmcp-only — ssh is no longer used as health check. The hub
   exists to absorb lots of offline hosts, so an expensive ssh per probe
   was the exact wrong tradeoff. Actual remote_* tool calls still fall
   through to ssh fallback when lmcp is down.

4. Sticky DOWN cache with exponential backoff: 60 → 120 → 240 → 480 →
   900 s. Prevents a sleeping fleet from burning probe budget on every
   health check. UP hosts still use 30 s TTL. Tunable via
   LMCP_HUB_PROBE_TTL_{UP,DOWN_MIN,DOWN_MAX}.

5. Per-request logging to stderr (tool, host, via, elapsed) — invisible
   before, now captured in journal for the next hang's RCA.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.5.3
2026-04-21 11:58:44 +00:00
test0r b29a2716d1 v0.5.2: shell_bg / remote_shell_bg — background launch tools
server.lua gains a shell_bg tool that launches a detached command via
setsid + nohup + stdio-redirect + &, returns immediately with PID and
log path. Linux-only for MVP (Windows Start-Process equivalent TBD).

hub.lua gains remote_shell_bg, forwarding to backend shell_bg. lmcp-only,
no ssh fallback — fallback for fire-and-forget is semantically murky.

Addresses the 'how do I launch a daemon over lmcp without the sentinel-
file wrapper blocking forever' question. Existing remote_shell keeps
its current synchronous-with-timeout behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.5.2
2026-04-20 09:34:24 +00:00
test0r 555beb9fd9 v0.5.1: search_files uses find -L for macOS symlink start paths
BSD find on macOS silently emits nothing when the starting path is
itself a symlink (no trailing slash, no -L). On riemann with Homebrew,
/usr/local/share/lua is a symlink to /usr/local/Cellar/luarocks/.../share/lua
which tripped this — search_files returned empty for clearly-matching
patterns. GNU find on Linux follows the starting arg by default, so the
bug was invisible on every other host.

Add -L explicitly. Both BSD and GNU find accept it, both detect cycles,
and behavior becomes consistent.

Fixes marfrit-tracker task #16 (opened 2026-04-18 while stress-testing
riemann-tools MCP).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.5.1
2026-04-19 12:48:05 +00:00
test0r 490e688cc1 v0.5.0: add hub — fleet-wide MCP broker
One lmcp server on a central host (typically hertz) that proxies
remote_* tools to every backend in a registry, with a clean SSH
fallback for hosts whose lmcp is temporarily down or not installed.

Tools: remote_list_hosts, remote_{shell,read_file,write_file,edit_file,
list_dir,search_files}. Each takes a `host` argument naming the target
in /opt/herding/etc/hub-backends.conf (or $LMCP_HUB_BACKENDS).

Lazy 30s health cache; `remote_list_hosts force=true` bypasses it.
Bearer auth on inbound (standard lmcp opts.conf / LMCP_TOKEN machinery);
backend Bearer tokens kept in the registry and forwarded per-call.

SSH fallback uses `ssh host 'bash -s' < local_script` — stdin-piped
script body is the canonical shell-escape-free technique. Covers
shell/read_file/write_file/list_dir/search_files. edit_file is lmcp-only
because the literal-match + uniqueness check is nontrivial to replicate
safely in shell.

Ships an example systemd unit and a commented backends.conf template
in examples/. No migration required for existing lmcp deployments —
hub.lua is additive alongside the existing server.lua.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.5.0
2026-04-19 12:29:13 +00:00
test0r c5884d6a97 scripts/lmcp-install-macos.sh: fix for real-world Homebrew
Original draft assumed `brew install lua luasocket` works. It doesn't:
luasocket isn't a brew formula (install via luarocks), and default `lua`
is 5.5 while the rest of the fleet is on 5.4. Fix tested on riemann
(Intel Mac, macOS 14.8.3):

- Pin to lua@5.4 (keg-only brew formula) — matches fleet library paths.
- Install luasocket via luarocks into the user-local rocks tree.
- Source brew shellenv ourselves so non-login bash shells can find brew.
- Bake LUA_PATH / LUA_CPATH into the LaunchAgent plist so the service
  resolves `require 'socket'` from ~/.luarocks/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.4.2
2026-04-18 12:37:05 +00:00
test0r 2f2c1f3036 Add scripts/lmcp-install-macos.sh + LMCP_TOKEN env fallback
lmcp.lua: if opts.auth_token and opts.conf are both unset, fall back to
the LMCP_TOKEN environment variable. Empty string treated as unset.
This is the primitive launchd/systemd drop-ins need — no conf file
bookkeeping on hosts that don't already use one.

scripts/lmcp-install-macos.sh: macOS installer via Homebrew. Drops the
Lua library files into $(brew --prefix)/share/lua/5.4/, mints (or
reuses) a Bearer token stored at $(brew --prefix)/etc/lmcp/token,
installs a ~/Library/LaunchAgents/ plist with LMCP_TOKEN baked in,
launchctl-loads it, and smoke-tests. Prints the Claude Code ~/.claude.json
snippet at the end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.4.1
2026-04-18 10:45:54 +00:00
test0r b00b8ef63c Add edit_file tool — Claude Code Edit semantics
Literal string replacement with uniqueness check. Fails if old_string
is not found or matches multiple times (unless replace_all=true).

Matches the Claude Code harness Edit tool so sibling lmcp clients get
the same behaviour they already expect for in-place patches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.4.0
2026-04-17 15:47:17 +00:00
marfrit c6efc8f685 initial import: lmcp 0.1.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.3.0
2026-04-14 19:58:40 +00:00
test0r 6bf0f450dc Security hardening: body size limit, JSON depth limit, timing-safe auth
- Add MAX_BODY_SIZE (64KB) check before reading body — prevents pre-auth
  OOM on internet-facing deployments
- Add JSON nesting depth limit (64 levels) — prevents C stack overflow
  that bypasses pcall and crashes the process
- Timing-safe token comparison via XOR accumulate — prevents timing
  oracle on Bearer token
- Auth token from LMCP_TOKEN env var (highest priority) — avoids storing
  token in a file readable by the read_file tool
- Silent handling of unknown JSON-RPC notifications (spec compliance)
- Exact path matching on /mcp endpoint (was prefix-based)
- Remove dead json.array() function

Findings from architecture review + security audit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 20:45:16 +02:00
test0r abd9db30f2 Add Bearer auth, rewrite example server, add README
- lmcp.lua: optional Bearer token auth via conf file or explicit token
- lmcp.lua: CORS Authorization header allowed
- example_server.lua: rewritten with non-blocking shell, file ops, search
- README.md: usage, auth config, Claude Code integration, tool examples

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.2.0
2026-04-11 19:15:27 +02:00
test0r 2bd661a8c9 Initial release: Lua MCP server library
Zero-dependency MCP (Model Context Protocol) server in pure Lua.
Only requires luasocket. 2MB RSS vs Python FastMCP's 97MB.

- json.lua: pure Lua JSON encoder/decoder (~150 lines)
- lmcp.lua: MCP server with streamable-http transport (~230 lines)
- example_server.lua: shell/file tools demo

Implements MCP 2025-03-26: initialize, tools/list, tools/call,
notifications/initialized, ping. JSON-RPC 2.0. SSE support. CORS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:54:25 +00:00