Make search_issues semantic by default · github/github-mcp-server@ccd71b0 · GitHub
Skip to content

Commit ccd71b0

Browse files
authored
Make search_issues semantic by default
1 parent 22b6d74 commit ccd71b0

15 files changed

Lines changed: 369 additions & 72 deletions

‎README.md‎

Lines changed: 1 addition & 1 deletion

‎docs/feature-flags.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ runtime behavior (such as output formatting) won't appear here.
413413
- `owner`: Optional repository owner. If provided with repo, only issues for this repository are listed. (string, optional)
414414
- `page`: Page number for pagination (min 1) (number, optional)
415415
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
416-
- `query`: Search query using GitHub issues search syntax (string, required)
416+
- `query`: The search query, as natural language. When the user gives alternative wordings, include them as plain words rather than joining them with OR. (string, required)
417417
- `repo`: Optional repository name. If provided with owner, only issues for this repository are listed. (string, optional)
418418
- `sort`: Sort field by number of matches of categories, defaults to best match (string, optional)
419419

‎docs/insiders-features.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ The list below is generated from the Go source. It covers tool **inventory and s
208208
- `owner`: Optional repository owner. If provided with repo, only issues for this repository are listed. (string, optional)
209209
- `page`: Page number for pagination (min 1) (number, optional)
210210
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
211-
- `query`: Search query using GitHub issues search syntax (string, required)
211+
- `query`: The search query, as natural language. When the user gives alternative wordings, include them as plain words rather than joining them with OR. (string, required)
212212
- `repo`: Optional repository name. If provided with owner, only issues for this repository are listed. (string, optional)
213213
- `sort`: Sort field by number of matches of categories, defaults to best match (string, optional)
214214

‎internal/ghmcp/server.go‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se
138138
return nil, fmt.Errorf("failed to parse API host: %w", err)
139139
}
140140

141+
hostType, err := utils.ParseHostType(cfg.Host)
142+
if err != nil {
143+
return nil, fmt.Errorf("failed to classify API host: %w", err)
144+
}
145+
141146
clients, err := createGitHubClients(cfg, apiHost)
142147
if err != nil {
143148
return nil, fmt.Errorf("failed to create GitHub clients: %w", err)
@@ -165,7 +170,7 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se
165170
obs,
166171
)
167172
// Build and register the tool/resource/prompt inventory
168-
inventoryBuilder := github.NewInventory(cfg.Translator).
173+
inventoryBuilder := github.NewInventory(cfg.Translator, github.WithHost(hostType)).
169174
WithDeprecatedAliases(github.DeprecatedToolAliases).
170175
WithReadOnly(cfg.ReadOnly).
171176
WithToolsets(github.ResolvedEnabledToolsets(cfg.EnabledToolsets, cfg.EnabledTools)).

‎pkg/github/__toolsnaps__/search_issues.snap‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"readOnlyHint": true,
55
"title": "Search issues"
66
},
7-
"description": "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue",
7+
"description": "Search issues using natural-language semantic matching. Best for conceptual or paraphrased queries (e.g. \"login fails after password reset\"). Already scoped to is:issue.",
88
"inputSchema": {
99
"properties": {
1010
"order": {
@@ -31,7 +31,7 @@
3131
"type": "number"
3232
},
3333
"query": {
34-
"description": "Search query using GitHub issues search syntax",
34+
"description": "The search query, as natural language. When the user gives alternative wordings, include them as plain words rather than joining them with OR.",
3535
"type": "string"
3636
},
3737
"repo": {

‎pkg/github/__toolsnaps__/search_issues_ff_fields_param.snap‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"readOnlyHint": true,
55
"title": "Search issues"
66
},
7-
"description": "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue",
7+
"description": "Search issues using natural-language semantic matching. Best for conceptual or paraphrased queries (e.g. \"login fails after password reset\"). Already scoped to is:issue.",
88
"inputSchema": {
99
"properties": {
1010
"fields": {
@@ -64,7 +64,7 @@
6464
"type": "number"
6565
},
6666
"query": {
67-
"description": "Search query using GitHub issues search syntax",
67+
"description": "The search query, as natural language. When the user gives alternative wordings, include them as plain words rather than joining them with OR.",
6868
"type": "string"
6969
},
7070
"repo": {

‎pkg/github/inventory.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import (
1010
// This function is stateless - no dependencies are captured.
1111
// Handlers are generated on-demand during registration via RegisterAll(ctx, server, deps).
1212
// The "default" keyword in WithToolsets will expand to toolsets marked with Default: true.
13-
func NewInventory(t translations.TranslationHelperFunc) *inventory.Builder {
13+
func NewInventory(t translations.TranslationHelperFunc, opts ...ToolOption) *inventory.Builder {
1414
return inventory.NewBuilder().
15-
SetTools(AllTools(t)).
15+
SetTools(AllTools(t, opts...)).
1616
SetResources(AllResources(t)).
1717
SetPrompts(AllPrompts(t))
1818
}

‎pkg/github/issues.go‎

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,8 +1601,8 @@ func ReprioritizeSubIssue(ctx context.Context, client *github.Client, owner stri
16011601
// LegacySearchIssues register under the tool name "search_issues"; exactly one is
16021602
// active for any given request thanks to mutually exclusive FeatureFlagEnable /
16031603
// FeatureFlagDisable annotations.
1604-
func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool {
1605-
st := searchIssuesTool(t, true)
1604+
func SearchIssues(t translations.TranslationHelperFunc, opts ...ToolOption) inventory.ServerTool {
1605+
st := searchIssuesTool(t, true, newToolConfig(opts))
16061606
st.FeatureFlagEnable = FeatureFlagFieldsParam
16071607
return st
16081608
}
@@ -1612,23 +1612,50 @@ func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool {
16121612
// filters results, so it acts as the kill switch when the flag is off. It owns
16131613
// the canonical search_issues.snap; the flag-enabled variant owns
16141614
// search_issues_ff_<flag>.snap. Delete this function when the flag is removed.
1615-
func LegacySearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool {
1616-
st := searchIssuesTool(t, false)
1615+
func LegacySearchIssues(t translations.TranslationHelperFunc, opts ...ToolOption) inventory.ServerTool {
1616+
st := searchIssuesTool(t, false, newToolConfig(opts))
16171617
st.FeatureFlagDisable = []string{FeatureFlagFieldsParam}
16181618
return st
16191619
}
16201620

1621+
// The two search engines want opposite things from a caller, so steering advice
1622+
// for one is counterproductive for the other: semantic rewards paraphrased
1623+
// natural language and degrades on boolean operators, while lexical needs the
1624+
// caller's literal keywords and handles OR fine. The description has to describe
1625+
// the engine the host will actually use.
1626+
const (
1627+
searchIssuesSemanticDescription = "Search issues using natural-language semantic matching. Best for conceptual or paraphrased queries (e.g. \"login fails after password reset\"). Already scoped to is:issue."
1628+
searchIssuesLexicalDescription = "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue"
1629+
1630+
searchIssuesSemanticQueryDescription = "The search query, as natural language. When the user gives alternative wordings, include them as plain words rather than joining them with OR."
1631+
searchIssuesLexicalQueryDescription = "Search query using GitHub issues search syntax"
1632+
)
1633+
16211634
// searchIssuesTool builds the search_issues tool. When includeFields is true the
16221635
// tool advertises the optional `fields` parameter, filters each result to the
16231636
// requested subset, and emits fields telemetry. When false it is the original
16241637
// tool with no fields parameter and no filtering.
1625-
func searchIssuesTool(t translations.TranslationHelperFunc, includeFields bool) inventory.ServerTool {
1638+
func searchIssuesTool(t translations.TranslationHelperFunc, includeFields bool, cfg toolConfig) inventory.ServerTool {
1639+
// Semantic is the default; however as it is not available on GHES, we fall back to
1640+
// lexical search for that host type.
1641+
mode := searchModeSemantic
1642+
if cfg.hostType == utils.HostTypeGHES {
1643+
mode = searchModeLexical
1644+
}
1645+
1646+
toolDescription := searchIssuesSemanticDescription
1647+
queryDescription := searchIssuesSemanticQueryDescription
1648+
if mode == searchModeLexical {
1649+
toolDescription = searchIssuesLexicalDescription
1650+
queryDescription = searchIssuesLexicalQueryDescription
1651+
}
1652+
16261653
schema := &jsonschema.Schema{
16271654
Type: "object",
16281655
Properties: map[string]*jsonschema.Schema{
16291656
"query": {
16301657
Type: "string",
1631-
Description: "Search query using GitHub issues search syntax",
1658+
Description: queryDescription,
16321659
},
16331660
"owner": {
16341661
Type: "string",
@@ -1675,7 +1702,7 @@ func searchIssuesTool(t translations.TranslationHelperFunc, includeFields bool)
16751702
ToolsetMetadataIssues,
16761703
mcp.Tool{
16771704
Name: "search_issues",
1678-
Description: t("TOOL_SEARCH_ISSUES_DESCRIPTION", "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue"),
1705+
Description: t("TOOL_SEARCH_ISSUES_DESCRIPTION", toolDescription),
16791706
Annotations: &mcp.ToolAnnotations{
16801707
Title: t("TOOL_SEARCH_ISSUES_USER_TITLE", "Search issues"),
16811708
ReadOnlyHint: true,
@@ -1692,7 +1719,7 @@ func searchIssuesTool(t translations.TranslationHelperFunc, includeFields bool)
16921719
}
16931720
options = append(options, withFieldsFiltering(deps, "search_issues", fields))
16941721
}
1695-
result, err := searchIssuesHandler(ctx, deps, args, options...)
1722+
result, err := searchIssuesHandler(ctx, deps, args, mode, options...)
16961723
return result, nil, err
16971724
})
16981725
}
@@ -1960,10 +1987,15 @@ func fetchIssueReadEnrichment(ctx context.Context, gqlClient *githubv4.Client, n
19601987
// searchIssuesHandler runs the REST issues search, enriches each hit with custom field values
19611988
// fetched via a single follow-up GraphQL nodes() query, and applies any post-process options
19621989
// (e.g. IFC labelling).
1963-
func searchIssuesHandler(ctx context.Context, deps ToolDependencies, args map[string]any, options ...searchOption) (*mcp.CallToolResult, error) {
1990+
func searchIssuesHandler(ctx context.Context, deps ToolDependencies, args map[string]any, mode searchMode, options ...searchOption) (*mcp.CallToolResult, error) {
19641991
const errorPrefix = "failed to search issues"
19651992

1966-
query, opts, err := prepareSearchArgs(args, "issue")
1993+
cfg := searchConfig{}
1994+
for _, opt := range options {
1995+
opt(&cfg)
1996+
}
1997+
1998+
query, opts, err := prepareSearchArgs(args, "issue", mode)
19671999
if err != nil {
19682000
return utils.NewToolResultError(err.Error()), nil
19692001
}
@@ -2013,11 +2045,6 @@ func searchIssuesHandler(ctx context.Context, deps ToolDependencies, args map[st
20132045
Items: items,
20142046
}
20152047

2016-
cfg := searchConfig{}
2017-
for _, opt := range options {
2018-
opt(&cfg)
2019-
}
2020-
20212048
filtered := false
20222049
var payload any = response
20232050
if len(cfg.fields) > 0 {

‎pkg/github/issues_test.go‎

Lines changed: 42 additions & 32 deletions

0 commit comments

Comments
 (0)