mcp: add ResourceLink type (#76)
This PR introduces the ResourceLink type to represent links to resources. The new type includes the following fields: `uri`, `name`, `title`, `description`, `mimeType`, `size`, and `_meta`. This implementation conforms to the `ResourceLink` schema introduced in the [MCP schema 2025-06-18](1cfdf1e7a8/schema/2025-06-18/schema.json (L1826)). Note: `ResourceLink` extends from the `Resource` type in the MCP schema. As such, it inherits support for optional fields like `title`, `_meta`, etc. Thus, `URI`, `Name`, `Title`, `Description`, and `Size` are added to the `wireContent` For reference, the [TypeScript SDK test suite](fb4d07f0ee/src/types.test.ts (L43)) provides examples demonstrating valid ResourceLink objects with all optional fields populated. https://modelcontextprotocol.io/specification/2025-06-18/server/tools#resource-links - [ResourceLink JSON Schema](1cfdf1e7a8/schema/2025-06-18/schema.json (L1826)) Signed-off-by: Xin Fu <xfu83@bloomberg.net> Fixes #14.
This commit is contained in:
parent
95e96a6daa
commit
92b6983c1c
2 changed files with 69 additions and 7 deletions
|
|
@ -10,10 +10,8 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
// A Content is a [TextContent], [ImageContent], [AudioContent] or
|
||||
// [EmbeddedResource].
|
||||
//
|
||||
// TODO(rfindley): add ResourceLink.
|
||||
// A Content is a [TextContent], [ImageContent], [AudioContent],
|
||||
// [ResourceLink], or [EmbeddedResource].
|
||||
type Content interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
fromWire(*wireContent)
|
||||
|
|
@ -91,6 +89,43 @@ func (c *AudioContent) fromWire(wire *wireContent) {
|
|||
c.Annotations = wire.Annotations
|
||||
}
|
||||
|
||||
// ResourceLink is a link to a resource
|
||||
type ResourceLink struct {
|
||||
URI string
|
||||
Name string
|
||||
Title string
|
||||
Description string
|
||||
MIMEType string
|
||||
Size *int64
|
||||
Meta Meta
|
||||
Annotations *Annotations
|
||||
}
|
||||
|
||||
func (c *ResourceLink) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&wireContent{
|
||||
Type: "resource_link",
|
||||
URI: c.URI,
|
||||
Name: c.Name,
|
||||
Title: c.Title,
|
||||
Description: c.Description,
|
||||
MIMEType: c.MIMEType,
|
||||
Size: c.Size,
|
||||
Meta: c.Meta,
|
||||
Annotations: c.Annotations,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ResourceLink) fromWire(wire *wireContent) {
|
||||
c.URI = wire.URI
|
||||
c.Name = wire.Name
|
||||
c.Title = wire.Title
|
||||
c.Description = wire.Description
|
||||
c.MIMEType = wire.MIMEType
|
||||
c.Size = wire.Size
|
||||
c.Meta = wire.Meta
|
||||
c.Annotations = wire.Annotations
|
||||
}
|
||||
|
||||
// EmbeddedResource contains embedded resources.
|
||||
type EmbeddedResource struct {
|
||||
Resource *ResourceContents
|
||||
|
|
@ -151,17 +186,22 @@ func (r ResourceContents) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
|
||||
// wireContent is the wire format for content.
|
||||
// It represents the protocol types TextContent, ImageContent, AudioContent
|
||||
// and EmbeddedResource.
|
||||
// It represents the protocol types TextContent, ImageContent, AudioContent,
|
||||
// ResourceLink, and EmbeddedResource.
|
||||
// The Type field distinguishes them. In the protocol, each type has a constant
|
||||
// value for the field.
|
||||
// At most one of Text, Data, and Resource is non-zero.
|
||||
// At most one of Text, Data, Resource, and URI is non-zero.
|
||||
type wireContent struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text,omitempty"`
|
||||
MIMEType string `json:"mimeType,omitempty"`
|
||||
Data []byte `json:"data,omitempty"`
|
||||
Resource *ResourceContents `json:"resource,omitempty"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Size *int64 `json:"size,omitempty"`
|
||||
Meta Meta `json:"_meta,omitempty"`
|
||||
Annotations *Annotations `json:"annotations,omitempty"`
|
||||
}
|
||||
|
|
@ -195,6 +235,10 @@ func contentFromWire(wire *wireContent, allow map[string]bool) (Content, error)
|
|||
v := new(AudioContent)
|
||||
v.fromWire(wire)
|
||||
return v, nil
|
||||
case "resource_link":
|
||||
v := new(ResourceLink)
|
||||
v.fromWire(wire)
|
||||
return v, nil
|
||||
case "resource":
|
||||
v := new(EmbeddedResource)
|
||||
v.fromWire(wire)
|
||||
|
|
|
|||
|
|
@ -82,6 +82,24 @@ func TestContent(t *testing.T) {
|
|||
},
|
||||
`{"type":"resource","resource":{"uri":"file://foo","mimeType":"text","text":"abc"},"_meta":{"key":"value"},"annotations":{"priority":1}}`,
|
||||
},
|
||||
{
|
||||
&mcp.ResourceLink{
|
||||
URI: "file:///path/to/file.txt",
|
||||
Name: "file.txt",
|
||||
},
|
||||
`{"type":"resource_link","uri":"file:///path/to/file.txt","name":"file.txt"}`,
|
||||
},
|
||||
{
|
||||
&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"},
|
||||
},
|
||||
`{"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"}}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
|||
Loading…
Reference in a new issue