Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions lib/mcp/content.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,60 @@
module MCP
module Content
class Text
attr_reader :text, :annotations
attr_reader :text, :annotations, :meta

def initialize(text, annotations: nil)
def initialize(text, annotations: nil, meta: nil)
@text = text
@annotations = annotations
@meta = meta
end

def to_h
{ text: text, annotations: annotations, type: "text" }.compact
{ text: text, annotations: annotations, _meta: meta, type: "text" }.compact
end
end

class Image
attr_reader :data, :mime_type, :annotations
attr_reader :data, :mime_type, :annotations, :meta

def initialize(data, mime_type, annotations: nil)
def initialize(data, mime_type, annotations: nil, meta: nil)
@data = data
@mime_type = mime_type
@annotations = annotations
@meta = meta
end

def to_h
{ data: data, mimeType: mime_type, annotations: annotations, type: "image" }.compact
{ data: data, mimeType: mime_type, annotations: annotations, _meta: meta, type: "image" }.compact
end
end

class Audio
attr_reader :data, :mime_type, :annotations
attr_reader :data, :mime_type, :annotations, :meta

def initialize(data, mime_type, annotations: nil)
def initialize(data, mime_type, annotations: nil, meta: nil)
@data = data
@mime_type = mime_type
@annotations = annotations
@meta = meta
end

def to_h
{ data: data, mimeType: mime_type, annotations: annotations, type: "audio" }.compact
{ data: data, mimeType: mime_type, annotations: annotations, _meta: meta, type: "audio" }.compact
end
end

class EmbeddedResource
attr_reader :resource, :annotations
attr_reader :resource, :annotations, :meta

def initialize(resource, annotations: nil)
def initialize(resource, annotations: nil, meta: nil)
@resource = resource
@annotations = annotations
@meta = meta
end

def to_h
{ resource: resource.to_h, annotations: annotations, type: "resource" }.compact
{ resource: resource.to_h, annotations: annotations, _meta: meta, type: "resource" }.compact
end
end
end
Expand Down
7 changes: 4 additions & 3 deletions lib/mcp/prompt/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
module MCP
class Prompt
class Result
attr_reader :description, :messages
attr_reader :description, :messages, :meta

def initialize(description: nil, messages: [])
def initialize(description: nil, messages: [], meta: nil)
@description = description
@messages = messages
@meta = meta
end

def to_h
{ description: description, messages: messages.map(&:to_h) }.compact
{ description: description, messages: messages.map(&:to_h), _meta: meta }.compact
end
end
end
Expand Down
6 changes: 4 additions & 2 deletions lib/mcp/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@

module MCP
class Resource
attr_reader :uri, :name, :title, :description, :icons, :mime_type
attr_reader :uri, :name, :title, :description, :icons, :mime_type, :meta

def initialize(uri:, name:, title: nil, description: nil, icons: [], mime_type: nil)
def initialize(uri:, name:, title: nil, description: nil, icons: [], mime_type: nil, meta: nil)
@uri = uri
@name = name
@title = title
@description = description
@icons = icons
@mime_type = mime_type
@meta = meta
end

def to_h
Expand All @@ -24,6 +25,7 @@ def to_h
description: description,
icons: icons&.then { |icons| icons.empty? ? nil : icons.map(&:to_h) },
mimeType: mime_type,
_meta: meta,
}.compact
end
end
Expand Down
15 changes: 8 additions & 7 deletions lib/mcp/resource/contents.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@
module MCP
class Resource
class Contents
attr_reader :uri, :mime_type
attr_reader :uri, :mime_type, :meta

def initialize(uri:, mime_type: nil)
def initialize(uri:, mime_type: nil, meta: nil)
@uri = uri
@mime_type = mime_type
@meta = meta
end

def to_h
{ uri: uri, mimeType: mime_type }.compact
{ uri: uri, mimeType: mime_type, _meta: meta }.compact
end
end

class TextContents < Contents
attr_reader :text

def initialize(text:, uri:, mime_type:)
super(uri: uri, mime_type: mime_type)
def initialize(text:, uri:, mime_type:, meta: nil)
super(uri: uri, mime_type: mime_type, meta: meta)
@text = text
end

Expand All @@ -31,8 +32,8 @@ def to_h
class BlobContents < Contents
attr_reader :data

def initialize(data:, uri:, mime_type:)
super(uri: uri, mime_type: mime_type)
def initialize(data:, uri:, mime_type:, meta: nil)
super(uri: uri, mime_type: mime_type, meta: meta)
@data = data
end

Expand Down
6 changes: 4 additions & 2 deletions lib/mcp/resource_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

module MCP
class ResourceTemplate
attr_reader :uri_template, :name, :title, :description, :icons, :mime_type
attr_reader :uri_template, :name, :title, :description, :icons, :mime_type, :meta

def initialize(uri_template:, name:, title: nil, description: nil, icons: [], mime_type: nil)
def initialize(uri_template:, name:, title: nil, description: nil, icons: [], mime_type: nil, meta: nil)
@uri_template = uri_template
@name = name
@title = title
@description = description
@icons = icons
@mime_type = mime_type
@meta = meta
end

def to_h
Expand All @@ -21,6 +22,7 @@ def to_h
description: description,
icons: icons&.then { |icons| icons.empty? ? nil : icons.map(&:to_h) },
mimeType: mime_type,
_meta: meta,
}.compact
end
end
Expand Down
7 changes: 4 additions & 3 deletions lib/mcp/tool/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ class Tool
class Response
NOT_GIVEN = Object.new.freeze

attr_reader :content, :structured_content
attr_reader :content, :structured_content, :meta

def initialize(content = nil, deprecated_error = NOT_GIVEN, error: false, structured_content: nil)
def initialize(content = nil, deprecated_error = NOT_GIVEN, error: false, structured_content: nil, meta: nil)
if deprecated_error != NOT_GIVEN
warn("Passing `error` with the 2nd argument of `Response.new` is deprecated. Use keyword argument like `Response.new(content, error: error)` instead.", uplevel: 1)
error = deprecated_error
Expand All @@ -16,14 +16,15 @@ def initialize(content = nil, deprecated_error = NOT_GIVEN, error: false, struct
@content = content || []
@error = error
@structured_content = structured_content
@meta = meta
end

def error?
!!@error
end

def to_h
{ content: content, isError: error?, structuredContent: @structured_content }.compact
{ content: content, isError: error?, structuredContent: @structured_content, _meta: meta }.compact
end
end
end
Expand Down
72 changes: 72 additions & 0 deletions test/mcp/content_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ class ImageTest < ActiveSupport::TestCase

refute result.key?(:annotations)
end

test "#to_h with meta" do
meta = { "application/vnd.ant.mcp-app" => { "csp" => "default-src 'self'" } }
image = Image.new("base64data", "image/png", meta: meta)

assert_equal meta, image.to_h[:_meta]
end

test "#to_h without meta omits the key" do
image = Image.new("base64data", "image/png")

refute image.to_h.key?(:_meta)
end
end

class AudioTest < ActiveSupport::TestCase
Expand All @@ -53,6 +66,19 @@ class AudioTest < ActiveSupport::TestCase

refute result.key?(:annotations)
end

test "#to_h with meta" do
meta = { "application/vnd.ant.mcp-app" => { "csp" => "default-src 'self'" } }
audio = Audio.new("base64data", "audio/wav", meta: meta)

assert_equal meta, audio.to_h[:_meta]
end

test "#to_h without meta omits the key" do
audio = Audio.new("base64data", "audio/wav")

refute audio.to_h.key?(:_meta)
end
end

class EmbeddedResourceTest < ActiveSupport::TestCase
Expand Down Expand Up @@ -92,6 +118,52 @@ def resource.to_h

refute result.key?(:annotations)
end

test "#to_h with meta" do
resource = Object.new
def resource.to_h
{ uri: "test://x" }
end

meta = { "application/vnd.ant.mcp-app" => { "csp" => "default-src 'self'" } }
embedded = EmbeddedResource.new(resource, meta: meta)

assert_equal meta, embedded.to_h[:_meta]
end

test "#to_h without meta omits the key" do
resource = Object.new
def resource.to_h
{ uri: "test://x" }
end

embedded = EmbeddedResource.new(resource)

refute embedded.to_h.key?(:_meta)
end
end

class TextTest < ActiveSupport::TestCase
test "#to_h returns correct format per MCP spec" do
text = Text.new("hello")
result = text.to_h

assert_equal "text", result[:type]
assert_equal "hello", result[:text]
end

test "#to_h with meta" do
meta = { "application/vnd.ant.mcp-app" => { "csp" => "default-src 'self'" } }
text = Text.new("hello", meta: meta)

assert_equal meta, text.to_h[:_meta]
end

test "#to_h without meta omits the key" do
text = Text.new("hello")

refute text.to_h.key?(:_meta)
end
end
end
end
41 changes: 41 additions & 0 deletions test/mcp/prompt/result_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

require "test_helper"

module MCP
class Prompt
class ResultTest < ActiveSupport::TestCase
test "#to_h returns description and messages" do
result = Prompt::Result.new(
description: "a prompt",
messages: [Prompt::Message.new(role: "user", content: Content::Text.new("hi"))],
)

hash = result.to_h

assert_equal "a prompt", hash[:description]
assert_equal 1, hash[:messages].size
end

test "#to_h includes _meta when present" do
meta = { "application/vnd.ant.mcp-app" => { "csp" => "default-src 'self'" } }
result = Prompt::Result.new(
description: "a prompt",
messages: [Prompt::Message.new(role: "user", content: Content::Text.new("hi"))],
meta: meta,
)

assert_equal meta, result.to_h[:_meta]
end

test "#to_h omits _meta when nil" do
result = Prompt::Result.new(
description: "a prompt",
messages: [Prompt::Message.new(role: "user", content: Content::Text.new("hi"))],
)

refute result.to_h.key?(:_meta)
end
end
end
end
Loading