Support structuredContent in tool response (#147)

This commit is contained in:
Anandaroop Roy 2025-09-27 02:23:41 -04:00 • committed by GitHub
commit f7cf080cc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 75 additions and 3 deletions

View file

@ -555,6 +555,38 @@ MCP spec for the [Output Schema](https://modelcontextprotocol.io/specification/2
The output schema follows standard JSON Schema format and helps ensure consistent data exchange between MCP servers and clients. The output schema follows standard JSON Schema format and helps ensure consistent data exchange between MCP servers and clients.
### Tool Responses with Structured Content
Tools can return structured data alongside text content using the `structured_content` parameter.
The structured content will be included in the JSON-RPC response as the `structuredContent` field.
```ruby
class APITool < MCP::Tool
description "Get current weather and return structured data"
def self.call(endpoint:, server_context:)
# Call weather API and structure the response
api_response = WeatherAPI.fetch(location, units)
weather_data = {
temperature: api_response.temp,
condition: api_response.description,
humidity: api_response.humidity_percent
}
output_schema.validate_result(weather_data)
MCP::Tool::Response.new(
[{
type: "text",
text: weather_data.to_json
}],
structured_content: weather_data
)
end
end
```
### Prompts ### Prompts
MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. MCP spec includes [Prompts](https://modelcontextprotocol.io/specification/2025-06-18/server/prompts), which enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs.

View file

@ -5,9 +5,9 @@ module MCP
class Response class Response
NOT_GIVEN = Object.new.freeze NOT_GIVEN = Object.new.freeze
attr_reader :content attr_reader :content, :structured_content
def initialize(content, deprecated_error = NOT_GIVEN, error: false) def initialize(content = nil, deprecated_error = NOT_GIVEN, error: false, structured_content: nil)
if deprecated_error != NOT_GIVEN 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) 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 error = deprecated_error
@ -15,6 +15,7 @@ module MCP
@content = content @content = content
@error = error @error = error
@structured_content = structured_content
end end
def error? def error?
@ -22,7 +23,7 @@ module MCP
end end
def to_h def to_h
{ content:, isError: error? }.compact { content:, isError: error?, structuredContent: @structured_content }.compact
end end
end end
end end

View file

@ -38,6 +38,19 @@ module MCP
refute response.error? refute response.error?
end end
test "#initialize with content and structuredContent" do
content = [{
type: "text",
text: "{\"code\":401,\"message\":\"Unauthorized\"}",
}]
structured_content = { code: 401, message: "Unauthorized" }
response = Response.new(content, structured_content: structured_content)
assert_equal content, response.content
assert_equal structured_content, response.structured_content
refute response.error?
end
test "#error? for a standard response" do test "#error? for a standard response" do
response = Response.new(nil, error: false) response = Response.new(nil, error: false)
refute response.error? refute response.error?
@ -72,6 +85,32 @@ module MCP
assert_equal content, actual[:content] assert_equal content, actual[:content]
assert actual[:isError] assert actual[:isError]
end end
test "#to_h for a standard response with content and structured content" do
content = [{
type: "text",
text: "{\"code\":401,\"message\":\"Unauthorized\"}",
}]
structured_content = { code: 401, message: "Unauthorized" }
response = Response.new(content, structured_content: structured_content)
actual = response.to_h
assert_equal [:content, :isError, :structuredContent].sort, actual.keys.sort
assert_equal content, actual[:content]
assert_equal structured_content, actual[:structuredContent]
refute actual[:isError]
end
test "#to_h for a standard response with structured content only" do
structured_content = { code: 401, message: "Unauthorized" }
response = Response.new(structured_content: structured_content)
actual = response.to_h
assert_equal [:isError, :structuredContent].sort, actual.keys.sort
assert_nil actual[:content]
assert_equal structured_content, actual[:structuredContent]
refute actual[:isError]
end
end end
end end
end end