Skip to content
Open
41 changes: 34 additions & 7 deletions examples/server/everything/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package main

import (
"context"
"encoding/base64"
"flag"
"fmt"
"log"
Expand Down Expand Up @@ -50,26 +51,40 @@ func main() {
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.
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: "ping"}, pingingTool) // performs a ping
mcp.AddTool(server, &mcp.Tool{Name: "log"}, loggingTool) // performs a log
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
mcp.AddTool(server, &mcp.Tool{Name: "greet (structured)"}, structuredTool) // returns structured output
mcp.AddTool(server, &mcp.Tool{Name: "greet (with Icons)", Icons: icons}, structuredTool) // tool with icons
mcp.AddTool(server, &mcp.Tool{Name: "ping"}, pingingTool) // performs a ping
mcp.AddTool(server, &mcp.Tool{Name: "log"}, loggingTool) // performs a log
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.
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.
server.AddResource(&mcp.Resource{
Name: "info",
MIMEType: "text/plain",
URI: "embedded:info",
}, embeddedResource)
server.AddResource(&mcp.Resource{ // text resource with icons
Name: "info (with Icons)",
MIMEType: "text/plain",
URI: "embedded:info",
Icons: icons,
}, embeddedResource)

// Serve over stdio, or streamable HTTP if -http is set.
if *httpAddr != "" {
Expand Down Expand Up @@ -222,3 +237,15 @@ func complete(ctx context.Context, req *mcp.CompleteRequest) (*mcp.CompleteResul
},
}, 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"},
}}, nil
}
Binary file added examples/server/everything/mcp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 30 additions & 1 deletion mcp/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,19 +135,34 @@ func incTool(_ context.Context, _ *CallToolRequest, args incInput) (*CallToolRes
return nil, incOutput{args.X + 1}, nil
}

var iconObj = Icon{Source: "foobar", MIMEType: "image/png", Sizes: []string{"48x48", "96x96"}}

// runServerTest runs the server conformance test.
// It must be executed in a synctest bubble.
func runServerTest(t *testing.T, test *conformanceTest) {
ctx := t.Context()
// 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 {
switch tn {
case "greet":
AddTool(s, &Tool{
Name: "greet",
Description: "say hi",
}, sayHi)
case "greetWithIcon":
AddTool(s, &Tool{
Name: "greetWithIcon",
Description: "say hi",
Icons: []Icon{iconObj},
}, sayHi)
case "structured":
AddTool(s, &Tool{Name: "structured"}, structuredTool)
case "tomorrow":
Expand All @@ -167,6 +182,13 @@ func runServerTest(t *testing.T, test *conformanceTest) {
switch pn {
case "code_review":
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:
t.Fatalf("unknown prompt %q", pn)
}
Expand All @@ -177,6 +199,13 @@ func runServerTest(t *testing.T, test *conformanceTest) {
s.AddResource(resource1, readHandler)
case "info":
s.AddResource(resource3, handleEmbeddedResource)
case "infoWithIcon":
s.AddResource(&Resource{
Name: "info",
MIMEType: "text/plain",
URI: "embedded:info",
Icons: []Icon{iconObj},
}, handleEmbeddedResource)
default:
t.Fatalf("unknown resource %q", rn)
}
Expand Down
25 changes: 25 additions & 0 deletions mcp/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,21 @@ type ProgressNotificationParams struct {

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"`
}

// A prompt or prompt template that the server offers.
type Prompt struct {
// See [specification/2025-06-18/basic/index#general-fields] for notes on _meta
Expand All @@ -673,6 +688,8 @@ type Prompt struct {
// Intended for UI and end-user contexts — optimized to be human-readable and
// easily understood, even by those unfamiliar with domain-specific terminology.
Title string `json:"title,omitempty"`
// Icons for the prompt, if any.
Icons []Icon `json:"icons,omitempty"`
}

// Describes an argument that a prompt can accept.
Expand Down Expand Up @@ -782,6 +799,8 @@ type Resource struct {
Title string `json:"title,omitempty"`
// The URI of this resource.
URI string `json:"uri"`
// Icons for the resource, if any.
Icons []Icon `json:"icons,omitempty"`
}

type ResourceListChangedParams struct {
Expand Down Expand Up @@ -948,6 +967,8 @@ type Tool struct {
// If not provided, Annotations.Title should be used for display if present,
// otherwise Name.
Title string `json:"title,omitempty"`
// Icons for the tool, if any.
Icons []Icon `json:"icons,omitempty"`
}

// Additional properties describing a Tool to clients.
Expand Down Expand Up @@ -1090,6 +1111,10 @@ type Implementation struct {
// easily understood, even by those unfamiliar with domain-specific terminology.
Title string `json:"title,omitempty"`
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.
Expand Down
157 changes: 157 additions & 0 deletions mcp/testdata/conformance/server/spec-sep-973-additional-metadata.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
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:
- If client sends protocolVersion as "draft", server responds with same
- 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

-- tools --
greetWithIcon

-- 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" }

-- 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"
]
}
]
}
}
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"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"
]
}
]
}
]
}
}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resources": [
{
"mimeType": "text/plain",
"name": "info",
"uri": "embedded:info",
"icons": [
{
"src": "foobar",
"mimeType": "image/png",
"sizes": [
"48x48",
"96x96"
]
}
]
}
]
}
}
{
"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"
]
}
]
}
]
}
}