Skip to content

Commit 2be83a0

Browse files
committed
get it working
1 parent b1ab893 commit 2be83a0

File tree

4 files changed

+99
-1
lines changed

4 files changed

+99
-1
lines changed

internal/ghmcp/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
235235
fmt.Fprintf(os.Stderr, "Warning: unrecognized toolsets ignored: %s\n", strings.Join(unrecognized, ", "))
236236
}
237237

238+
// Check for unrecognized tools and error out (unlike toolsets which just warn)
239+
if unrecognized := inventory.UnrecognizedTools(); len(unrecognized) > 0 {
240+
return nil, fmt.Errorf("unrecognized tools: %s", strings.Join(unrecognized, ", "))
241+
}
242+
238243
// Register GitHub tools/resources/prompts from the inventory.
239244
// In dynamic mode with no explicit toolsets, this is a no-op since enabledToolsets
240245
// is empty - users enable toolsets at runtime via the dynamic tools below (but can

pkg/inventory/builder.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,17 @@ func (b *Builder) Build() *Inventory {
145145
// Process toolsets and pre-compute metadata in a single pass
146146
r.enabledToolsets, r.unrecognizedToolsets, r.toolsetIDs, r.toolsetIDSet, r.defaultToolsetIDs, r.toolsetDescriptions = b.processToolsets()
147147

148-
// Process additional tools (resolve aliases)
148+
// Build set of valid tool names for validation
149+
validToolNames := make(map[string]bool, len(b.tools))
150+
for i := range b.tools {
151+
validToolNames[b.tools[i].Tool.Name] = true
152+
}
153+
154+
// Process additional tools (resolve aliases and track unrecognized)
155+
// Note: input is expected to be pre-cleaned (trimmed, deduped) via CleanTools
149156
if len(b.additionalTools) > 0 {
150157
r.additionalTools = make(map[string]bool, len(b.additionalTools))
158+
var unrecognizedTools []string
151159
for _, name := range b.additionalTools {
152160
// Always include the original name - this handles the case where
153161
// the tool exists but is controlled by a feature flag that's OFF.
@@ -157,8 +165,12 @@ func (b *Builder) Build() *Inventory {
157165
// the new consolidated tool is available.
158166
if canonical, isAlias := b.deprecatedAliases[name]; isAlias {
159167
r.additionalTools[canonical] = true
168+
} else if !validToolNames[name] {
169+
// Not a valid tool and not a deprecated alias - track as unrecognized
170+
unrecognizedTools = append(unrecognizedTools, name)
160171
}
161172
}
173+
r.unrecognizedTools = unrecognizedTools
162174
}
163175

164176
return r

pkg/inventory/registry.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ type Inventory struct {
5858
filters []ToolFilter
5959
// unrecognizedToolsets holds toolset IDs that were requested but don't match any registered toolsets
6060
unrecognizedToolsets []string
61+
// unrecognizedTools holds tool names that were requested via WithTools but don't exist
62+
unrecognizedTools []string
6163
}
6264

6365
// UnrecognizedToolsets returns toolset IDs that were passed to WithToolsets but don't
@@ -66,6 +68,13 @@ func (r *Inventory) UnrecognizedToolsets() []string {
6668
return r.unrecognizedToolsets
6769
}
6870

71+
// UnrecognizedTools returns tool names that were passed to WithTools but don't
72+
// match any registered tools (and are not deprecated aliases). This is used to
73+
// error out when invalid tool names are specified.
74+
func (r *Inventory) UnrecognizedTools() []string {
75+
return r.unrecognizedTools
76+
}
77+
6978
// MCP method constants for use with ForMCPRequest.
7079
const (
7180
MCPMethodInitialize = "initialize"
@@ -112,6 +121,7 @@ func (r *Inventory) ForMCPRequest(method string, itemName string) *Inventory {
112121
featureChecker: r.featureChecker,
113122
filters: r.filters, // shared, not modified
114123
unrecognizedToolsets: r.unrecognizedToolsets,
124+
unrecognizedTools: r.unrecognizedTools,
115125
}
116126

117127
// Helper to clear all item types

pkg/inventory/registry_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,77 @@ func TestUnrecognizedToolsets(t *testing.T) {
270270
}
271271
}
272272

273+
func TestUnrecognizedTools(t *testing.T) {
274+
tools := []ServerTool{
275+
mockTool("tool1", "toolset1", true),
276+
mockTool("tool2", "toolset2", true),
277+
}
278+
279+
deprecatedAliases := map[string]string{
280+
"old_tool": "tool1",
281+
}
282+
283+
tests := []struct {
284+
name string
285+
withTools []string
286+
expectedUnrecognized []string
287+
}{
288+
{
289+
name: "all valid",
290+
withTools: []string{"tool1", "tool2"},
291+
expectedUnrecognized: nil,
292+
},
293+
{
294+
name: "one invalid",
295+
withTools: []string{"tool1", "blabla"},
296+
expectedUnrecognized: []string{"blabla"},
297+
},
298+
{
299+
name: "multiple invalid",
300+
withTools: []string{"invalid1", "tool1", "invalid2"},
301+
expectedUnrecognized: []string{"invalid1", "invalid2"},
302+
},
303+
{
304+
name: "deprecated alias is valid",
305+
withTools: []string{"old_tool"},
306+
expectedUnrecognized: nil,
307+
},
308+
{
309+
name: "mixed valid and deprecated alias",
310+
withTools: []string{"old_tool", "tool2"},
311+
expectedUnrecognized: nil,
312+
},
313+
{
314+
name: "empty input",
315+
withTools: []string{},
316+
expectedUnrecognized: nil,
317+
},
318+
}
319+
320+
for _, tt := range tests {
321+
t.Run(tt.name, func(t *testing.T) {
322+
inv := NewBuilder().
323+
SetTools(tools).
324+
WithDeprecatedAliases(deprecatedAliases).
325+
WithToolsets([]string{"all"}).
326+
WithTools(tt.withTools).
327+
Build()
328+
unrecognized := inv.UnrecognizedTools()
329+
330+
if len(unrecognized) != len(tt.expectedUnrecognized) {
331+
t.Fatalf("Expected %d unrecognized, got %d: %v",
332+
len(tt.expectedUnrecognized), len(unrecognized), unrecognized)
333+
}
334+
335+
for i, expected := range tt.expectedUnrecognized {
336+
if unrecognized[i] != expected {
337+
t.Errorf("Expected unrecognized[%d] = %q, got %q", i, expected, unrecognized[i])
338+
}
339+
}
340+
})
341+
}
342+
}
343+
273344
func TestWithTools(t *testing.T) {
274345
tools := []ServerTool{
275346
mockTool("tool1", "toolset1", true),

0 commit comments

Comments
 (0)