Skip to content

Commit c34055e

Browse files
refactor(inventory): generalize tool availability guards
Gate protocol-restricted tools on required elicitation capabilities and enforce direct calls inside the registered handler so SDK result finalization remains intact. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8
1 parent 98aef1f commit c34055e

9 files changed

Lines changed: 359 additions & 211 deletions

pkg/github/repositories.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,7 @@ func DeleteRepository(t translations.TranslationHelperFunc) inventory.ServerTool
814814
},
815815
)
816816
tool.MinimumProtocolVersion = inventory.ProtocolVersionMultiRoundTrip
817+
tool.RequiredElicitationMode = inventory.ElicitationModeForm
817818
return tool
818819
}
819820

pkg/github/repositories_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3000,6 +3000,7 @@ func Test_DeleteRepository(t *testing.T) {
30003000
require.NotNil(t, tool.Annotations.DestructiveHint)
30013001
assert.True(t, *tool.Annotations.DestructiveHint)
30023002
assert.Equal(t, inventory.ProtocolVersionMultiRoundTrip, serverTool.MinimumProtocolVersion)
3003+
assert.Equal(t, inventory.ElicitationModeForm, serverTool.RequiredElicitationMode)
30033004
assert.Equal(t, []string{string(scopes.DeleteRepo)}, serverTool.RequiredScopes)
30043005

30053006
t.Run("requests exact repository name through elicitation", func(t *testing.T) {

pkg/http/handler_test.go

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -934,31 +934,53 @@ func TestHTTPToolMinimumProtocolVersion(t *testing.T) {
934934
handler.RegisterRoutes(router)
935935

936936
for _, tt := range []struct {
937-
name string
938-
protocolVersion string
939-
wantDeleteRepoTool bool
937+
name string
938+
protocolVersion string
939+
elicitationCapabilities map[string]any
940+
wantDeleteRepoTool bool
940941
}{
941942
{
942-
name: "current protocol includes delete repository",
943-
protocolVersion: inventory.ProtocolVersionMultiRoundTrip,
944-
wantDeleteRepoTool: true,
943+
name: "current protocol with form elicitation includes delete repository",
944+
protocolVersion: inventory.ProtocolVersionMultiRoundTrip,
945+
elicitationCapabilities: map[string]any{"form": map[string]any{}},
946+
wantDeleteRepoTool: true,
945947
},
946948
{
947-
name: "legacy protocol hides delete repository",
948-
protocolVersion: "2025-11-25",
949-
wantDeleteRepoTool: false,
949+
name: "current protocol with URL-only elicitation hides delete repository",
950+
protocolVersion: inventory.ProtocolVersionMultiRoundTrip,
951+
elicitationCapabilities: map[string]any{"url": map[string]any{}},
952+
},
953+
{
954+
name: "current protocol without elicitation hides delete repository",
955+
protocolVersion: inventory.ProtocolVersionMultiRoundTrip,
956+
},
957+
{
958+
name: "legacy protocol hides delete repository",
959+
protocolVersion: "2025-11-25",
960+
elicitationCapabilities: map[string]any{"form": map[string]any{}},
950961
},
951962
} {
952963
t.Run(tt.name, func(t *testing.T) {
953-
body := strings.Replace(
954-
`{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"PROTOCOL_VERSION","io.modelcontextprotocol/clientCapabilities":{"elicitation":{"form":{}}},"io.modelcontextprotocol/clientInfo":{"name":"test","version":"v0.0.1"}}}}`,
955-
"PROTOCOL_VERSION",
956-
tt.protocolVersion,
957-
1,
958-
)
959-
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
964+
clientCapabilities := map[string]any{}
965+
if tt.elicitationCapabilities != nil {
966+
clientCapabilities["elicitation"] = tt.elicitationCapabilities
967+
}
968+
body, err := json.Marshal(map[string]any{
969+
"jsonrpc": "2.0",
970+
"id": 1,
971+
"method": "tools/list",
972+
"params": map[string]any{
973+
"_meta": map[string]any{
974+
mcp.MetaKeyProtocolVersion: tt.protocolVersion,
975+
mcp.MetaKeyClientCapabilities: clientCapabilities,
976+
mcp.MetaKeyClientInfo: map[string]any{"name": "test", "version": "v0.0.1"},
977+
},
978+
},
979+
})
980+
require.NoError(t, err)
981+
982+
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(body)))
960983
req.Header.Set(headers.ContentTypeHeader, headers.ContentTypeJSON)
961-
req.Header.Set(headers.AuthorizationHeader, "Bearer test-token")
962984
req.Header.Set(headers.AcceptHeader, strings.Join([]string{headers.ContentTypeJSON, headers.ContentTypeEventStream}, ", "))
963985
req.Header.Set("Mcp-Protocol-Version", tt.protocolVersion)
964986
req.Header.Set("Mcp-Method", "tools/list")

pkg/inventory/protocol_version.go

Lines changed: 0 additions & 74 deletions
This file was deleted.

pkg/inventory/protocol_version_test.go

Lines changed: 0 additions & 119 deletions
This file was deleted.

pkg/inventory/registry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ func shouldStripMCPAppsMetadata(ctx context.Context, featureFlagEnabled bool) bo
221221
// /insiders route.
222222
func (r *Inventory) RegisterTools(ctx context.Context, s *mcp.Server, deps any, middleware ...ToolHandlerMiddleware) {
223223
tools := r.ToolsForRegistration(ctx)
224-
addToolProtocolVersionMiddleware(s, tools)
224+
addToolAvailabilityMiddleware(s, tools)
225225
for _, tool := range tools {
226226
tool.RegisterFunc(s, deps, middleware...)
227227
}

pkg/inventory/server_tool.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ type ServerTool struct {
8585
// call this tool. Empty means the tool is available on every version.
8686
MinimumProtocolVersion string
8787

88+
// RequiredElicitationMode is the elicitation mode the client must support to
89+
// list or call this tool. Empty means the tool does not require elicitation.
90+
RequiredElicitationMode ElicitationMode
91+
8892
// RequiredScopes specifies the minimum OAuth scopes required for this tool.
8993
// These are the scopes that must be present for the tool to function.
9094
RequiredScopes []string
@@ -123,6 +127,7 @@ func (st *ServerTool) RegisterFunc(s *mcp.Server, deps any, middleware ...ToolHa
123127
for i := len(middleware) - 1; i >= 0; i-- {
124128
handler = middleware[i](handler)
125129
}
130+
handler = st.wrapAvailabilityCheck(handler)
126131
// Make a shallow copy of the tool to avoid mutating the original
127132
toolCopy := st.Tool
128133
// Apply icons from toolset metadata if tool doesn't have icons set

0 commit comments

Comments
 (0)