Skip to content

Commit d5a2e4d

Browse files
Attach repo-visibility IFC label to find_duplicate results
1 parent 887d19a commit d5a2e4d

2 files changed

Lines changed: 105 additions & 1 deletion

File tree

pkg/github/find_duplicate.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strconv"
1010

1111
ghErrors "github.com/github/github-mcp-server/pkg/errors"
12+
"github.com/github/github-mcp-server/pkg/ifc"
1213
"github.com/github/github-mcp-server/pkg/inventory"
1314
"github.com/github/github-mcp-server/pkg/scopes"
1415
"github.com/github/github-mcp-server/pkg/translations"
@@ -168,7 +169,11 @@ func FindDuplicate(t translations.TranslationHelperFunc) inventory.ServerTool {
168169
return utils.NewToolResultErrorFromErr("failed to marshal duplicate candidates", err), nil, nil
169170
}
170171

171-
return utils.NewToolResultText(string(r)), nil, nil
172+
// Candidate issue titles are user-authored content scoped to the source
173+
// repository, so classify the result like issue_read.
174+
result := utils.NewToolResultText(string(r))
175+
result = attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, result, ifc.LabelRepoUserContent)
176+
return result, nil, nil
172177
})
173178
st.FeatureFlagEnable = FeatureFlagDuplicateDetection
174179
return st

pkg/github/find_duplicate_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,102 @@ func Test_FindDuplicate_Errors(t *testing.T) {
238238
getErrorResult(t, result)
239239
})
240240
}
241+
242+
func Test_FindDuplicate_IFCLabels(t *testing.T) {
243+
serverTool := FindDuplicate(translations.NullTranslationHelper)
244+
245+
rankedResults := []map[string]any{
246+
{
247+
"issue": map[string]any{
248+
"number": 585,
249+
"title": "Improve the onboarding flow for new users",
250+
"state": "open",
251+
"html_url": "https://github.com/owner/repo/issues/585",
252+
},
253+
"score": 1.93,
254+
"confidence": "high",
255+
"likely_duplicate": true,
256+
},
257+
}
258+
259+
// makeClient serves the semantic-similarity endpoint plus the repo lookup
260+
// that the IFC labeler uses to resolve visibility.
261+
makeClient := func(isPrivate bool, repoStatus int) *http.Client {
262+
handlers := map[string]http.HandlerFunc{
263+
string(endpointSemanticallySimilar): mockResponse(t, http.StatusOK, rankedResults),
264+
}
265+
if repoStatus != 0 && repoStatus != http.StatusOK {
266+
handlers[GetReposByOwnerByRepo] = mockResponse(t, repoStatus, "boom")
267+
} else {
268+
handlers[GetReposByOwnerByRepo] = mockResponse(t, http.StatusOK, map[string]any{
269+
"name": "repo",
270+
"private": isPrivate,
271+
})
272+
}
273+
return MockHTTPClientWithHandlers(handlers)
274+
}
275+
276+
req := map[string]any{
277+
"owner": "owner",
278+
"repo": "repo",
279+
"issue_number": float64(769),
280+
}
281+
282+
t.Run("flag disabled omits ifc label", func(t *testing.T) {
283+
deps := BaseDeps{Client: mustNewGHClient(t, makeClient(false, 0))}
284+
handler := serverTool.Handler(deps)
285+
request := createMCPRequest(req)
286+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
287+
require.NoError(t, err)
288+
require.False(t, result.IsError)
289+
assert.Nil(t, result.Meta)
290+
})
291+
292+
t.Run("flag enabled on public repo emits public untrusted", func(t *testing.T) {
293+
deps := BaseDeps{
294+
Client: mustNewGHClient(t, makeClient(false, 0)),
295+
featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
296+
}
297+
handler := serverTool.Handler(deps)
298+
request := createMCPRequest(req)
299+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
300+
require.NoError(t, err)
301+
require.False(t, result.IsError)
302+
require.NotNil(t, result.Meta)
303+
ifcMap := unmarshalIFC(t, result.Meta["ifc"])
304+
assert.Equal(t, "untrusted", ifcMap["integrity"])
305+
assert.Equal(t, "public", ifcMap["confidentiality"])
306+
})
307+
308+
t.Run("flag enabled on private repo emits private trusted", func(t *testing.T) {
309+
deps := BaseDeps{
310+
Client: mustNewGHClient(t, makeClient(true, 0)),
311+
featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
312+
}
313+
handler := serverTool.Handler(deps)
314+
request := createMCPRequest(req)
315+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
316+
require.NoError(t, err)
317+
require.False(t, result.IsError)
318+
require.NotNil(t, result.Meta)
319+
ifcMap := unmarshalIFC(t, result.Meta["ifc"])
320+
assert.Equal(t, "trusted", ifcMap["integrity"])
321+
assert.Equal(t, "private", ifcMap["confidentiality"])
322+
})
323+
324+
t.Run("visibility lookup failure omits label but still succeeds", func(t *testing.T) {
325+
deps := BaseDeps{
326+
Client: mustNewGHClient(t, makeClient(false, http.StatusInternalServerError)),
327+
featureChecker: featureCheckerFor(FeatureFlagIFCLabels),
328+
}
329+
handler := serverTool.Handler(deps)
330+
request := createMCPRequest(req)
331+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
332+
require.NoError(t, err)
333+
require.False(t, result.IsError, "tool call should still succeed when visibility lookup fails")
334+
if result.Meta != nil {
335+
_, hasIFC := result.Meta["ifc"]
336+
assert.False(t, hasIFC, "label must be omitted on visibility lookup failure")
337+
}
338+
})
339+
}

0 commit comments

Comments
 (0)