Skip to content

Commit d87f53a

Browse files
feat: Add Meta support for resources and prompts in ToolsetRegistry
- Add Meta field with toolset info to all resource templates and prompts - Create NewResourceMeta and NewPromptMeta helpers - Extend ToolsetRegistry to accept resources and prompts via WithResourceTemplates/WithPrompts - Registry now automatically distributes resources/prompts to correct toolsets - Remove addResourceTemplatesAndPrompts helper (no longer needed)
1 parent 689380b commit d87f53a

File tree

5 files changed

+102
-33
lines changed

5 files changed

+102
-33
lines changed

pkg/github/issues.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,6 +1783,7 @@ func parseISOTimestamp(timestamp string) (time.Time, error) {
17831783

17841784
func AssignCodingAgentPrompt(t translations.TranslationHelperFunc) (mcp.Prompt, mcp.PromptHandler) {
17851785
return mcp.Prompt{
1786+
Meta: NewPromptMeta(ToolsetMetadataIssues),
17861787
Name: "AssignCodingAgent",
17871788
Description: t("PROMPT_ASSIGN_CODING_AGENT_DESCRIPTION", "Assign GitHub Coding Agent to multiple tasks in a GitHub repository."),
17881789
Arguments: []*mcp.PromptArgument{

pkg/github/repository_resource.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var (
3131
// GetRepositoryResourceContent defines the resource template and handler for getting repository content.
3232
func GetRepositoryResourceContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) {
3333
return mcp.ResourceTemplate{
34+
Meta: NewResourceMeta(ToolsetMetadataRepos),
3435
Name: "repository_content",
3536
URITemplate: repositoryResourceContentURITemplate.Raw(), // Resource template
3637
Description: t("RESOURCE_REPOSITORY_CONTENT_DESCRIPTION", "Repository Content"),
@@ -41,6 +42,7 @@ func GetRepositoryResourceContent(getClient GetClientFn, getRawClient raw.GetRaw
4142
// GetRepositoryResourceBranchContent defines the resource template and handler for getting repository content for a branch.
4243
func GetRepositoryResourceBranchContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) {
4344
return mcp.ResourceTemplate{
45+
Meta: NewResourceMeta(ToolsetMetadataRepos),
4446
Name: "repository_content_branch",
4547
URITemplate: repositoryResourceBranchContentURITemplate.Raw(), // Resource template
4648
Description: t("RESOURCE_REPOSITORY_CONTENT_BRANCH_DESCRIPTION", "Repository Content for specific branch"),
@@ -51,6 +53,7 @@ func GetRepositoryResourceBranchContent(getClient GetClientFn, getRawClient raw.
5153
// GetRepositoryResourceCommitContent defines the resource template and handler for getting repository content for a commit.
5254
func GetRepositoryResourceCommitContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) {
5355
return mcp.ResourceTemplate{
56+
Meta: NewResourceMeta(ToolsetMetadataRepos),
5457
Name: "repository_content_commit",
5558
URITemplate: repositoryResourceCommitContentURITemplate.Raw(), // Resource template
5659
Description: t("RESOURCE_REPOSITORY_CONTENT_COMMIT_DESCRIPTION", "Repository Content for specific commit"),
@@ -61,6 +64,7 @@ func GetRepositoryResourceCommitContent(getClient GetClientFn, getRawClient raw.
6164
// GetRepositoryResourceTagContent defines the resource template and handler for getting repository content for a tag.
6265
func GetRepositoryResourceTagContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) {
6366
return mcp.ResourceTemplate{
67+
Meta: NewResourceMeta(ToolsetMetadataRepos),
6468
Name: "repository_content_tag",
6569
URITemplate: repositoryResourceTagContentURITemplate.Raw(), // Resource template
6670
Description: t("RESOURCE_REPOSITORY_CONTENT_TAG_DESCRIPTION", "Repository Content for specific tag"),
@@ -71,6 +75,7 @@ func GetRepositoryResourceTagContent(getClient GetClientFn, getRawClient raw.Get
7175
// GetRepositoryResourcePrContent defines the resource template and handler for getting repository content for a pull request.
7276
func GetRepositoryResourcePrContent(getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) (mcp.ResourceTemplate, mcp.ResourceHandler) {
7377
return mcp.ResourceTemplate{
78+
Meta: NewResourceMeta(ToolsetMetadataRepos),
7479
Name: "repository_content_pr",
7580
URITemplate: repositoryResourcePrContentURITemplate.Raw(), // Resource template
7681
Description: t("RESOURCE_REPOSITORY_CONTENT_PR_DESCRIPTION", "Repository Content for specific pull request"),

pkg/github/tools.go

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ func NewToolMeta(toolset ToolsetMetadata, requiredScopes ...scopes.Scope) mcp.Me
4343
return meta
4444
}
4545

46+
// NewResourceMeta creates resource template metadata with the given toolset.
47+
// Returns mcp.Meta (map[string]any) for direct use in mcp.ResourceTemplate.Meta.
48+
func NewResourceMeta(toolset ToolsetMetadata) mcp.Meta {
49+
if toolset.ID == "" {
50+
panic("toolset ID is required for ResourceMeta")
51+
}
52+
return mcp.Meta{"toolset": toolset.ID}
53+
}
54+
55+
// NewPromptMeta creates prompt metadata with the given toolset.
56+
// Returns mcp.Meta (map[string]any) for direct use in mcp.Prompt.Meta.
57+
func NewPromptMeta(toolset ToolsetMetadata) mcp.Meta {
58+
if toolset.ID == "" {
59+
panic("toolset ID is required for PromptMeta")
60+
}
61+
return mcp.Meta{"toolset": toolset.ID}
62+
}
63+
4664
var (
4765
ToolsetMetadataAll = ToolsetMetadata{
4866
ID: "all",
@@ -318,7 +336,24 @@ func NewDefaultToolsetRegistry(getClient GetClientFn, getGQLClient GetGQLClientF
318336
// Include all available toolsets plus experiments (which has no tools but needs to exist)
319337
toolsetMetadatas := append(AvailableToolsets(), ToolsetMetadataExperiments)
320338

321-
return toolsets.NewToolsetRegistry(toolsetMetadatas, tools)
339+
// Resource templates - self-describing with toolset in Meta
340+
resourceTemplates := []toolsets.ServerResourceTemplate{
341+
toolsets.NewServerResourceTemplate(GetRepositoryResourceContent(getClient, getRawClient, t)),
342+
toolsets.NewServerResourceTemplate(GetRepositoryResourceBranchContent(getClient, getRawClient, t)),
343+
toolsets.NewServerResourceTemplate(GetRepositoryResourceCommitContent(getClient, getRawClient, t)),
344+
toolsets.NewServerResourceTemplate(GetRepositoryResourceTagContent(getClient, getRawClient, t)),
345+
toolsets.NewServerResourceTemplate(GetRepositoryResourcePrContent(getClient, getRawClient, t)),
346+
}
347+
348+
// Prompts - self-describing with toolset in Meta
349+
prompts := []toolsets.ServerPrompt{
350+
toolsets.NewServerPrompt(AssignCodingAgentPrompt(t)),
351+
toolsets.NewServerPrompt(IssueToFixWorkflowPrompt(t)),
352+
}
353+
354+
return toolsets.NewToolsetRegistry(toolsetMetadatas, tools).
355+
WithResourceTemplates(resourceTemplates...).
356+
WithPrompts(prompts...)
322357
}
323358

324359
// DefaultToolsetGroup creates a ToolsetGroup with the default configuration.
@@ -330,37 +365,11 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
330365
AvailableScopes: nil, // No scope filtering for backwards compatibility
331366
})
332367

333-
// Add resource templates and prompts (these aren't tools, handled separately)
334-
addResourceTemplatesAndPrompts(tsg, getClient, getRawClient, t)
335-
336368
tsg.AddDeprecatedToolAliases(DeprecatedToolAliases)
337369

338370
return tsg
339371
}
340372

341-
// addResourceTemplatesAndPrompts adds resource templates and prompts to toolsets.
342-
// These are not tools and need to be added separately.
343-
func addResourceTemplatesAndPrompts(tsg *toolsets.ToolsetGroup, getClient GetClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc) {
344-
// Add repository resource templates
345-
if repos, err := tsg.GetToolset(ToolsetMetadataRepos.ID); err == nil {
346-
repos.AddResourceTemplates(
347-
toolsets.NewServerResourceTemplate(GetRepositoryResourceContent(getClient, getRawClient, t)),
348-
toolsets.NewServerResourceTemplate(GetRepositoryResourceBranchContent(getClient, getRawClient, t)),
349-
toolsets.NewServerResourceTemplate(GetRepositoryResourceCommitContent(getClient, getRawClient, t)),
350-
toolsets.NewServerResourceTemplate(GetRepositoryResourceTagContent(getClient, getRawClient, t)),
351-
toolsets.NewServerResourceTemplate(GetRepositoryResourcePrContent(getClient, getRawClient, t)),
352-
)
353-
}
354-
355-
// Add issue prompts
356-
if issues, err := tsg.GetToolset(ToolsetMetadataIssues.ID); err == nil {
357-
issues.AddPrompts(
358-
toolsets.NewServerPrompt(AssignCodingAgentPrompt(t)),
359-
toolsets.NewServerPrompt(IssueToFixWorkflowPrompt(t)),
360-
)
361-
}
362-
}
363-
364373
// InitDynamicToolset creates a dynamic toolset that can be used to enable other toolsets, and so requires the server and toolset group as arguments
365374
//
366375
//nolint:unused

pkg/github/workflow_prompts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
// IssueToFixWorkflowPrompt provides a guided workflow for creating an issue and then generating a PR to fix it
1212
func IssueToFixWorkflowPrompt(t translations.TranslationHelperFunc) (tool mcp.Prompt, handler mcp.PromptHandler) {
1313
return mcp.Prompt{
14+
Meta: NewPromptMeta(ToolsetMetadataIssues),
1415
Name: "issue_to_fix_workflow",
1516
Description: t("PROMPT_ISSUE_TO_FIX_WORKFLOW_DESCRIPTION", "Create an issue for a problem and then generate a pull request to fix it"),
1617
Arguments: []*mcp.PromptArgument{

pkg/toolsets/toolsets.go

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,13 @@ type ToolsetMetadata struct {
213213
Description string
214214
}
215215

216-
// ToolsetRegistry holds a collection of toolset definitions and their tools.
216+
// ToolsetRegistry holds a collection of toolset definitions and their tools, resources, and prompts.
217217
// It provides a NewToolsetGroup method to create configured ToolsetGroups.
218218
type ToolsetRegistry struct {
219-
toolsetMetadatas []ToolsetMetadata
220-
tools []ServerTool
219+
toolsetMetadatas []ToolsetMetadata
220+
tools []ServerTool
221+
resourceTemplates []ServerResourceTemplate
222+
prompts []ServerPrompt
221223
}
222224

223225
// NewToolsetRegistry creates a new ToolsetRegistry with the given toolset metadata and tools.
@@ -228,6 +230,18 @@ func NewToolsetRegistry(toolsetMetadatas []ToolsetMetadata, tools []ServerTool)
228230
}
229231
}
230232

233+
// WithResourceTemplates adds resource templates to the registry.
234+
func (r *ToolsetRegistry) WithResourceTemplates(templates ...ServerResourceTemplate) *ToolsetRegistry {
235+
r.resourceTemplates = append(r.resourceTemplates, templates...)
236+
return r
237+
}
238+
239+
// WithPrompts adds prompts to the registry.
240+
func (r *ToolsetRegistry) WithPrompts(prompts ...ServerPrompt) *ToolsetRegistry {
241+
r.prompts = append(r.prompts, prompts...)
242+
return r
243+
}
244+
231245
// ToolsetGroupConfig specifies the configuration for creating a ToolsetGroup.
232246
type ToolsetGroupConfig struct {
233247
// ReadOnly when true restricts the group to read-only tools
@@ -272,8 +286,40 @@ func (r *ToolsetRegistry) NewToolsetGroup(config ToolsetGroupConfig) *ToolsetGro
272286
toolsByToolset[toolsetID] = append(toolsByToolset[toolsetID], tool)
273287
}
274288

275-
// Create toolsets and add tools
276-
for toolsetID, toolsetTools := range toolsByToolset {
289+
// Group resources by toolset
290+
resourcesByToolset := make(map[string][]ServerResourceTemplate)
291+
for _, resource := range r.resourceTemplates {
292+
toolsetID := getToolsetFromMeta(resource.Template.Meta)
293+
if toolsetID == "" {
294+
panic(fmt.Sprintf("resource template %q has no toolset in Meta", resource.Template.Name))
295+
}
296+
resourcesByToolset[toolsetID] = append(resourcesByToolset[toolsetID], resource)
297+
}
298+
299+
// Group prompts by toolset
300+
promptsByToolset := make(map[string][]ServerPrompt)
301+
for _, prompt := range r.prompts {
302+
toolsetID := getToolsetFromMeta(prompt.Prompt.Meta)
303+
if toolsetID == "" {
304+
panic(fmt.Sprintf("prompt %q has no toolset in Meta", prompt.Prompt.Name))
305+
}
306+
promptsByToolset[toolsetID] = append(promptsByToolset[toolsetID], prompt)
307+
}
308+
309+
// Collect all toolset IDs that have tools, resources, or prompts
310+
allToolsetIDs := make(map[string]bool)
311+
for id := range toolsByToolset {
312+
allToolsetIDs[id] = true
313+
}
314+
for id := range resourcesByToolset {
315+
allToolsetIDs[id] = true
316+
}
317+
for id := range promptsByToolset {
318+
allToolsetIDs[id] = true
319+
}
320+
321+
// Create toolsets and add tools, resources, and prompts
322+
for toolsetID := range allToolsetIDs {
277323
meta, ok := metadataByID[toolsetID]
278324
if !ok {
279325
// Use a default description if not provided
@@ -282,14 +328,21 @@ func (r *ToolsetRegistry) NewToolsetGroup(config ToolsetGroupConfig) *ToolsetGro
282328

283329
ts := NewToolset(meta.ID, meta.Description)
284330

285-
for _, tool := range toolsetTools {
331+
// Add tools
332+
for _, tool := range toolsByToolset[toolsetID] {
286333
if isReadOnlyTool(tool) {
287334
ts.readTools = append(ts.readTools, tool)
288335
} else {
289336
ts.writeTools = append(ts.writeTools, tool)
290337
}
291338
}
292339

340+
// Add resources
341+
ts.resourceTemplates = append(ts.resourceTemplates, resourcesByToolset[toolsetID]...)
342+
343+
// Add prompts
344+
ts.prompts = append(ts.prompts, promptsByToolset[toolsetID]...)
345+
293346
tsg.AddToolset(ts)
294347
}
295348

0 commit comments

Comments
 (0)