Skip to content

Commit ae96122

Browse files
authored
feat(api): add MCP resource (#1091)
* Add MCP resources to Toolset interface Signed-off-by: Swarup Ghosh <swghosh@redhat.com> * Add MCP resource capabilities to Server Signed-off-by: Swarup Ghosh <swghosh@redhat.com> * React to changes in the Toolset interface Signed-off-by: Swarup Ghosh <swghosh@redhat.com> * Update suggestions encountered during review Signed-off-by: Swarup Ghosh <swghosh@redhat.com> * Update suggestions from review Signed-off-by: Swarup Ghosh <swghosh@redhat.com> * Add more test coverage for MCP resources (assertion pairing in resource_test.go, Blob/MIMEType-override coverage, filter/mutator tests, AddResource URI-validation panic) Signed-off-by: Swarup Ghosh <swghosh@redhat.com> --------- Signed-off-by: Swarup Ghosh <swghosh@redhat.com>
1 parent e3e0e24 commit ae96122

21 files changed

Lines changed: 1226 additions & 16 deletions

AGENTS.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,13 @@ The `docs/specs/` directory contains feature specifications (living documentatio
318318
### Documentation conventions
319319

320320
- Use **lowercase filenames** for new documentation files (e.g., `configuration.md`, `prompts.md`)
321-
- The toolsets table in `README.md` and `docs/configuration.md` is **auto-generated** - use `make update-readme-tools` to update it
322-
- Both files use markers (`<!-- AVAILABLE-TOOLSETS-START -->` / `<!-- AVAILABLE-TOOLSETS-END -->`) for the generated content
321+
- The toolsets table, tools, prompts, resources, and resource templates in `README.md` and `docs/configuration.md` are **auto-generated** - use `make update-readme-tools` to update them after modifying toolsets
322+
- Both files use marker pairs for the generated content:
323+
- `<!-- AVAILABLE-TOOLSETS-START -->` / `<!-- AVAILABLE-TOOLSETS-END -->` (toolset summary table)
324+
- `<!-- AVAILABLE-TOOLSETS-TOOLS-START -->` / `<!-- AVAILABLE-TOOLSETS-TOOLS-END -->` (tool details)
325+
- `<!-- AVAILABLE-TOOLSETS-PROMPTS-START -->` / `<!-- AVAILABLE-TOOLSETS-PROMPTS-END -->` (prompt details)
326+
- `<!-- AVAILABLE-TOOLSETS-RESOURCES-START -->` / `<!-- AVAILABLE-TOOLSETS-RESOURCES-END -->` (resource details)
327+
- `<!-- AVAILABLE-TOOLSETS-RESOURCES-TEMPLATES-START -->` / `<!-- AVAILABLE-TOOLSETS-RESOURCES-TEMPLATES-END -->` (resource template details)
323328

324329
## Distribution Methods
325330

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,20 @@ In case multi-cluster support is enabled (default) and you have access to multip
597597

598598
<!-- AVAILABLE-TOOLSETS-PROMPTS-END -->
599599

600+
### Resources
601+
602+
<!-- AVAILABLE-TOOLSETS-RESOURCES-START -->
603+
604+
605+
<!-- AVAILABLE-TOOLSETS-RESOURCES-END -->
606+
607+
### Resource Templates
608+
609+
<!-- AVAILABLE-TOOLSETS-RESOURCES-TEMPLATES-START -->
610+
611+
612+
<!-- AVAILABLE-TOOLSETS-RESOURCES-TEMPLATES-END -->
613+
600614
## Helm Chart
601615

602616
A [Helm Chart](https://helm.sh) is available to simplify the deployment of the Kubernetes MCP server.

docs/configuration.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,20 @@ Toolsets group related tools together. Enable only the toolsets you need to redu
311311
toolsets = ["core", "config", "helm", "kubevirt"]
312312
```
313313

314+
**Available Resources:**
315+
316+
<!-- AVAILABLE-TOOLSETS-RESOURCES-START -->
317+
318+
319+
<!-- AVAILABLE-TOOLSETS-RESOURCES-END -->
320+
321+
**Available Resource Templates:**
322+
323+
<!-- AVAILABLE-TOOLSETS-RESOURCES-TEMPLATES-START -->
324+
325+
326+
<!-- AVAILABLE-TOOLSETS-RESOURCES-TEMPLATES-END -->
327+
314328
### Tool Filtering
315329

316330
Fine-grained control over individual tools within enabled toolsets.

internal/test/mcp.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,21 @@ func (m *McpClient) ListPrompts() (*mcp.ListPromptsResult, error) {
327327
return m.Session.ListPrompts(m.ctx, &mcp.ListPromptsParams{})
328328
}
329329

330+
// ListResources helper function to list available resources
331+
func (m *McpClient) ListResources() (*mcp.ListResourcesResult, error) {
332+
return m.Session.ListResources(m.ctx, &mcp.ListResourcesParams{})
333+
}
334+
335+
// ReadResource helper function to read a resource by URI
336+
func (m *McpClient) ReadResource(uri string) (*mcp.ReadResourceResult, error) {
337+
return m.Session.ReadResource(m.ctx, &mcp.ReadResourceParams{URI: uri})
338+
}
339+
340+
// ListResourceTemplates helper function to list available resource templates
341+
func (m *McpClient) ListResourceTemplates() (*mcp.ListResourceTemplatesResult, error) {
342+
return m.Session.ListResourceTemplates(m.ctx, &mcp.ListResourceTemplatesParams{})
343+
}
344+
330345
// GetPrompt helper function to get a prompt by name
331346
func (m *McpClient) GetPrompt(name string, arguments map[string]string) (*mcp.GetPromptResult, error) {
332347
return m.Session.GetPrompt(m.ctx, &mcp.GetPromptParams{

internal/tools/update-readme/main.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,50 @@ func main() {
135135
toolsetPrompts.String(),
136136
)
137137

138+
// Available Toolset Resources
139+
toolsetResources := strings.Builder{}
140+
for _, toolset := range toolsetsList {
141+
resources := toolset.GetResources()
142+
if len(resources) == 0 {
143+
continue
144+
}
145+
toolsetResources.WriteString("<details>\n\n<summary>" + toolset.GetName() + "</summary>\n\n")
146+
for _, resource := range resources {
147+
fmt.Fprintf(&toolsetResources, "- **%s** - %s\n", resource.Resource.Name, resource.Resource.Description)
148+
fmt.Fprintf(&toolsetResources, " - URI: `%s`\n", resource.Resource.URI)
149+
fmt.Fprintf(&toolsetResources, " - MIME Type: `%s`\n", resource.Resource.MIMEType)
150+
}
151+
toolsetResources.WriteString("</details>\n\n")
152+
}
153+
updated = replaceBetweenMarkers(
154+
updated,
155+
"<!-- AVAILABLE-TOOLSETS-RESOURCES-START -->",
156+
"<!-- AVAILABLE-TOOLSETS-RESOURCES-END -->",
157+
toolsetResources.String(),
158+
)
159+
160+
// Available Toolset Resource Templates
161+
toolsetResourceTemplates := strings.Builder{}
162+
for _, toolset := range toolsetsList {
163+
templates := toolset.GetResourceTemplates()
164+
if len(templates) == 0 {
165+
continue
166+
}
167+
toolsetResourceTemplates.WriteString("<details>\n\n<summary>" + toolset.GetName() + "</summary>\n\n")
168+
for _, template := range templates {
169+
fmt.Fprintf(&toolsetResourceTemplates, "- **%s** - %s\n", template.ResourceTemplate.Name, template.ResourceTemplate.Description)
170+
fmt.Fprintf(&toolsetResourceTemplates, " - URI Template: `%s`\n", template.ResourceTemplate.URITemplate)
171+
fmt.Fprintf(&toolsetResourceTemplates, " - MIME Type: `%s`\n", template.ResourceTemplate.MIMEType)
172+
}
173+
toolsetResourceTemplates.WriteString("</details>\n\n")
174+
}
175+
updated = replaceBetweenMarkers(
176+
updated,
177+
"<!-- AVAILABLE-TOOLSETS-RESOURCES-TEMPLATES-START -->",
178+
"<!-- AVAILABLE-TOOLSETS-RESOURCES-TEMPLATES-END -->",
179+
toolsetResourceTemplates.String(),
180+
)
181+
138182
if err := os.WriteFile(localReadmePath, []byte(updated), 0o644); err != nil {
139183
panic(err)
140184
}

pkg/api/toolsets.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ type Toolset interface {
4747
// GetPrompts returns the prompts provided by this toolset.
4848
// Returns nil if the toolset doesn't provide any prompts.
4949
GetPrompts() []ServerPrompt
50+
// GetResources returns the resources provided by this toolset.
51+
// Returns nil if the toolset doesn't provide any resources.
52+
GetResources() []ServerResource
53+
// GetResourceTemplates returns the resource templates provided by this toolset.
54+
// Returns nil if the toolset doesn't provide any resource templates.
55+
GetResourceTemplates() []ServerResourceTemplate
5056
}
5157

5258
type ToolCallRequest interface {
@@ -103,6 +109,56 @@ func NewToolCallResultStructured(structured any, err error) *ToolCallResult {
103109
return NewToolCallResultFull(content, structured, err)
104110
}
105111

112+
// Resource represents the metadata of an MCP resource.
113+
type Resource struct {
114+
URI string
115+
Name string
116+
Description string
117+
MIMEType string
118+
}
119+
120+
// ResourceContent is the value returned by a resource handler.
121+
// Exactly one of Text or Blob must be set; both cannot be nil or both non-nil.
122+
type ResourceContent struct {
123+
// MIMEType overrides Resource.MIMEType when set; otherwise Resource.MIMEType is used.
124+
MIMEType string
125+
// Text is the UTF-8 text content of the resource.
126+
Text string
127+
// Blob is the binary content of the resource.
128+
Blob []byte
129+
}
130+
131+
// ResourceHandler is called when a client reads a resource.
132+
// Session state (auth, request context) is available on ctx via sessionInjectionMiddleware.
133+
// Handlers should return a ResourceContent with exactly one of Text or Blob set.
134+
type ResourceHandler func(ctx context.Context) (*ResourceContent, error)
135+
136+
// ServerResource represents a resource that can be registered with the MCP server.
137+
type ServerResource struct {
138+
Resource Resource
139+
Handler ResourceHandler
140+
}
141+
142+
// ResourceTemplate represents the metadata of an MCP resource template.
143+
type ResourceTemplate struct {
144+
URITemplate string
145+
Name string
146+
Description string
147+
MIMEType string
148+
}
149+
150+
// ResourceTemplateHandler is called when a client reads a resource matching a template.
151+
// Session state (auth, request context) is available on ctx via sessionInjectionMiddleware.
152+
// The uri parameter is the actual resource URI that matches the template.
153+
// Handlers should return a ResourceContent with exactly one of Text or Blob set.
154+
type ResourceTemplateHandler func(ctx context.Context, uri string) (*ResourceContent, error)
155+
156+
// ServerResourceTemplate represents a resource template that can be registered with the MCP server.
157+
type ServerResourceTemplate struct {
158+
ResourceTemplate ResourceTemplate
159+
Handler ResourceTemplateHandler
160+
}
161+
106162
type ToolHandlerParams struct {
107163
context.Context
108164
BaseConfig

pkg/mcp/elicit_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,14 @@ func (m *mockElicitToolset) GetPrompts() []api.ServerPrompt {
281281
return nil
282282
}
283283

284+
func (m *mockElicitToolset) GetResources() []api.ServerResource {
285+
return nil
286+
}
287+
288+
func (m *mockElicitToolset) GetResourceTemplates() []api.ServerResourceTemplate {
289+
return nil
290+
}
291+
284292
func TestElicitationSuite(t *testing.T) {
285293
suite.Run(t, new(ElicitationSuite))
286294
}

0 commit comments

Comments
 (0)