plugins/turul-mcp-skills/skills/output-schemas/SKILL.md
This skill should be used when the user asks about "output schema", "outputSchema", "structuredContent", "schemars", "JsonSchema derive", "output_field", "output = Type", "Vec output", "tool returns a struct", "output type", or "schema shows inputs not outputs". Covers the required output = Type attribute on derive macros, automatic schemars detection, Vec<T> output patterns, output_field customization, and structuredContent auto-generation in the Turul MCP Framework (Rust).
npx skillsauth add aussierobots/turul-mcp-framework output-schemasInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
MCP tools can declare an output schema so clients know the shape of the result. The framework auto-generates structuredContent when an output schema exists — never create it manually.
output = Type on Derive MacrosProblem: Your tool's tools/list response shows the input parameters as the output schema instead of the actual return type.
Cause: Derive macros operate on the struct definition at compile time. They cannot inspect the execute method's return type.
Fix: Add the output attribute:
// WRONG — schema shows {a: number, b: number} as output
#[derive(McpTool)]
#[tool(name = "calc", description = "Calculate")]
struct Calc { a: f64, b: f64 }
// CORRECT — schema shows {sum: number}
#[derive(McpTool)]
#[tool(name = "calc", description = "Calculate", output = CalcResult)]
struct Calc { a: f64, b: f64 }
Function macros (#[mcp_tool]) do NOT need this — they auto-detect the return type.
See: CLAUDE.md — Output Types and Schemas
When your output type derives schemars::JsonSchema, the framework automatically generates a detailed JSON schema including nested objects, arrays, and optional fields:
use schemars::JsonSchema;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CalculationResult {
/// The result of the calculation
pub value: f64,
/// The operation that was performed
pub operation: String,
}
#[mcp_tool]): Automatically detected from the return type. If the return type derives JsonSchema, the detailed schema is used.#[derive(McpTool)]): Detected from the output = Type attribute. The type must derive JsonSchema.No additional flags or attributes are needed — just derive JsonSchema on your output type.
For schemars to work, your output type needs:
#[derive(
Debug, // Standard
Clone, // Standard
serde::Serialize, // Required for JSON serialization
serde::Deserialize, // Required for JSON deserialization
schemars::JsonSchema, // Enables detailed schema generation
)]
struct MyOutput {
pub value: f64,
}
See: references/schemars-integration.md for advanced schemars patterns.
Do NOT return bare Vec<T> from tools. Wrap arrays in a response struct:
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SearchResult {
pub title: String,
pub score: f64,
}
// RECOMMENDED: Wrapper struct with Vec<T> field
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SearchResponse {
/// The matching results
pub results: Vec<SearchResult>,
/// Optional pagination cursor
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
// Derive macro: output = SearchResponse (NOT Vec<SearchResult>)
#[derive(McpTool, Default)]
#[tool(
name = "search",
description = "Search items",
output = SearchResponse
)]
struct SearchTool {
#[param(description = "Search query")]
query: String,
}
// Function macro: return SearchResponse
#[mcp_tool(name = "search_fn", description = "Search items")]
async fn search(
#[param(description = "Search query")] query: String,
) -> McpResult<SearchResponse> {
Ok(SearchResponse {
results: vec![SearchResult { title: query, score: 1.0 }],
next_cursor: None,
})
}
Bare Vec<T> output has known issues with schemars 1.x:
schema_for!(Vec<T>) generates a root array schema, but ToolSchema::from_schemars() requires type: "object" at root — it rejects array schemas entirely.tools/list can show "type": "object" instead of "array", causing client-side validation failures (FastMCP, MCP Inspector).next_cursor, total_count).Wrapper structs work reliably with all tool patterns (function macro, derive macro, builder).
By default, the tool result is wrapped in {"result": <value>}. Customize with output_field:
// Function macro
#[mcp_tool(
name = "word_count",
description = "Count words",
output_field = "countResult" // Output: {"countResult": 42}
)]
async fn word_count(text: String) -> McpResult<usize> {
Ok(text.split_whitespace().count())
}
The output_field affects the JSON key name in the structuredContent response.
The MCP 2025-11-25 spec requires that tools with outputSchema provide structuredContent in the response. The framework handles this automatically:
outputSchema (via output = Type, schemars, or builder schema methods), the framework generates structuredContent from your return value.execute — the framework serializes it into both content (text) and structuredContent (typed JSON).structuredContent yourself in handler code.See: CLAUDE.md — MCP Tool Output Compliance
| Scenario | Pattern | output Attribute | Schemars |
|---|---|---|---|
| Simple f64/String return | Function macro | Not needed | Optional |
| Custom struct return (fn macro) | Function macro | Not needed | Recommended |
| Custom struct return (derive) | Derive macro | Required | Recommended |
| Array return | Any | Use wrapper struct (e.g., SearchResponse) | Recommended |
| Dynamic/runtime | Builder | .custom_output_schema() | N/A |
Array returns: Always wrap Vec<T> in a response struct. Bare Vec<T> output has schemars 1.x compatibility issues. See Vec<T> Output above.
Which tool pattern to use? → See the tool-creation-patterns skill for choosing between function macro, derive, and builder.
Server configuration? Use McpServer::builder(). See: CLAUDE.md — Basic Server
Release validation of schemas? Run cargo test -p turul-mcp-derive schemars_integration_test and cargo test --test schema_tests mcp_vec_result_schema_test. See: AGENTS.md — Release Readiness Notes
tools
This skill should be used when the user asks to "create a tool", "add a tool", "new tool", "which tool pattern", "compare tool patterns", "function macro vs derive", "mcp_tool macro", "#[mcp_tool]", "derive McpTool", "#[derive(McpTool)]", "ToolBuilder", "tool creation", "function macro tool", "server icon", "server branding", ".icons()", "Icon::data_uri", "server identity", "dynamic tools", "ToolChangeMode", "activate_tool", "deactivate_tool", "ToolRegistry", "tool_change_mode", or "notifications/tools/list_changed". Covers choosing between function macro (#[mcp_tool]), derive macro (#[derive(McpTool)]), and runtime builder (ToolBuilder) patterns, plus server identity (icons), in the Turul MCP Framework (Rust).
tools
This skill should be used when the user asks about "testing", "test patterns", "write tests", "unit test", "e2e test", "integration test", "McpTestClient", "TestServerManager", "compliance test", "test server", "test fixture", "doctest", "cargo test", "test organization", "SSE testing", or "test consolidation". Covers unit testing, E2E testing, compliance testing, SSE testing, and test organization in the Turul MCP Framework (Rust). McpTestClient is the in-process test harness; for the production client API see mcp-client-patterns.
tools
This skill should be used when the user asks about "task support", "TaskRuntime", "TaskStorage", "task_support attribute", "long-running tool", "CancellationHandle", "tasks/get", "tasks/list", "tasks/cancel", "tasks/result", "TaskStatus", "TaskRecord", "TaskOutcome", "InMemoryTaskStorage", "with_task_storage", "task state machine", "TaskExecutor", "TokioTaskExecutor", "task_support = optional", "task_support = required", "task_support = forbidden", "with_task_runtime", or "task storage backend". Covers MCP task support for long-running tools, state machine, storage backends, cancellation, and capability truthfulness in the Turul MCP Framework (Rust). TaskStorage is distinct from SessionStorage; for session persistence see session-storage-backends.
tools
This skill should be used when the user asks about "session storage", "SessionStorage trait", "SqliteSessionStorage", "PostgresSessionStorage", "DynamoDbSessionStorage", "InMemorySessionStorage", "session backend", "session persistence", "session events", "SSE reconnection storage", "which storage backend", "session TTL", "session cleanup", "session event management", "SseEvent", or "SessionStorageError". Covers the SessionStorage trait, backend selection, event management for SSE resumability, error types, and background cleanup in the Turul MCP Framework (Rust). Do NOT use for TaskStorage — task persistence is a separate trait; see task-patterns.