feat(mcp): register built-in tool groups
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
20caaebc21
commit
bfb5bf84e2
8 changed files with 114 additions and 27 deletions
|
|
@ -256,6 +256,13 @@ func (s *Service) registerTools(server *mcp.Server) {
|
|||
Name: "lang_list",
|
||||
Description: "Get list of supported programming languages",
|
||||
}, s.getSupportedLanguages)
|
||||
|
||||
// Additional built-in tool groups.
|
||||
s.registerMetricsTools(server)
|
||||
s.registerRAGTools(server)
|
||||
s.registerProcessTools(server)
|
||||
s.registerWebviewTools(server)
|
||||
s.registerWSTools(server)
|
||||
}
|
||||
|
||||
// Tool input/output types for MCP file operations.
|
||||
|
|
|
|||
|
|
@ -55,6 +55,46 @@ func TestNew_Good_NoRestriction(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNew_Good_RegistersBuiltInTools(t *testing.T) {
|
||||
s, err := New(Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create service: %v", err)
|
||||
}
|
||||
|
||||
tools := map[string]bool{}
|
||||
for _, rec := range s.Tools() {
|
||||
tools[rec.Name] = true
|
||||
}
|
||||
|
||||
for _, name := range []string{
|
||||
"metrics_record",
|
||||
"metrics_query",
|
||||
"rag_query",
|
||||
"rag_ingest",
|
||||
"rag_collections",
|
||||
"webview_connect",
|
||||
"webview_disconnect",
|
||||
"webview_navigate",
|
||||
"webview_click",
|
||||
"webview_type",
|
||||
"webview_query",
|
||||
"webview_console",
|
||||
"webview_eval",
|
||||
"webview_screenshot",
|
||||
"webview_wait",
|
||||
} {
|
||||
if !tools[name] {
|
||||
t.Fatalf("expected tool %q to be registered", name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range []string{"process_start", "ws_start"} {
|
||||
if tools[name] {
|
||||
t.Fatalf("did not expect tool %q to be registered without dependencies", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMedium_Good_ReadWrite(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
s, err := New(Options{WorkspaceRoot: tmpDir})
|
||||
|
|
|
|||
|
|
@ -68,8 +68,12 @@ func TestToolRegistry_Good_ToolCount(t *testing.T) {
|
|||
|
||||
tools := svc.Tools()
|
||||
// Built-in tools: file_read, file_write, file_delete, file_rename,
|
||||
// file_exists, file_edit, dir_list, dir_create, lang_detect, lang_list
|
||||
const expectedCount = 10
|
||||
// file_exists, file_edit, dir_list, dir_create, lang_detect, lang_list,
|
||||
// metrics_record, metrics_query, rag_query, rag_ingest, rag_collections,
|
||||
// webview_connect, webview_disconnect, webview_navigate, webview_click,
|
||||
// webview_type, webview_query, webview_console, webview_eval,
|
||||
// webview_screenshot, webview_wait
|
||||
const expectedCount = 25
|
||||
if len(tools) != expectedCount {
|
||||
t.Errorf("expected %d tools, got %d", expectedCount, len(tools))
|
||||
for _, tr := range tools {
|
||||
|
|
@ -86,6 +90,9 @@ func TestToolRegistry_Good_GroupAssignment(t *testing.T) {
|
|||
|
||||
fileTools := []string{"file_read", "file_write", "file_delete", "file_rename", "file_exists", "file_edit", "dir_list", "dir_create"}
|
||||
langTools := []string{"lang_detect", "lang_list"}
|
||||
metricsTools := []string{"metrics_record", "metrics_query"}
|
||||
ragTools := []string{"rag_query", "rag_ingest", "rag_collections"}
|
||||
webviewTools := []string{"webview_connect", "webview_disconnect", "webview_navigate", "webview_click", "webview_type", "webview_query", "webview_console", "webview_eval", "webview_screenshot", "webview_wait"}
|
||||
|
||||
byName := make(map[string]ToolRecord)
|
||||
for _, tr := range svc.Tools() {
|
||||
|
|
@ -113,6 +120,39 @@ func TestToolRegistry_Good_GroupAssignment(t *testing.T) {
|
|||
t.Errorf("tool %s: expected group 'language', got %q", name, tr.Group)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range metricsTools {
|
||||
tr, ok := byName[name]
|
||||
if !ok {
|
||||
t.Errorf("tool %s not found in registry", name)
|
||||
continue
|
||||
}
|
||||
if tr.Group != "metrics" {
|
||||
t.Errorf("tool %s: expected group 'metrics', got %q", name, tr.Group)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range ragTools {
|
||||
tr, ok := byName[name]
|
||||
if !ok {
|
||||
t.Errorf("tool %s not found in registry", name)
|
||||
continue
|
||||
}
|
||||
if tr.Group != "rag" {
|
||||
t.Errorf("tool %s: expected group 'rag', got %q", name, tr.Group)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range webviewTools {
|
||||
tr, ok := byName[name]
|
||||
if !ok {
|
||||
t.Errorf("tool %s not found in registry", name)
|
||||
continue
|
||||
}
|
||||
if tr.Group != "webview" {
|
||||
t.Errorf("tool %s: expected group 'webview', got %q", name, tr.Group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToolRegistry_Good_ToolRecordFields(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"forge.lthn.ai/core/go-ai/ai"
|
||||
core "dappco.re/go/core"
|
||||
"forge.lthn.ai/core/go-ai/ai"
|
||||
"forge.lthn.ai/core/go-log"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
|
@ -71,19 +71,19 @@ type MetricCount struct {
|
|||
// // ev.Type == "dispatch.complete", ev.AgentID == "cladius", ev.Repo == "core-php"
|
||||
type MetricEventBrief struct {
|
||||
Type string `json:"type"` // e.g. "dispatch.complete"
|
||||
Timestamp time.Time `json:"timestamp"` // when the event occurred
|
||||
Timestamp time.Time `json:"timestamp"` // when the event occurred
|
||||
AgentID string `json:"agent_id,omitempty"` // e.g. "cladius"
|
||||
Repo string `json:"repo,omitempty"` // e.g. "core-php"
|
||||
}
|
||||
|
||||
// registerMetricsTools adds metrics tools to the MCP server.
|
||||
func (s *Service) registerMetricsTools(server *mcp.Server) {
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "metrics", &mcp.Tool{
|
||||
Name: "metrics_record",
|
||||
Description: "Record a metrics event for AI/security tracking. Events are stored in daily JSONL files.",
|
||||
}, s.metricsRecord)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "metrics", &mcp.Tool{
|
||||
Name: "metrics_query",
|
||||
Description: "Query metrics events and get aggregated statistics by type, repo, and agent.",
|
||||
}, s.metricsQuery)
|
||||
|
|
|
|||
|
|
@ -139,32 +139,32 @@ func (s *Service) registerProcessTools(server *mcp.Server) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "process", &mcp.Tool{
|
||||
Name: "process_start",
|
||||
Description: "Start a new external process. Returns process ID for tracking.",
|
||||
}, s.processStart)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "process", &mcp.Tool{
|
||||
Name: "process_stop",
|
||||
Description: "Gracefully stop a running process by ID.",
|
||||
}, s.processStop)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "process", &mcp.Tool{
|
||||
Name: "process_kill",
|
||||
Description: "Force kill a process by ID. Use when process_stop doesn't work.",
|
||||
}, s.processKill)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "process", &mcp.Tool{
|
||||
Name: "process_list",
|
||||
Description: "List all managed processes. Use running_only=true for only active processes.",
|
||||
}, s.processList)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "process", &mcp.Tool{
|
||||
Name: "process_output",
|
||||
Description: "Get the captured output of a process by ID.",
|
||||
}, s.processOutput)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "process", &mcp.Tool{
|
||||
Name: "process_input",
|
||||
Description: "Send input to a running process stdin.",
|
||||
}, s.processInput)
|
||||
|
|
|
|||
|
|
@ -99,17 +99,17 @@ type RAGCollectionsOutput struct {
|
|||
|
||||
// registerRAGTools adds RAG tools to the MCP server.
|
||||
func (s *Service) registerRAGTools(server *mcp.Server) {
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "rag", &mcp.Tool{
|
||||
Name: "rag_query",
|
||||
Description: "Query the RAG vector database for relevant documentation. Returns semantically similar content based on the query.",
|
||||
}, s.ragQuery)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "rag", &mcp.Tool{
|
||||
Name: "rag_ingest",
|
||||
Description: "Ingest documents into the RAG vector database. Supports both single files and directories.",
|
||||
}, s.ragIngest)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "rag", &mcp.Tool{
|
||||
Name: "rag_collections",
|
||||
Description: "List all available collections in the RAG vector database.",
|
||||
}, s.ragCollections)
|
||||
|
|
|
|||
|
|
@ -201,52 +201,52 @@ type WebviewDisconnectOutput struct {
|
|||
|
||||
// registerWebviewTools adds webview tools to the MCP server.
|
||||
func (s *Service) registerWebviewTools(server *mcp.Server) {
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_connect",
|
||||
Description: "Connect to Chrome DevTools Protocol. Start Chrome with --remote-debugging-port=9222 first.",
|
||||
}, s.webviewConnect)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_disconnect",
|
||||
Description: "Disconnect from Chrome DevTools.",
|
||||
}, s.webviewDisconnect)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_navigate",
|
||||
Description: "Navigate the browser to a URL.",
|
||||
}, s.webviewNavigate)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_click",
|
||||
Description: "Click on an element by CSS selector.",
|
||||
}, s.webviewClick)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_type",
|
||||
Description: "Type text into an element by CSS selector.",
|
||||
}, s.webviewType)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_query",
|
||||
Description: "Query DOM elements by CSS selector.",
|
||||
}, s.webviewQuery)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_console",
|
||||
Description: "Get browser console output.",
|
||||
}, s.webviewConsole)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_eval",
|
||||
Description: "Evaluate JavaScript in the browser context.",
|
||||
}, s.webviewEval)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_screenshot",
|
||||
Description: "Capture a screenshot of the browser window.",
|
||||
}, s.webviewScreenshot)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "webview", &mcp.Tool{
|
||||
Name: "webview_wait",
|
||||
Description: "Wait for an element to appear by CSS selector.",
|
||||
}, s.webviewWait)
|
||||
|
|
|
|||
|
|
@ -47,12 +47,12 @@ func (s *Service) registerWSTools(server *mcp.Server) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "ws", &mcp.Tool{
|
||||
Name: "ws_start",
|
||||
Description: "Start the WebSocket server for real-time process output streaming.",
|
||||
}, s.wsStart)
|
||||
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
addToolRecorded(s, server, "ws", &mcp.Tool{
|
||||
Name: "ws_info",
|
||||
Description: "Get WebSocket hub statistics (connected clients and active channels).",
|
||||
}, s.wsInfo)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue