Skip to content

Commit 72cf172

Browse files
authored
refactor(tool): migrate McpAuth/MCPLanguageServer/ListMcpResources/ReadMcpResource/NilAway/Revive/AgenticFetch to typed schemas (#329)
1 parent d242f05 commit 72cf172

6 files changed

Lines changed: 272 additions & 142 deletions

File tree

‎internal/tool/agentic_fetch.go‎

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,36 @@ func (AgenticFetchTool) Description() string {
3939
return "Fetch and intelligently summarize web content using a sub-agent. Better than raw WebFetch for research — the sub-agent extracts only the relevant information."
4040
}
4141

42-
func (AgenticFetchTool) Parameters() map[string]interface{} {
43-
return map[string]interface{}{
44-
"type": "object",
45-
"properties": map[string]interface{}{
46-
"url": map[string]interface{}{"type": "string", "description": "URL to fetch and analyze. Provide this OR urls (not both)."},
47-
"urls": map[string]interface{}{
48-
"type": "array",
49-
"items": map[string]interface{}{"type": "string"},
50-
"description": "Multiple URLs to fetch and summarize concurrently against the same query, in a single call.",
51-
},
52-
"query": map[string]interface{}{"type": "string", "description": "What to look for or extract from the page(s)"},
42+
// Schema returns the typed input schema. Parameters() delegates to it so the
43+
// two cannot diverge.
44+
func (AgenticFetchTool) Schema() ToolSchema {
45+
return ToolSchema{
46+
Type: "object",
47+
Properties: map[string]SchemaProperty{
48+
"url": {Type: "string", Description: "URL to fetch and analyze. Provide this OR urls (not both)."},
49+
"urls": {Type: "array", Items: &SchemaProperty{Type: "string"}, Description: "Multiple URLs to fetch and summarize concurrently against the same query, in a single call."},
50+
"query": {Type: "string", Description: "What to look for or extract from the page(s)"},
5351
},
5452
}
5553
}
5654

55+
func (AgenticFetchTool) Parameters() map[string]interface{} {
56+
return agenticFetchSchema.ToJSONSchema()
57+
}
58+
59+
// agenticFetchSchema is the single source of truth for AgenticFetch's input schema.
60+
var agenticFetchSchema = AgenticFetchTool{}.Schema()
61+
62+
// AgenticFetchInput is the typed input for AgenticFetchTool.
63+
type AgenticFetchInput struct {
64+
URL string `json:"url"`
65+
URLs []string `json:"urls"`
66+
Query string `json:"query"`
67+
}
68+
5769
func (t AgenticFetchTool) Execute(ctx context.Context, input json.RawMessage) (string, error) {
58-
var p struct {
59-
URL string `json:"url"`
60-
URLs []string `json:"urls"`
61-
Query string `json:"query"`
62-
}
63-
if err := json.Unmarshal(input, &p); err != nil {
70+
p, err := DecodeInput[AgenticFetchInput]("AgenticFetch", input)
71+
if err != nil {
6472
return "", err
6573
}
6674

‎internal/tool/mcp_auth.go‎

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -53,42 +53,44 @@ const mcpAuthCallbackTimeout = 5 * time.Minute
5353
// same server_name later to check progress.
5454
type McpAuthTool struct{}
5555

56+
// McpAuthInput is the typed input for McpAuthTool.
57+
type McpAuthInput struct {
58+
ServerName string `json:"server_name"`
59+
ServerURL string `json:"server_url"`
60+
ClientID string `json:"client_id"`
61+
}
62+
5663
func (McpAuthTool) Name() string { return "McpAuth" }
5764
func (McpAuthTool) Aliases() []string { return []string{"mcp_auth"} }
5865
func (McpAuthTool) Description() string {
5966
return "Start OAuth authentication for an MCP server that requires authorization. " +
6067
"Call again with the same server_name to check progress once the user has visited the URL."
6168
}
6269

63-
func (McpAuthTool) Parameters() map[string]interface{} {
64-
return map[string]interface{}{
65-
"type": "object",
66-
"properties": map[string]interface{}{
67-
"server_name": map[string]interface{}{
68-
"type": "string",
69-
"description": "Name of the MCP server to authenticate",
70-
},
71-
"server_url": map[string]interface{}{
72-
"type": "string",
73-
"description": "URL of the MCP server",
74-
},
75-
"client_id": map[string]interface{}{
76-
"type": "string",
77-
"description": "Optional pre-registered OAuth client_id. If omitted, rho attempts " +
78-
"dynamic client registration (RFC 7591) against the server's advertised registration endpoint.",
79-
},
70+
// Schema returns the typed input schema. Parameters() delegates to it so the
71+
// two cannot diverge.
72+
func (McpAuthTool) Schema() ToolSchema {
73+
return ToolSchema{
74+
Type: "object",
75+
Properties: map[string]SchemaProperty{
76+
"server_name": {Type: "string", Description: "Name of the MCP server to authenticate"},
77+
"server_url": {Type: "string", Description: "URL of the MCP server"},
78+
"client_id": {Type: "string", Description: "Optional pre-registered OAuth client_id. If omitted, rho attempts dynamic client registration (RFC 7591) against the server's advertised registration endpoint."},
8079
},
81-
"required": []string{"server_name", "server_url"},
80+
Required: []string{"server_name", "server_url"},
8281
}
8382
}
8483

84+
func (McpAuthTool) Parameters() map[string]interface{} {
85+
return mcpAuthSchema.ToJSONSchema()
86+
}
87+
88+
// mcpAuthSchema is the single source of truth for McpAuth's input schema.
89+
var mcpAuthSchema = McpAuthTool{}.Schema()
90+
8591
func (McpAuthTool) Execute(ctx context.Context, input json.RawMessage) (string, error) {
86-
var p struct {
87-
ServerName string `json:"server_name"`
88-
ServerURL string `json:"server_url"`
89-
ClientID string `json:"client_id"`
90-
}
91-
if err := json.Unmarshal(input, &p); err != nil {
92+
p, err := DecodeInput[McpAuthInput]("McpAuth", input)
93+
if err != nil {
9294
return "", err
9395
}
9496
if p.ServerName == "" {

‎internal/tool/mcp_lsp.go‎

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,61 +13,51 @@ import (
1313
// across multiple languages without running a language server directly.
1414
type MCPLanguageServerTool struct{}
1515

16+
// MCPLanguageServerInput is the typed input for MCPLanguageServerTool.
17+
type MCPLanguageServerInput struct {
18+
Action string `json:"action"`
19+
File string `json:"file"`
20+
Line int `json:"line"`
21+
Column int `json:"column"`
22+
Symbol string `json:"symbol"`
23+
NewName string `json:"newName"`
24+
Language string `json:"language"`
25+
}
26+
1627
func (MCPLanguageServerTool) Name() string { return "MCPLSP" }
1728
func (MCPLanguageServerTool) Aliases() []string { return []string{"mcplsp", "lsp-mcp"} }
1829
func (MCPLanguageServerTool) Description() string {
1930
return "Deep code understanding via MCP language server. Provides go-to-definition, find-references, rename, and diagnostics across multiple languages."
2031
}
2132

22-
func (MCPLanguageServerTool) Parameters() map[string]interface{} {
23-
return map[string]interface{}{
24-
"type": "object",
25-
"properties": map[string]interface{}{
26-
"action": map[string]interface{}{
27-
"type": "string",
28-
"enum": []string{"definition", "references", "rename", "diagnostics", "hover", "symbols"},
29-
"description": "LSP action to perform",
30-
},
31-
"file": map[string]interface{}{
32-
"type": "string",
33-
"description": "File path",
34-
},
35-
"line": map[string]interface{}{
36-
"type": "integer",
37-
"description": "Line number (1-based)",
38-
},
39-
"column": map[string]interface{}{
40-
"type": "integer",
41-
"description": "Column number (1-based)",
42-
},
43-
"symbol": map[string]interface{}{
44-
"type": "string",
45-
"description": "Symbol name (for rename: old name)",
46-
},
47-
"newName": map[string]interface{}{
48-
"type": "string",
49-
"description": "New name for rename action",
50-
},
51-
"language": map[string]interface{}{
52-
"type": "string",
53-
"description": "Language server to use (go, python, typescript, rust)",
54-
},
33+
// Schema returns the typed input schema. Parameters() delegates to it so the
34+
// two cannot diverge.
35+
func (MCPLanguageServerTool) Schema() ToolSchema {
36+
return ToolSchema{
37+
Type: "object",
38+
Properties: map[string]SchemaProperty{
39+
"action": {Type: "string", Enum: []interface{}{"definition", "references", "rename", "diagnostics", "hover", "symbols"}, Description: "LSP action to perform"},
40+
"file": {Type: "string", Description: "File path"},
41+
"line": {Type: "integer", Description: "Line number (1-based)"},
42+
"column": {Type: "integer", Description: "Column number (1-based)"},
43+
"symbol": {Type: "string", Description: "Symbol name (for rename: old name)"},
44+
"newName": {Type: "string", Description: "New name for rename action"},
45+
"language": {Type: "string", Description: "Language server to use (go, python, typescript, rust)"},
5546
},
56-
"required": []string{"action"},
47+
Required: []string{"action"},
5748
}
5849
}
5950

51+
func (MCPLanguageServerTool) Parameters() map[string]interface{} {
52+
return mcpLSPLanguageSchema.ToJSONSchema()
53+
}
54+
55+
// mcpLSPLanguageSchema is the single source of truth for MCPLanguageServer's input schema.
56+
var mcpLSPLanguageSchema = MCPLanguageServerTool{}.Schema()
57+
6058
func (MCPLanguageServerTool) Execute(ctx context.Context, input json.RawMessage) (string, error) {
61-
var p struct {
62-
Action string `json:"action"`
63-
File string `json:"file"`
64-
Line int `json:"line"`
65-
Column int `json:"column"`
66-
Symbol string `json:"symbol"`
67-
NewName string `json:"newName"`
68-
Language string `json:"language"`
69-
}
70-
if err := json.Unmarshal(input, &p); err != nil {
59+
p, err := DecodeInput[MCPLanguageServerInput]("MCPLanguageServer", input)
60+
if err != nil {
7161
return "", err
7262
}
7363

‎internal/tool/mcp_resources.go‎

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import (
1010

1111
type ListMcpResourcesTool struct{}
1212

13+
// ListMcpResourcesInput is the typed input for ListMcpResourcesTool.
14+
type ListMcpResourcesInput struct {
15+
Server string `json:"server"`
16+
}
17+
1318
func (ListMcpResourcesTool) Name() string { return "ListMcpResourcesTool" }
1419
func (ListMcpResourcesTool) Aliases() []string {
1520
return []string{"list_mcp_resources", "listMcpResources"}
@@ -19,23 +24,32 @@ func (ListMcpResourcesTool) Description() string {
1924
return "List resources exposed by connected MCP servers. Optionally filter by server name."
2025
}
2126

22-
func (ListMcpResourcesTool) Parameters() map[string]interface{} {
23-
return map[string]interface{}{
24-
"type": "object",
25-
"properties": map[string]interface{}{
26-
"server": map[string]interface{}{"type": "string", "description": "Optional MCP server name to filter resources by"},
27+
// Schema returns the typed input schema. Parameters() delegates to it so the
28+
// two cannot diverge.
29+
func (ListMcpResourcesTool) Schema() ToolSchema {
30+
return ToolSchema{
31+
Type: "object",
32+
Properties: map[string]SchemaProperty{
33+
"server": {Type: "string", Description: "Optional MCP server name to filter resources by"},
2734
},
2835
}
2936
}
3037

38+
func (ListMcpResourcesTool) Parameters() map[string]interface{} {
39+
return listMcpResourcesSchema.ToJSONSchema()
40+
}
41+
42+
// listMcpResourcesSchema is the single source of truth for ListMcpResources's input schema.
43+
var listMcpResourcesSchema = ListMcpResourcesTool{}.Schema()
44+
3145
func (ListMcpResourcesTool) Execute(_ context.Context, input json.RawMessage) (string, error) {
32-
var p struct {
33-
Server string `json:"server"`
34-
}
35-
if len(input) > 0 {
36-
if err := json.Unmarshal(input, &p); err != nil {
46+
var p ListMcpResourcesInput
47+
if len(input) > 0 && string(input) != "null" {
48+
decoded, err := DecodeInput[ListMcpResourcesInput]("ListMcpResources", input)
49+
if err != nil {
3750
return "", err
3851
}
52+
p = decoded
3953
}
4054

4155
type resourceOut struct {
@@ -79,6 +93,12 @@ func (ListMcpResourcesTool) Execute(_ context.Context, input json.RawMessage) (s
7993

8094
type ReadMcpResourceTool struct{}
8195

96+
// ReadMcpResourceInput is the typed input for ReadMcpResourceTool.
97+
type ReadMcpResourceInput struct {
98+
Server string `json:"server"`
99+
URI string `json:"uri"`
100+
}
101+
82102
func (ReadMcpResourceTool) Name() string { return "ReadMcpResourceTool" }
83103
func (ReadMcpResourceTool) Aliases() []string {
84104
return []string{"read_mcp_resource", "readMcpResource"}
@@ -88,23 +108,29 @@ func (ReadMcpResourceTool) Description() string {
88108
return "Read a resource exposed by a connected MCP server."
89109
}
90110

91-
func (ReadMcpResourceTool) Parameters() map[string]interface{} {
92-
return map[string]interface{}{
93-
"type": "object",
94-
"properties": map[string]interface{}{
95-
"server": map[string]interface{}{"type": "string", "description": "MCP server name"},
96-
"uri": map[string]interface{}{"type": "string", "description": "Resource URI"},
111+
// Schema returns the typed input schema. Parameters() delegates to it so the
112+
// two cannot diverge.
113+
func (ReadMcpResourceTool) Schema() ToolSchema {
114+
return ToolSchema{
115+
Type: "object",
116+
Properties: map[string]SchemaProperty{
117+
"server": {Type: "string", Description: "MCP server name"},
118+
"uri": {Type: "string", Description: "Resource URI"},
97119
},
98-
"required": []string{"server", "uri"},
120+
Required: []string{"server", "uri"},
99121
}
100122
}
101123

124+
func (ReadMcpResourceTool) Parameters() map[string]interface{} {
125+
return readMcpResourceSchema.ToJSONSchema()
126+
}
127+
128+
// readMcpResourceSchema is the single source of truth for ReadMcpResource's input schema.
129+
var readMcpResourceSchema = ReadMcpResourceTool{}.Schema()
130+
102131
func (ReadMcpResourceTool) Execute(_ context.Context, input json.RawMessage) (string, error) {
103-
var p struct {
104-
Server string `json:"server"`
105-
URI string `json:"uri"`
106-
}
107-
if err := json.Unmarshal(input, &p); err != nil {
132+
p, err := DecodeInput[ReadMcpResourceInput]("ReadMcpResource", input)
133+
if err != nil {
108134
return "", err
109135
}
110136
if p.Server == "" || p.URI == "" {

0 commit comments

Comments
 (0)