mcp works

This commit is contained in:
2026-08-21 22:34:27 +07:00
parent da21790263
commit d2592ab6d6
5 changed files with 161 additions and 197 deletions
+40 -82
View File
@@ -1,36 +1,12 @@
module toolRegistry
export toolStore, registerTool, registerAllTools, getTools, clearTools, listTool
export registerTool, registerAllTools, clearTools, listTool
using Dates
using JSON, DataStructures
using ..type
"""
Per-agent isolated tool storage.
Each agent gets its own `toolStore` so tool registration is independent.
# Fields
- `tools::OrderedDict{String, agentTool}` — keyed by name for O(1) lookup + ordered iteration
- `name::String` — identifier for debugging/logs
"""
struct toolStore
tools::OrderedDict{String, agentTool}
name::String
end
"""
toolStore(; name="default") -> toolStore
Create a new empty tool store.
# Keyword Arguments
- `name::String`: Display name for logging (default: `"default"`)
"""
function toolStore(; name::String="default")::toolStore
toolStore(OrderedDict{String, agentTool}(), name)
end
# ── MCP helper functions ────────────────────────────────────────────
@@ -40,7 +16,7 @@ Extract text from MCP tool result content array.
Handles JSON-RPC 2.0 result content format:
{"content": [{"type": "text", "text": "..."}], "isError": false}
"""
function _extract_text_content(result::Dict)::String
function _extract_text_content(result::AbstractDict)::String
content = get(result, "content", Any[])
if content isa Vector && !isempty(content)
lines = String[]
@@ -90,8 +66,7 @@ function _wrap_mcp_tool(mcpserver, tool_def::AbstractDict{String, Any}; eventSin
inputSchema=params,
execute=(toolCallId::String, args::AbstractDict{String, Any},
signal::Union{Nothing,abortSignal},
eventSink,
llmCall=nothing) -> begin
eventSink) -> begin
try
response = mcpserver("tools/call", name, args)
@@ -140,7 +115,7 @@ Handles pagination via `nextCursor`. Skips tools already registered.
# Returns
- `Int`: number of new tools registered
"""
function register_mcp_tools(mcpserver, store::toolStore; eventSink=nothing)::Int
function register_mcp_tools(tools::OrderedDict{String, agentTool}, mcpserver; eventSink=nothing)::Int
if mcpserver === nothing
return 0
end
@@ -148,13 +123,14 @@ function register_mcp_tools(mcpserver, store::toolStore; eventSink=nothing)::Int
new_count = 0
try
eventSink("register_mcp_tools 1")
response = mcpserver("tools/list")
eventSink("register_mcp_tools 2")
# Parse JSON-RPC 2.0 response envelope
if haskey(response, "error")
rpc_error = response["error"]
err_msg = get(rpc_error, "message", "Unknown MCP error")
println("[toolRegistry:$(store.name)] MCP tools/list failed: $err_msg")
println("[toolRegistry] MCP tools/list failed: $err_msg")
return 0
end
if haskey(response, "result")
@@ -166,12 +142,12 @@ function register_mcp_tools(mcpserver, store::toolStore; eventSink=nothing)::Int
for tool_def in tools_array
name = tool_def["name"]
if haskey(store.tools, name)
if haskey(tools, name)
continue
end
@show "tool_def $(typeof(tool_def))"
wrapped = _wrap_mcp_tool(mcpserver, tool_def, eventSink=eventSink)
store.tools[name] = wrapped
tools[name] = wrapped
new_count += 1
end
@@ -185,16 +161,16 @@ function register_mcp_tools(mcpserver, store::toolStore; eventSink=nothing)::Int
cursor = get(response, "nextCursor", nothing)
for tool_def in tools_array
name = tool_def["name"]
if haskey(store.tools, name)
if haskey(tools, name)
continue
end
wrapped = _wrap_mcp_tool(mcpserver, tool_def, eventSink=eventSink)
store.tools[name] = wrapped
tools[name] = wrapped
new_count += 1
end
end
println("[toolRegistry:$(store.name)] Discovered $new_count MCP tools. Total: $(length(store.tools))")
println("[toolRegistry] Discovered $new_count MCP tools. Total: $(length(tools))")
catch e
bt = catch_backtrace()
err_msg = sprint() do io
@@ -205,75 +181,60 @@ function register_mcp_tools(mcpserver, store::toolStore; eventSink=nothing)::Int
eventSink(err_msg)
errMsg = sprint(showerror, e)
println("[toolRegistry:$(store.name)] MCP tools/list failed: $errMsg")
println("[toolRegistry] MCP tools/list failed: $errMsg")
end
return new_count
end
"""
registerTool(store::toolStore, tool::agentTool) -> OrderedDict{String, agentTool}
registerTool(tools::OrderedDict{String, agentTool}, tool::agentTool) -> OrderedDict{String, agentTool}
Add `tool` to `store`, overwriting any existing tool with the same name.
Add `tool` to `tools`, overwriting any existing tool with the same name.
# Arguments
- `store`: Tool store to modify
- `tools`: Tool dict to modify
- `tool`: The `agentTool` to register
# Returns
- The same `store.tools` dict (modified in place)
- The same `tools` dict (modified in place)
"""
function registerTool(store::toolStore, tool::agentTool; eventSink=nothing
function registerTool(tools::OrderedDict{String, agentTool}, tool::agentTool; eventSink=nothing
)::OrderedDict{String, agentTool}
store.tools[tool.name] = tool
println("[$(store.name)] Registered tool: $(tool.name)")
return store.tools
tools[tool.name] = tool
println("[toolRegistry] Registered tool: $(tool.name)")
return tools
end
"""
Return the tools registered in `store`.
The returned dict is the **same object** stored inside `store`.
Remove all tools from the dict.
# Arguments
- `store`: Tool store to query
# Returns
- `OrderedDict{String, agentTool}`: Tools keyed by name, in registration order
"""
function getTools(store::toolStore)::OrderedDict{String, agentTool}
return store.tools
end
"""
Remove all tools from `store`.
# Arguments
- `store`: Tool store to clear
- `tools`: Tool dict to clear
# Returns
- `nothing`
"""
function clearTools(store::toolStore)::Nothing
empty!(store.tools)
println("[$(store.name)] Registry cleared")
function clearTools(tools::OrderedDict{String, agentTool})::Nothing
empty!(tools)
println("[toolRegistry] Registry cleared")
return nothing
end
# ── listTools tool (auto-discover new MCP tools at runtime) ─────────
"""
listTool(store::toolStore, mcpserver) -> agentTool
listTool(tools::OrderedDict{String, agentTool}, mcpserver) -> agentTool
MCP-aware listTools tool (JSON-RPC 2.0 protocol).
First call: queries the MCP server via `mcpserver("tools/list")`, registers
all discovered tools into the shared `store.tools` (in-place mutation, with
all discovered tools into `tools` (in-place mutation, with
pagination via nextCursor), then returns the full tool list.
Subsequent calls: returns the current list (tools remain registered).
"""
function listTool(store::toolStore, mcpserver)::agentTool
function listTool(tools::OrderedDict{String, agentTool}, mcpserver; eventSink=nothing)::agentTool
return agentTool(
name="listTools",
label="List Tools",
@@ -285,16 +246,17 @@ function listTool(store::toolStore, mcpserver)::agentTool
),
execute=(toolCallId::String, args::AbstractDict{String, Any},
signal::Union{Nothing,abortSignal},
eventSink, llmCall=nothing) -> begin
eventSink) -> begin
# Discover and register MCP tools (idempotent — skips already registered)
new_count = register_mcp_tools(mcpserver, store)
new_count = register_mcp_tools(tools, mcpserver; eventSink=eventSink)
eventSink("tools dump: " * sprint(show, tools))
# Always include listTools itself in the count
total = length(store.tools)
total = length(tools)
lines = String[
"- $(t.name): $(t.label) — $(t.description)"
for (k, t) in store.tools
for (k, t) in tools
]
result_text = "Available tools ($total):\n" * join(lines, "\n")
@@ -313,23 +275,19 @@ end
# ── High-level API ──────────────────────────────────────────────────
"""
registerAllTools(store::toolStore, mcpserver) -> toolStore
registerAllTools(tools::OrderedDict{String, agentTool}, mcpserver)
Register all tools for an agent:
1. Auto-discover existing tools from the MCP server
2. Register the `listTools` tool so the agent can discover new tools at runtime
# Arguments
- `store`: The tool store to populate
- `tools`: The tool dict to populate
- `mcpserver`: A callable struct that communicates with the MCP server
# Returns
- The populated `toolStore`
"""
function registerAllTools(store::toolStore, mcpserver=nothing; eventSink=nothing)::toolStore
register_mcp_tools(mcpserver, store; eventSink=eventSink)
registerTool(store, listTool(store, mcpserver); eventSink=eventSink)
return store
function registerAllTools(tools::OrderedDict{String, agentTool}, mcpserver=nothing; eventSink=nothing)
list_t = listTool(tools, mcpserver; eventSink=eventSink)
registerTool(tools, list_t; eventSink=eventSink)
end
end # module