Files
lmcp/example_server.lua
T
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>
2026-04-11 19:15:27 +02:00

101 lines
3.4 KiB
Lua

#!/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()