Implement SEP-973 (#570)
mcp/protocol: Implement SEP-973 - Define Icon structure, which includes source, mimeType and sizes. - sizes is any array of strings. refer https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1531 - Support setting websiteUrl, icons for mcp.Implementation - Support setting icons for mcp.Prompt - Support setting icons for mcp.Tool - Support setting icons for mcp.Resource Fixes #552
This commit is contained in:
parent
35dfcf21c9
commit
faddd764aa
7 changed files with 417 additions and 9 deletions
|
|
@ -7,6 +7,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
@ -50,19 +51,28 @@ func main() {
|
||||||
CompletionHandler: complete, // support completions by setting this handler
|
CompletionHandler: complete, // support completions by setting this handler
|
||||||
}
|
}
|
||||||
|
|
||||||
server := mcp.NewServer(&mcp.Implementation{Name: "everything"}, opts)
|
// Optionally add an icon to the server implementation.
|
||||||
|
icons, err := iconToBase64DataURL("./mcp.png")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to read icon: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := mcp.NewServer(&mcp.Implementation{Name: "everything", WebsiteURL: "https://example.com", Icons: icons}, opts)
|
||||||
|
|
||||||
// Add tools that exercise different features of the protocol.
|
// Add tools that exercise different features of the protocol.
|
||||||
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, contentTool)
|
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, contentTool)
|
||||||
mcp.AddTool(server, &mcp.Tool{Name: "greet (structured)"}, structuredTool) // returns structured output
|
mcp.AddTool(server, &mcp.Tool{Name: "greet (structured)"}, structuredTool) // returns structured output
|
||||||
mcp.AddTool(server, &mcp.Tool{Name: "ping"}, pingingTool) // performs a ping
|
mcp.AddTool(server, &mcp.Tool{Name: "greet (with Icons)", Icons: icons}, structuredTool) // tool with icons
|
||||||
mcp.AddTool(server, &mcp.Tool{Name: "log"}, loggingTool) // performs a log
|
mcp.AddTool(server, &mcp.Tool{Name: "greet (content with ResourceLink)"}, resourceLinkContentTool(icons)) // tool that returns content with a resource link
|
||||||
mcp.AddTool(server, &mcp.Tool{Name: "sample"}, samplingTool) // performs sampling
|
mcp.AddTool(server, &mcp.Tool{Name: "ping"}, pingingTool) // performs a ping
|
||||||
mcp.AddTool(server, &mcp.Tool{Name: "elicit"}, elicitingTool) // performs elicitation
|
mcp.AddTool(server, &mcp.Tool{Name: "log"}, loggingTool) // performs a log
|
||||||
mcp.AddTool(server, &mcp.Tool{Name: "roots"}, rootsTool) // lists roots
|
mcp.AddTool(server, &mcp.Tool{Name: "sample"}, samplingTool) // performs sampling
|
||||||
|
mcp.AddTool(server, &mcp.Tool{Name: "elicit"}, elicitingTool) // performs elicitation
|
||||||
|
mcp.AddTool(server, &mcp.Tool{Name: "roots"}, rootsTool) // lists roots
|
||||||
|
|
||||||
// Add a basic prompt.
|
// Add a basic prompt.
|
||||||
server.AddPrompt(&mcp.Prompt{Name: "greet"}, prompt)
|
server.AddPrompt(&mcp.Prompt{Name: "greet"}, prompt)
|
||||||
|
server.AddPrompt(&mcp.Prompt{Name: "greet (with Icons)", Icons: icons}, prompt) // greet prompt with icons
|
||||||
|
|
||||||
// Add an embedded resource.
|
// Add an embedded resource.
|
||||||
server.AddResource(&mcp.Resource{
|
server.AddResource(&mcp.Resource{
|
||||||
|
|
@ -70,6 +80,20 @@ func main() {
|
||||||
MIMEType: "text/plain",
|
MIMEType: "text/plain",
|
||||||
URI: "embedded:info",
|
URI: "embedded:info",
|
||||||
}, embeddedResource)
|
}, embeddedResource)
|
||||||
|
server.AddResource(&mcp.Resource{ // text resource with icons
|
||||||
|
Name: "info (with Icons)",
|
||||||
|
MIMEType: "text/plain",
|
||||||
|
URI: "embedded:info",
|
||||||
|
Icons: icons,
|
||||||
|
}, embeddedResource)
|
||||||
|
|
||||||
|
// Add a resource template.
|
||||||
|
server.AddResourceTemplate(&mcp.ResourceTemplate{
|
||||||
|
Name: "Resource template (with Icon)",
|
||||||
|
MIMEType: "text/plain",
|
||||||
|
URITemplate: "http://example.com/~{resource_name}/",
|
||||||
|
Icons: icons,
|
||||||
|
}, embeddedResource)
|
||||||
|
|
||||||
// Serve over stdio, or streamable HTTP if -http is set.
|
// Serve over stdio, or streamable HTTP if -http is set.
|
||||||
if *httpAddr != "" {
|
if *httpAddr != "" {
|
||||||
|
|
@ -140,6 +164,23 @@ func contentTool(ctx context.Context, req *mcp.CallToolRequest, args args) (*mcp
|
||||||
}, nil, nil
|
}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resourceLinkContentTool returns a ResourceLink content with icons.
|
||||||
|
func resourceLinkContentTool(icons []mcp.Icon) func(ctx context.Context, req *mcp.CallToolRequest, args args) (*mcp.CallToolResult, any, error) {
|
||||||
|
return func(ctx context.Context, req *mcp.CallToolRequest, args args) (*mcp.CallToolResult, any, error) {
|
||||||
|
return &mcp.CallToolResult{
|
||||||
|
Content: []mcp.Content{
|
||||||
|
&mcp.ResourceLink{
|
||||||
|
Name: "greeting",
|
||||||
|
Title: "A friendly greeting",
|
||||||
|
MIMEType: "text/plain",
|
||||||
|
URI: "data:text/plain,Hi%20" + url.PathEscape(args.Name),
|
||||||
|
Icons: icons,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type result struct {
|
type result struct {
|
||||||
Message string `json:"message" jsonschema:"the message to convey"`
|
Message string `json:"message" jsonschema:"the message to convey"`
|
||||||
}
|
}
|
||||||
|
|
@ -222,3 +263,16 @@ func complete(ctx context.Context, req *mcp.CompleteRequest) (*mcp.CompleteResul
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func iconToBase64DataURL(path string) ([]mcp.Icon, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []mcp.Icon{{
|
||||||
|
Source: "data:image/png;base64," + base64.StdEncoding.EncodeToString(data),
|
||||||
|
MIMEType: "image/png",
|
||||||
|
Sizes: []string{"48x48"},
|
||||||
|
Theme: "light", // or "dark" or empty
|
||||||
|
}}, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
BIN
examples/server/everything/mcp.png
Normal file
BIN
examples/server/everything/mcp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -111,6 +111,19 @@ func structuredTool(ctx context.Context, req *CallToolRequest, args *structuredI
|
||||||
return nil, &structuredOutput{"Ack " + args.In}, nil
|
return nil, &structuredOutput{"Ack " + args.In}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contentTool(ctx context.Context, req *CallToolRequest, args *structuredInput) (*CallToolResult, any, error) {
|
||||||
|
return &CallToolResult{
|
||||||
|
Content: []Content{
|
||||||
|
&ResourceLink{
|
||||||
|
Name: "Example Resource Link with Icons",
|
||||||
|
MIMEType: "text/plain",
|
||||||
|
URI: "https://example.com/resource/" + args.In,
|
||||||
|
Icons: []Icon{iconObj},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
type tomorrowInput struct {
|
type tomorrowInput struct {
|
||||||
Now time.Time
|
Now time.Time
|
||||||
}
|
}
|
||||||
|
|
@ -135,12 +148,26 @@ func incTool(_ context.Context, _ *CallToolRequest, args incInput) (*CallToolRes
|
||||||
return nil, incOutput{args.X + 1}, nil
|
return nil, incOutput{args.X + 1}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var iconObj = Icon{
|
||||||
|
Source: "foobar",
|
||||||
|
MIMEType: "image/png",
|
||||||
|
Sizes: []string{"48x48", "96x96"},
|
||||||
|
Theme: "light",
|
||||||
|
}
|
||||||
|
|
||||||
// runServerTest runs the server conformance test.
|
// runServerTest runs the server conformance test.
|
||||||
// It must be executed in a synctest bubble.
|
// It must be executed in a synctest bubble.
|
||||||
func runServerTest(t *testing.T, test *conformanceTest) {
|
func runServerTest(t *testing.T, test *conformanceTest) {
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
// Construct the server based on features listed in the test.
|
// Construct the server based on features listed in the test.
|
||||||
s := NewServer(&Implementation{Name: "testServer", Version: "v1.0.0"}, nil)
|
impl := &Implementation{Name: "testServer", Version: "v1.0.0"}
|
||||||
|
|
||||||
|
if test.name == "spec-sep-973-additional-metadata.txtar" {
|
||||||
|
impl.Icons = []Icon{iconObj}
|
||||||
|
impl.WebsiteURL = "https://github.com/modelcontextprotocol/go-sdk"
|
||||||
|
}
|
||||||
|
|
||||||
|
s := NewServer(impl, nil)
|
||||||
for _, tn := range test.tools {
|
for _, tn := range test.tools {
|
||||||
switch tn {
|
switch tn {
|
||||||
case "greet":
|
case "greet":
|
||||||
|
|
@ -148,6 +175,12 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
||||||
Name: "greet",
|
Name: "greet",
|
||||||
Description: "say hi",
|
Description: "say hi",
|
||||||
}, sayHi)
|
}, sayHi)
|
||||||
|
case "greetWithIcon":
|
||||||
|
AddTool(s, &Tool{
|
||||||
|
Name: "greetWithIcon",
|
||||||
|
Description: "say hi",
|
||||||
|
Icons: []Icon{iconObj},
|
||||||
|
}, sayHi)
|
||||||
case "structured":
|
case "structured":
|
||||||
AddTool(s, &Tool{Name: "structured"}, structuredTool)
|
AddTool(s, &Tool{Name: "structured"}, structuredTool)
|
||||||
case "tomorrow":
|
case "tomorrow":
|
||||||
|
|
@ -159,6 +192,12 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
||||||
}
|
}
|
||||||
inSchema.Properties["x"].Default = json.RawMessage(`6`)
|
inSchema.Properties["x"].Default = json.RawMessage(`6`)
|
||||||
AddTool(s, &Tool{Name: "inc", InputSchema: inSchema}, incTool)
|
AddTool(s, &Tool{Name: "inc", InputSchema: inSchema}, incTool)
|
||||||
|
case "contentTool":
|
||||||
|
AddTool(s, &Tool{
|
||||||
|
Name: "contentTool",
|
||||||
|
Title: "contentTool",
|
||||||
|
Description: "return resourceLink content with Icon",
|
||||||
|
}, contentTool)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unknown tool %q", tn)
|
t.Fatalf("unknown tool %q", tn)
|
||||||
}
|
}
|
||||||
|
|
@ -167,6 +206,13 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
||||||
switch pn {
|
switch pn {
|
||||||
case "code_review":
|
case "code_review":
|
||||||
s.AddPrompt(codeReviewPrompt, codReviewPromptHandler)
|
s.AddPrompt(codeReviewPrompt, codReviewPromptHandler)
|
||||||
|
case "code_reviewWithIcon":
|
||||||
|
s.AddPrompt(&Prompt{
|
||||||
|
Name: "code_review",
|
||||||
|
Description: "do a code review",
|
||||||
|
Arguments: []*PromptArgument{{Name: "Code", Required: true}},
|
||||||
|
Icons: []Icon{iconObj},
|
||||||
|
}, codReviewPromptHandler)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unknown prompt %q", pn)
|
t.Fatalf("unknown prompt %q", pn)
|
||||||
}
|
}
|
||||||
|
|
@ -177,6 +223,13 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
||||||
s.AddResource(resource1, readHandler)
|
s.AddResource(resource1, readHandler)
|
||||||
case "info":
|
case "info":
|
||||||
s.AddResource(resource3, handleEmbeddedResource)
|
s.AddResource(resource3, handleEmbeddedResource)
|
||||||
|
case "infoWithIcon":
|
||||||
|
s.AddResource(&Resource{
|
||||||
|
Name: "info",
|
||||||
|
MIMEType: "text/plain",
|
||||||
|
URI: "embedded:info",
|
||||||
|
Icons: []Icon{iconObj},
|
||||||
|
}, handleEmbeddedResource)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unknown resource %q", rn)
|
t.Fatalf("unknown resource %q", rn)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,8 @@ type ResourceLink struct {
|
||||||
Size *int64
|
Size *int64
|
||||||
Meta Meta
|
Meta Meta
|
||||||
Annotations *Annotations
|
Annotations *Annotations
|
||||||
|
// Icons for the resource link, if any.
|
||||||
|
Icons []Icon `json:"icons,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ResourceLink) MarshalJSON() ([]byte, error) {
|
func (c *ResourceLink) MarshalJSON() ([]byte, error) {
|
||||||
|
|
@ -143,6 +145,7 @@ func (c *ResourceLink) MarshalJSON() ([]byte, error) {
|
||||||
Size: c.Size,
|
Size: c.Size,
|
||||||
Meta: c.Meta,
|
Meta: c.Meta,
|
||||||
Annotations: c.Annotations,
|
Annotations: c.Annotations,
|
||||||
|
Icons: c.Icons,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,6 +158,7 @@ func (c *ResourceLink) fromWire(wire *wireContent) {
|
||||||
c.Size = wire.Size
|
c.Size = wire.Size
|
||||||
c.Meta = wire.Meta
|
c.Meta = wire.Meta
|
||||||
c.Annotations = wire.Annotations
|
c.Annotations = wire.Annotations
|
||||||
|
c.Icons = wire.Icons
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmbeddedResource contains embedded resources.
|
// EmbeddedResource contains embedded resources.
|
||||||
|
|
@ -237,6 +241,7 @@ type wireContent struct {
|
||||||
Size *int64 `json:"size,omitempty"`
|
Size *int64 `json:"size,omitempty"`
|
||||||
Meta Meta `json:"_meta,omitempty"`
|
Meta Meta `json:"_meta,omitempty"`
|
||||||
Annotations *Annotations `json:"annotations,omitempty"`
|
Annotations *Annotations `json:"annotations,omitempty"`
|
||||||
|
Icons []Icon `json:"icons,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func contentsFromWire(wires []*wireContent, allow map[string]bool) ([]Content, error) {
|
func contentsFromWire(wires []*wireContent, allow map[string]bool) ([]Content, error) {
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,9 @@ func TestContent(t *testing.T) {
|
||||||
Description: "This resource demonstrates all fields",
|
Description: "This resource demonstrates all fields",
|
||||||
MIMEType: "text/plain",
|
MIMEType: "text/plain",
|
||||||
Meta: mcp.Meta{"custom": "metadata"},
|
Meta: mcp.Meta{"custom": "metadata"},
|
||||||
|
Icons: []mcp.Icon{{Source: "foobar", MIMEType: "image/png", Sizes: []string{"48x48"}, Theme: "light"}},
|
||||||
},
|
},
|
||||||
`{"type":"resource_link","mimeType":"text/plain","uri":"https://example.com/resource","name":"Example Resource","title":"A comprehensive example resource","description":"This resource demonstrates all fields","_meta":{"custom":"metadata"}}`,
|
`{"type":"resource_link","mimeType":"text/plain","uri":"https://example.com/resource","name":"Example Resource","title":"A comprehensive example resource","description":"This resource demonstrates all fields","_meta":{"custom":"metadata"},"icons":[{"src":"foobar","mimeType":"image/png","sizes":["48x48"],"theme":"light"}]}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,3 +193,53 @@ func TestEmbeddedResource(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestContentUnmarshal tests that unmarshaling JSON into various Content types
|
||||||
|
// works correctly, including when the Content fields are initially nil.
|
||||||
|
func TestContentUnmarshal(t *testing.T) {
|
||||||
|
valInt64 := int64(24)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
content mcp.Content
|
||||||
|
expectContent mcp.Content
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "ResourceLink",
|
||||||
|
json: `{"type":"resource_link","mimeType":"text/plain","uri":"https://example.com/resource","name":"Example Resource","title":"A comprehensive example resource","description":"This resource demonstrates all fields","_meta":{"custom":"metadata"},"icons":[{"src":"foobar","mimeType":"image/png","sizes":["48x48"],"theme":"light"}], "size":24,"annotations":{"audience":["user","assistant"],"lastModified":"2025-01-12T15:00:58Z","priority":0.5}}`,
|
||||||
|
content: &mcp.ResourceLink{},
|
||||||
|
expectContent: &mcp.ResourceLink{
|
||||||
|
URI: "https://example.com/resource",
|
||||||
|
Name: "Example Resource",
|
||||||
|
Title: "A comprehensive example resource",
|
||||||
|
Description: "This resource demonstrates all fields",
|
||||||
|
MIMEType: "text/plain",
|
||||||
|
// Meta: mcp.Meta{"custom": "metadata"},
|
||||||
|
Size: &valInt64,
|
||||||
|
Annotations: &mcp.Annotations{Audience: []mcp.Role{"user", "assistant"}, LastModified: "2025-01-12T15:00:58Z", Priority: 0.5},
|
||||||
|
Icons: []mcp.Icon{{Source: "foobar", MIMEType: "image/png", Sizes: []string{"48x48"}, Theme: "light"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Test that unmarshaling doesn't panic on nil Content fields
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Errorf("UnmarshalJSON panicked: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(tt.json), tt.content)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalJSON failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the Content field was properly populated
|
||||||
|
if cmp.Diff(tt.expectContent, tt.content) != "" {
|
||||||
|
t.Errorf("Content is not equal: %v", cmp.Diff(tt.expectContent, tt.content))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -658,6 +658,23 @@ type ProgressNotificationParams struct {
|
||||||
|
|
||||||
func (*ProgressNotificationParams) isParams() {}
|
func (*ProgressNotificationParams) isParams() {}
|
||||||
|
|
||||||
|
// Icon provides visual identifiers for their resources, tools, prompts, and implementations
|
||||||
|
// See [/specification/draft/basic/index#icons] for notes on icons
|
||||||
|
//
|
||||||
|
// TODO(iamsurajbobade): update specification url from draft.
|
||||||
|
type Icon struct {
|
||||||
|
// Source is A URI pointing to the icon resource (required). This can be:
|
||||||
|
// - An HTTP/HTTPS URL pointing to an image file
|
||||||
|
// - A data URI with base64-encoded image data
|
||||||
|
Source string `json:"src"`
|
||||||
|
// Optional MIME type if the server's type is missing or generic
|
||||||
|
MIMEType string `json:"mimeType,omitempty"`
|
||||||
|
// Optional size specification (e.g., ["48x48"], ["any"] for scalable formats like SVG, or ["48x48", "96x96"] for multiple sizes)
|
||||||
|
Sizes []string `json:"sizes,omitempty"`
|
||||||
|
// Optional Theme of the icon, e.g., "light" or "dark"
|
||||||
|
Theme string `json:"theme,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// A prompt or prompt template that the server offers.
|
// A prompt or prompt template that the server offers.
|
||||||
type Prompt struct {
|
type Prompt struct {
|
||||||
// See [specification/2025-06-18/basic/index#general-fields] for notes on _meta
|
// See [specification/2025-06-18/basic/index#general-fields] for notes on _meta
|
||||||
|
|
@ -673,6 +690,8 @@ type Prompt struct {
|
||||||
// Intended for UI and end-user contexts — optimized to be human-readable and
|
// Intended for UI and end-user contexts — optimized to be human-readable and
|
||||||
// easily understood, even by those unfamiliar with domain-specific terminology.
|
// easily understood, even by those unfamiliar with domain-specific terminology.
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
|
// Icons for the prompt, if any.
|
||||||
|
Icons []Icon `json:"icons,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describes an argument that a prompt can accept.
|
// Describes an argument that a prompt can accept.
|
||||||
|
|
@ -782,6 +801,8 @@ type Resource struct {
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
// The URI of this resource.
|
// The URI of this resource.
|
||||||
URI string `json:"uri"`
|
URI string `json:"uri"`
|
||||||
|
// Icons for the resource, if any.
|
||||||
|
Icons []Icon `json:"icons,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourceListChangedParams struct {
|
type ResourceListChangedParams struct {
|
||||||
|
|
@ -822,6 +843,8 @@ type ResourceTemplate struct {
|
||||||
// A URI template (according to RFC 6570) that can be used to construct resource
|
// A URI template (according to RFC 6570) that can be used to construct resource
|
||||||
// URIs.
|
// URIs.
|
||||||
URITemplate string `json:"uriTemplate"`
|
URITemplate string `json:"uriTemplate"`
|
||||||
|
// Icons for the resource template, if any.
|
||||||
|
Icons []Icon `json:"icons,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The sender or recipient of messages and data in a conversation.
|
// The sender or recipient of messages and data in a conversation.
|
||||||
|
|
@ -948,6 +971,8 @@ type Tool struct {
|
||||||
// If not provided, Annotations.Title should be used for display if present,
|
// If not provided, Annotations.Title should be used for display if present,
|
||||||
// otherwise Name.
|
// otherwise Name.
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
|
// Icons for the tool, if any.
|
||||||
|
Icons []Icon `json:"icons,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional properties describing a Tool to clients.
|
// Additional properties describing a Tool to clients.
|
||||||
|
|
@ -1090,6 +1115,10 @@ type Implementation struct {
|
||||||
// easily understood, even by those unfamiliar with domain-specific terminology.
|
// easily understood, even by those unfamiliar with domain-specific terminology.
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
// WebsiteURL for the server, if any.
|
||||||
|
WebsiteURL string `json:"websiteUrl,omitempty"`
|
||||||
|
// Icons for the Server, if any.
|
||||||
|
Icons []Icon `json:"icons,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present if the server supports argument autocompletion suggestions.
|
// Present if the server supports argument autocompletion suggestions.
|
||||||
|
|
|
||||||
216
mcp/testdata/conformance/server/spec-sep-973-additional-metadata.txtar
vendored
Normal file
216
mcp/testdata/conformance/server/spec-sep-973-additional-metadata.txtar
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
Check behavior of server with Icons and websiteUrl metadata added as part of SEP-973 specification.
|
||||||
|
|
||||||
|
check modelcontextprotocol/go-sdk/issues/552 for more details.
|
||||||
|
|
||||||
|
Checks following:
|
||||||
|
- Test setting websiteUrl, icons for mcp.Implementation
|
||||||
|
- Test setting icons for mcp.Prompt
|
||||||
|
- Test setting icons for mcp.Tool
|
||||||
|
- Test setting icons for mcp.Resource
|
||||||
|
- Test if tool call returning ResourceLink with icons works as expected
|
||||||
|
|
||||||
|
|
||||||
|
-- tools --
|
||||||
|
greetWithIcon
|
||||||
|
contentTool
|
||||||
|
|
||||||
|
-- prompts --
|
||||||
|
code_reviewWithIcon
|
||||||
|
|
||||||
|
-- resources --
|
||||||
|
infoWithIcon
|
||||||
|
|
||||||
|
-- client --
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "initialize",
|
||||||
|
"params": {
|
||||||
|
"protocolVersion": "2025-06-18",
|
||||||
|
"capabilities": {},
|
||||||
|
"clientInfo": { "name": "ExampleClient", "version": "1.0.0" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ "jsonrpc": "2.0", "method": "notifications/initialized" }
|
||||||
|
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
|
||||||
|
{ "jsonrpc": "2.0", "id": 3, "method": "resources/list" }
|
||||||
|
{ "jsonrpc": "2.0", "id": 4, "method": "prompts/list" }
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 5,
|
||||||
|
"method": "tools/call",
|
||||||
|
"params": {
|
||||||
|
"name": "contentTool",
|
||||||
|
"arguments": {
|
||||||
|
"In": "resource1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- server --
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"result": {
|
||||||
|
"capabilities": {
|
||||||
|
"logging": {},
|
||||||
|
"prompts": {
|
||||||
|
"listChanged": true
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"listChanged": true
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"listChanged": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"protocolVersion": "2025-06-18",
|
||||||
|
"serverInfo": {
|
||||||
|
"name": "testServer",
|
||||||
|
"version": "v1.0.0",
|
||||||
|
"websiteUrl": "https://github.com/modelcontextprotocol/go-sdk",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "foobar",
|
||||||
|
"mimeType": "image/png",
|
||||||
|
"sizes": [
|
||||||
|
"48x48",
|
||||||
|
"96x96"
|
||||||
|
],
|
||||||
|
"theme": "light"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"result": {
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"description": "return resourceLink content with Icon",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"In"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"In": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "the input"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"name": "contentTool",
|
||||||
|
"title": "contentTool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "say hi",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"Name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"name": "greetWithIcon",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "foobar",
|
||||||
|
"mimeType": "image/png",
|
||||||
|
"sizes": [
|
||||||
|
"48x48",
|
||||||
|
"96x96"
|
||||||
|
],
|
||||||
|
"theme": "light"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 3,
|
||||||
|
"result": {
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"name": "info",
|
||||||
|
"uri": "embedded:info",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "foobar",
|
||||||
|
"mimeType": "image/png",
|
||||||
|
"sizes": [
|
||||||
|
"48x48",
|
||||||
|
"96x96"
|
||||||
|
],
|
||||||
|
"theme": "light"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 4,
|
||||||
|
"result": {
|
||||||
|
"prompts": [
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"name": "Code",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "do a code review",
|
||||||
|
"name": "code_review",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "foobar",
|
||||||
|
"mimeType": "image/png",
|
||||||
|
"sizes": [
|
||||||
|
"48x48",
|
||||||
|
"96x96"
|
||||||
|
],
|
||||||
|
"theme": "light"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 5,
|
||||||
|
"result": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "resource_link",
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"uri": "https://example.com/resource/resource1",
|
||||||
|
"name": "Example Resource Link with Icons",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "foobar",
|
||||||
|
"mimeType": "image/png",
|
||||||
|
"sizes": [
|
||||||
|
"48x48",
|
||||||
|
"96x96"
|
||||||
|
],
|
||||||
|
"theme": "light"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue