-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtools.lua
More file actions
143 lines (120 loc) · 4.31 KB
/
tools.lua
File metadata and controls
143 lines (120 loc) · 4.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
-- MCP Tool Discovery & Dispatch
-- Discovers tools from Wippy registry (meta mcp.tool = true)
-- Handles tools/list and tools/call via registry.find() + funcs.call()
-- Entry kind: library.lua
local registry = require("registry")
local funcs = require("funcs")
local jsonrpc = require("jsonrpc")
---------------------------------------------------------------------------
-- Tool discovery from registry
---------------------------------------------------------------------------
local function discover(scope)
local entries, err = registry.find({kind = "function.lua"})
if err then
return nil, err
end
local tools = {}
for _, entry in ipairs(entries) do
local meta = entry.meta
if meta and meta["mcp.tool"] == true then
-- Scope filter: scoped tools only visible on matching endpoints
if meta["mcp.scope"] and meta["mcp.scope"] ~= scope then
-- skip: tool has a scope that doesn't match this endpoint
else
local name = meta["mcp.name"] or entry.id
tools[name] = {
entry_id = entry.id,
name = name,
description = meta["mcp.description"],
inputSchema = meta["mcp.inputSchema"],
annotations = meta["mcp.annotations"]
}
end
end
end
return tools, nil
end
---------------------------------------------------------------------------
-- tools/list handler
---------------------------------------------------------------------------
local function handle_list(id, params, scope)
local tools, err = discover(scope)
if err then
return jsonrpc.internal_error(id, "Failed to discover tools: " .. tostring(err))
end
local tool_list = {}
for _, tool in pairs(tools) do
local entry = { name = tool.name }
if tool.description then
entry.description = tool.description
end
if tool.inputSchema then
entry.inputSchema = tool.inputSchema
end
if tool.annotations then
entry.annotations = tool.annotations
end
table.insert(tool_list, entry)
end
return jsonrpc.encode_response(id, {tools = tool_list})
end
---------------------------------------------------------------------------
-- tools/call handler
---------------------------------------------------------------------------
local function handle_call(id, params, scope)
local tool_name = params.name
local arguments = params.arguments or {}
if not tool_name or tool_name == "" then
return jsonrpc.invalid_params(id, "Missing tool name")
end
local tools, err = discover(scope)
if err then
return jsonrpc.internal_error(id, "Failed to discover tools: " .. tostring(err))
end
local tool = tools[tool_name]
if not tool then
return jsonrpc.invalid_params(id, "Unknown tool: " .. tool_name)
end
-- Invoke via funcs.call
local result, call_err = funcs.call(tool.entry_id, arguments)
if call_err then
-- Tool errors → isError=true in result (per MCP spec)
return jsonrpc.encode_response(id, {
content = {{type = "text", text = tostring(call_err)}},
isError = true
})
end
-- Wrap result into MCP TextContent
local content
if type(result) == "string" then
content = {{type = "text", text = result}}
elseif type(result) == "table" and result.content then
content = result.content
else
content = {{type = "text", text = tostring(result)}}
end
return jsonrpc.encode_response(id, {
content = content,
isError = false
})
end
---------------------------------------------------------------------------
-- Top-level dispatch for tool methods
---------------------------------------------------------------------------
local function handle(msg, scope)
if msg.kind ~= "request" then
return nil
end
if msg.method == "tools/list" then
return handle_list(msg.id, msg.params or {}, scope)
elseif msg.method == "tools/call" then
return handle_call(msg.id, msg.params or {}, scope)
end
return nil
end
return {
discover = discover,
handle_list = handle_list,
handle_call = handle_call,
handle = handle
}