Non-object outputSchema and structuredContent are sent to clients that negotiated 2025-06-18 / 2025-11-25, where an object is required
Maintainer thường phản hồi trong vòng 3 ngày
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 28/100
Hướng nghiên cứu
Start with strip_output in crates/rmcp/src/handler/server/common.rs, which always keeps non-object output schemas, and with the version gating for result_type in crates/rmcp/src/handler/server.rs. Check that Json<Vec<T>> advertises an array outputSchema and returns a non-object structuredContent for a session negotiated to 2025-06-18. Done means that session omits both for non-object roots while 2026-07-28 keeps them. The issue lists three options, so confirm with maintainers which one to build first.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
A tool that returns Json<Vec<T>> (or any non-object type) makes the server advertise a non-object outputSchema, and return a non-object structuredContent, to every client, including clients that negotiated protocol 2025-06-18 or 2025-11-25. In those versions both must be objects, so such a client sees a schema that is invalid for the version it asked for.
This is not a request to undo SEP-2106 (#874); allowing arrays and primitives is right for 2026-07-28. The gap is that the choice does not depend on the negotiated version, although other 2026-07-28 behaviour in handler/server.rs is gated on it.
What the spec versions say
2025-06-18,schema.ts:outputSchema?: { type: "object"; properties?: ...; required?: string[] }2025-11-25,schema.ts:outputSchema?: { $schema?: string; type: "object"; ... }, andstructuredContent?: { [key: string]: unknown }2026-07-28(SEP-2106): any JSON Schema 2020-12 schema, any JSON value.
crates/rmcp/src/handler/server/common.rs (strip_output) says output schemas "are not restricted to type: "object" (per SEP-2106)" unconditionally.
Reproduction
rmcp 3.5.1, features = ["server", "macros", "transport-io"].
use rmcp::handler::server::router::tool::ToolRouter;
use rmcp::model::{Implementation, ServerCapabilities, ServerConfig};
use rmcp::{tool, tool_handler, tool_router, Json, ServerHandler, ServiceExt};
#[derive(Clone)]
struct Server { tool_router: ToolRouter<Self> }
#[tool_router]
impl Server {
#[tool(description = "Returns a list of names")]
async fn names(&self) -> Json<Vec<String>> {
Json(vec!["a".to_string(), "b".to_string()])
}
}
#[tool_handler]
impl ServerHandler for Server {
fn get_info(&self) -> ServerConfig {
ServerConfig::new(ServerCapabilities::builder().enable_tools().build())
.with_server_info(Implementation::new("repro", "0.1.0"))
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let server = Server { tool_router: Server::tool_router() };
server.serve(rmcp::transport::stdio()).await.unwrap().waiting().await.unwrap();
}
A client that asks for 2025-06-18:
{ printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"names","arguments":{}}}'; sleep 1; } | ./target/debug/repro
Output (trimmed):
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18", ...}}
{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"names", ...,
"outputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","items":{"type":"string"},"type":"array"}}]}}
{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"[\"a\",\"b\"]"}],"structuredContent":["a","b"],"isError":false}}
The server answers protocolVersion: 2025-06-18 and then advertises outputSchema.type: "array" and returns structuredContent: ["a","b"]. The same happens when the client asks for 2025-11-25. When I asked for 2026-07-28 in initialize, the server answered 2025-11-25 and still advertised the array schema.
Why it matters
A client that validates tools/list against the schema of the negotiated version will reject the tool, and some validate the whole list at once, so one tool with a list result can make every tool unavailable. We hit this shipping an MCP server (metamug/plunger#97); the report there was that the output schema was not an object. I have not checked which specific clients reject it, only that the advertised schema is invalid for the version that was negotiated.
The workaround is to wrap every list in a struct (as suggested in #532), which works everywhere, but it is easy to miss because Json<Vec<T>> compiles and works with clients that are lenient or new.
Suggested behaviour
Any of these would help, in rough order of preference:
- For a session that negotiated a version before
2026-07-28, omitoutputSchemawhen its root type is not"object"(the call still works; the result is also incontent), and do the same forstructuredContent. This follows the version gating already used forresult_type. - Offer a wrapper (for example
Json<Wrapped<T>>, or an attribute on#[tool]) that produces{ "result": T }and an object schema in all versions, so authors have a supported way to be compatible without writing a struct for every list. - At minimum, document on
Json<T>and onschema_for_outputthat a non-objectTis only valid for peers on2026-07-28and later.
Related: #532 (the same symptom, closed before SEP-2106), #874 (SEP-2106).
- Ngôn ngữ chính
- Rust
- Star
- 4k
- Fork
- 654
- Merge trung bình
- 3 ngày 4 giờ
- Pull request đã merge (30 ngày)
- 37
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của modelcontextprotocol/rust-sdk
-
bug P2 ready for work T-security T-transport
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
modelcontextprotocol/rust-sdk#1339 ·
Maintainer thường phản hồi trong vòng 3 ngày
-
bug P2 ready for work T-config
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
modelcontextprotocol/rust-sdk#1335 ·
Maintainer thường phản hồi trong vòng 3 ngày
-
auth: refresh_token() requests offline_access that was never granted, so refreshes fail with invalid_scopeCó thể đã có người làm @jstar0 đã nhận 2 ngày trước. Đang mởbug P1 ready for work T-security T-transport
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
modelcontextprotocol/rust-sdk#1330 ·
Maintainer thường phản hồi trong vòng 3 ngày
-
P3 question Stale T-documentation T-enhancement
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 86/100
modelcontextprotocol/rust-sdk#1155 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 3 ngày
-
enhancement P2 ready for work T-service
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
modelcontextprotocol/rust-sdk#1338 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 3 ngày
Tất cả issue của modelcontextprotocol/rust-sdk
Issue tương tự
-
C-bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
rust-lang/rust-analyzer#23501 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
French BIP39 wordlist starts with a UTF-8 BOM, so generated French mnemonics carry U+FEFF and derive a non-canonical seedCó thể đã có người làm @Kshot3000 đã nhận hôm nay. Đang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 91/100
ergoplatform/sigma-rust#976 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug]: Web chat input doesn't regain focus after a reply finishesCó thể đã có người làm @GaijinSystems đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
zeroclaw-labs/zeroclaw#11658 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
good first issue help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100