#!/usr/bin/env lua -- Example lmcp server — customize tools for your host local dir = arg[0]:match("(.*/)") or "./" package.path = package.path .. ";" .. dir .. "?.lua" local lmcp = require("lmcp") local server = lmcp.new("my-tools", { port = tonumber(os.getenv("LMCP_PORT")) or 8080, -- Optional: Bearer token auth from config file -- conf = dir .. "lmcp.conf", -- Or set directly: -- auth_token = "my-secret-token", }) -- Non-blocking shell execution with timeout local function run(cmd, timeout_sec) timeout_sec = timeout_sec or 120 local base = os.tmpname() local out_file = base .. ".out" local done_file = base .. ".done" local sh_cmd = string.format("(%s) > '%s' 2>&1; echo $? > '%s'", cmd, out_file, done_file) os.execute("sh -c ' " .. sh_cmd .. " ' &") local elapsed = 0 local interval = 50 while elapsed < timeout_sec * 1000 do local f = io.open(done_file, "r") if f then f:close(); break end if interval < 1000 then os.execute("sleep 0." .. string.format("%03d", interval)) else os.execute("sleep " .. math.ceil(interval / 1000)) end elapsed = elapsed + interval if interval < 2000 then interval = math.floor(interval * 1.5) end end local f = io.open(out_file, "r") local output = f and f:read("*a") or "" if f then f:close() end os.remove(out_file); os.remove(done_file); os.remove(base) if elapsed >= timeout_sec * 1000 then return "Error: timed out after " .. timeout_sec .. "s" end return output ~= "" and output or "(no output)" end -- Register tools server:tool("shell", "Execute a shell command.", { type = "object", properties = { command = { type = "string" }, cwd = { type = "string" }, timeout = { type = "integer", default = 120 }, }, required = { "command" }, }, function(a) local c = a.command if a.cwd then c = "cd \"" .. a.cwd .. "\" && " .. c end return run(c, a.timeout or 120) end) server:tool("read_file", "Read a file.", { type = "object", properties = { path = { type = "string" } }, required = { "path" }, }, function(a) local f = io.open(a.path, "r") if not f then return "Error: could not read " .. a.path end local c = f:read("*a"); f:close(); return c end) server:tool("write_file", "Write content to a file.", { type = "object", properties = { path = { type = "string" }, content = { type = "string" } }, required = { "path", "content" }, }, function(a) local f = io.open(a.path, "w") if not f then return "Error: could not write " .. a.path end f:write(a.content); f:close() return string.format("Written %d bytes to %s", #a.content, a.path) end) server:tool("list_dir", "List directory contents.", { type = "object", properties = { path = { type = "string", default = "." } }, }, function(a) return run("ls -la '" .. (a.path or ".") .. "'", 10) end) server:tool("search_files", "Search for files by name.", { type = "object", properties = { pattern = { type = "string" }, path = { type = "string", default = "/" }, max_depth = { type = "integer", default = 4 }, }, required = { "pattern" }, }, function(a) return run(string.format("find '%s' -maxdepth %d -name '%s' 2>/dev/null", a.path or "/", a.max_depth or 4, a.pattern), 30) end) server:run()