Elicitation vs. MCP apps widgets for user input
Elicitation is the standard way to collect structured input from the user, but it is constrained to flat forms. When elicitation's flat-form constraints do not fit the interaction you need, switch to MCP apps, which render interactive UI widgets (forms, pickers, dashboards, charts) inline in the chat.
Missing outputSchema forces generic types in code mode
MCP servers can provide an optional `outputSchema` per tool; when present the host generates precise return types. When absent, prefer the simple path of accepting a generic `any` or `string` and handling unstructured output downstream — the real fix is for server authors to provide `outputSchema`. Alternatively, for single-shot calls outside loops, expose a host-brokered `extract(value, ExpectedType)` helper through the same stub-interception path so the sandbox never opens a network connection; it routes to a small model (e.g. Claude Haiku or Gemini Flash) to coerce the value, but adds latency and can hallucinate or drop fields, so validate against ExpectedType before use.
Tool call examples with named arguments
Tool invocations pass named arguments matching the input schema, for example searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15"), createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22"), and sendEmail(to: "team@work.com", subject: "Out of Office", body: "...").
Tool definition shape with JSON Schema inputSchema
A tool definition has `name`, `description`, and `inputSchema`. MCP uses JSON Schema for validation. Example: { name: "searchFlights", description: "Search for available flights", inputSchema: { type: "object", properties: { origin: { type: "string", description: "Departure city" }, destination: { type: "string", description: "Arrival city" }, date: { type: "string", format: "date", description: "Travel date" } }, required: ["origin", "destination", "date"] } }.
tools/list and tools/call protocol methods
Tools use two protocol operations: `tools/list` discovers available tools and returns an array of tool definitions with schemas; `tools/call` executes a specific tool and returns the tool execution result.
One operation per tool with typed inputs and outputs
Each tool should perform a single operation with clearly defined inputs and outputs. Tools are schema-defined interfaces that LLMs can invoke, and they may require user consent prior to execution so users retain control over actions taken by a model.
logging/setLevel is legacy-era only; modern servers opt in per request
In the Inspector CLI, `logging/setLevel` is available only in the legacy protocol era; modern servers opt into logging per request instead of setting a global level.
Recipe: list every tool that has a UI
To smoke-test tools with an MCP App UI: `mcp-inspector --cli "$URL" --transport http --method tools/list --app-info | jq -r 'select(.hasApp) | .toolName'`.
--app-info probes MCP App UI without calling the tool
`--app-info` reports whether a tool ships an MCP App UI (its `ui://` resource, CSP, and permissions) without calling the tool. With `tools/call --tool-name my_tool --app-info` it emits one JSON line like {"hasApp":true,"toolName":"my_tool","resourceUri":"ui://...","csp":{...},"permissions":{...}}. With `tools/list --app-info` it emits NDJSON, one line per tool, over a single connection.
--tool-arg coerces values, --tool-args-json passes verbatim
`--tool-arg` takes `key=value` and coerces the value by JSON-parsing it, so `count=1` becomes the number 1 and `"012"` becomes the number 12. `--tool-args-json` takes the whole argument object at once and passes it verbatim with no coercion, so `{"zip":"10001"}` keeps `"012"`-style strings intact. The two flags are mutually exclusive.
Modern tasks are an extension (io.modelcontextprotocol/tasks, SEP-2663)
In the modern era tasks are an extension named `io.modelcontextprotocol/tasks` (SEP-2663), so clients gate task UI on the negotiated extension rather than on `capabilities.tasks`. Running a tool as a task makes `tools/call` return a `CreateTaskResult` with `resultType: "task"`. Clients poll `tasks/get` only — there is no `tasks/list`, so refreshing re-polls handles the client already holds. A completed task inlines its result, with no blocking `tasks/result` call.
MRTR shapes to test: elicitation, sampling, roots, requestState
Useful MRTR variants to implement and test: a single elicitation round; two elicitation rounds threaded through `requestState`; an embedded sampling request routed to a sampling handler; an embedded `roots/list` answered silently from configured roots (no user prompt); an `inputRequests`-only round followed by a `requestState`-only round; and a tool that never completes so the client hits its max-rounds limit.
Modern task input_required and tasks/update
A modern task that needs more information moves to state `input_required` and surfaces an embedded elicitation. Answering it sends `tasks/update` carrying `inputResponses`, and the next `tasks/get` poll completes the task.
Multi-round tool results (MRTR) on the modern era
On the modern era a tool can return `input_required` instead of a final result, embedding an elicitation, a sampling request, or a `roots/list` request. The client answers that embedded request and retries the `tools/call` under a fresh JSON-RPC id until the call reaches `complete`. Clients should bound this with a maximum round limit (the Inspector uses `MRTR_MAX_ROUNDS`) because a misbehaving tool can loop forever.
Legacy tasks: capabilities.tasks, tasks/list, blocking tasks/result
In the legacy era, task support is advertised via `capabilities.tasks`. Tasks are enumerated with `tasks/list`, polled with `tasks/get`, the completed payload is fetched with a blocking `tasks/result`, and cancellation uses `tasks/cancel`.
x-mcp-header argument annotation mirrors args into Mcp-Param-* headers
SEP-2243 lets a tool annotate an argument with `x-mcp-header`, asking a Streamable HTTP client to mirror that argument's value into an `Mcp-Param-*` request header — for example argument `city` mirrored into header `Mcp-Param-City`.
MCP Apps are tools that carry a UI widget
MCP Apps are tools that carry a UI widget rendered from a UI resource. For automated review, use the Inspector CLI for every check that returns JSON and open a browser only to inspect the rendered widget.
--app-info probes an app tool without calling it
`mcp-inspector --cli --transport http --server-url <url> --method tools/call --tool-name <tool> --app-info` prints one JSON line on stdout and exits `0` if the tool has an app, `2` if not, so an `&&` chain short-circuits. The tool itself is never called.
Call a tool from the CLI with JSON arguments
Invoke a tool headlessly with `mcp-inspector --cli --transport http --server-url <url> --method tools/call --tool-name <tool> --tool-args-json '{"zip":"10001"}' --format json` to get the full result payload with no browser.
Python @app.tool() declares a tool from a typed async function
In the Python SDK tools are declared by decorating an async function with `@app.tool()`; the parameter type hints (e.g. `a: float, b: float`) form the input schema and the docstring, including an `Args:` section describing each parameter, becomes the tool description. The function can return a plain dict which is serialized as the tool result.
TypeScript registerTool with title, description and Zod inputSchema
Tools are declared with `server.registerTool(name, { title, description, inputSchema }, handler)`. Example: `server.registerTool('add', { title: 'Addition Tool', description: 'Add two numbers together', inputSchema: { a: z.number().describe('First number to add'), b: z.number().describe('Second number to add') } }, async ({ a, b }) => ({ content: [{ type: 'text', text: `${a} + ${b} = ${a + b}` }] }))`. The handler returns an object with a `content` array of typed blocks.
C# tools declared with [McpServerToolType] and [McpServerTool]
In the C# SDK a class is marked `[McpServerToolType]` and each method gets `[McpServerTool, Description("Add two numbers together.")]`, with each parameter annotated `[Description("First operand")]`. Methods may return `Task<double>` and the SDK handles serialization. The tool class is wired up via `.WithTools<MathTools>()`.
Reading tool result text in an MCP App UI
Tool results delivered to an MCP App UI are read as `result.content?.find((c) => c.type === "text")?.text`, i.e. the content array uses items with a `type` discriminator and text items carry a `text` field.
MCP Apps combine a tool and a UI resource
An MCP App is built from two MCP primitives: a tool registered with a `_meta.ui.resourceUri` field, and a resource that serves the app's HTML. When the host calls the tool, it fetches the referenced UI resource, renders it, and passes the tool result to the rendered UI on arrival.
MCP Apps: tool declares UI via _meta.ui.resourceUri
MCP Apps let a tool return an interactive HTML interface. The tool's description includes a `_meta.ui.resourceUri` field pointing to a `ui://` resource. The host can preload that resource before the tool is called, which enables features like streaming tool inputs into the app. The core pattern is two primitives combined: a tool that declares a UI resource, plus a UI resource that renders data as interactive HTML.
Task methods: tasks/get, tasks/update, tasks/cancel
Tasks add three requests: `tasks/get` (poll with the `taskId`; the response carries current status and, for terminal states, the final `result` or `error`), `tasks/update` (submit `inputResponses` keyed to outstanding `inputRequests`), and `tasks/cancel` (client may send at any time; cancellation is cooperative and the server is not obligated to stop the work).
MCP Tasks extension purpose and identifier
MCP Tasks is an extension (not core spec) identified by the string `io.modelcontextprotocol/tasks`, specified in the github.com/modelcontextprotocol/ext-tasks repository. It lets a server return a durable task handle instead of blocking on long-running operations such as CI pipelines, batch processing, or human approvals, so clients can poll for progress, supply mid-flight input, and retrieve the final result after reconnecting.
Tasks message flow sequence
Typical flow: client sends `tools/call` with the tasks capability; server replies with CreateTaskResult (taskId, status working); client loops on `tasks/get` receiving status working; when the server needs input a `tasks/get` returns status `input_required` with `inputRequests`; client sends `tasks/update` with inputResponses and the server acks; client resumes polling until `tasks/get` returns status `completed` with `result`.
Task status values and which are terminal
Task status is one of `working` (in progress), `input_required` (server needs client input; see `inputRequests`), `completed` (`result` field holds the final output), `failed` (`error` field holds the JSON-RPC error), or `cancelled` (cancellation was honored). `completed`, `failed`, and `cancelled` are terminal — once reached the task state does not change.
Primitive method naming: */list, */get, tools/call
Each MCP primitive type has associated methods for discovery (`*/list`), retrieval (`*/get`), and in some cases execution (`tools/call`). Clients use the `*/list` methods (e.g. `tools/list`) to discover available primitives before executing them, which allows listings to be dynamic. Resource support means the server can handle `resources/list` and `resources/read`.
Name tools with a clear namespaced pattern
Tool names should follow a clear naming pattern, for example `calculator_arithmetic` rather than just `calculate`, and `weather_current` rather than `weather`. The name is the unique identifier within the server's namespace and the primary key used for execution.
tools/list response fields per tool
A `tools/list` result contains `resultType`, a `tools` array, and caching fields `ttlMs` (e.g. 300000 = five minutes) and `cacheScope` (e.g. "public"). Each tool object includes `name` (unique identifier within the server namespace, the primary key for execution), `title` (human-readable display name shown to users), `description` (detailed explanation of what it does and when to use it), and `inputSchema` (a JSON Schema object with `properties` and a `required` array defining expected parameters).
Tool schema defaults: optional params fall back to schema default
Optional tool parameters declared in `inputSchema` may specify an `enum` and a `default` (for example a `units` parameter with enum ["metric", "imperial", "kelvin"] and default "metric"). If the client omits the argument, the declared default applies; parameters listed in `required` must be sent.
Three server primitives: tools, resources, prompts
MCP defines three core primitives servers can expose: Tools (executable functions the AI application invokes to perform actions such as file operations, API calls, database queries), Resources (data sources providing contextual information such as file contents, database records, API responses), and Prompts (reusable templates that structure interactions with language models, such as system prompts and few-shot examples).
tools/call request and response format
A tool is executed with method `tools/call` and params containing `name` (must exactly match the name from the discovery response, e.g. "weather_current"), `arguments` (an object matching the tool's inputSchema, e.g. {"location": "San Francisco", "units": "imperial"}), and `_meta`. The result contains `resultType` and a `content` array of objects, each with a `type` field; `"type": "text"` carries plain text in a `text` field. The content array allows rich multi-format responses (text, images, resources).
tools/list takes no params except _meta and optional cursor
The `tools/list` request requires no parameters beyond the standard `_meta` fields that accompany every MCP request. It also accepts an optional `cursor` parameter for pagination.
TypeScript MCP server: McpServer + registerTool with zod inputSchema
In TypeScript, import `{ McpServer } from "@modelcontextprotocol/server"` and `{ StdioServerTransport } from "@modelcontextprotocol/server/stdio"`, then `new McpServer({ name: "weather", version: "1.0.0" })`. Register a tool with `server.registerTool(name, { description, inputSchema: z.object({...}) }, async (args) => ({ content: [{ type: "text", text: "..." }] }))`. Use zod `.describe()` on each field to document parameters and constraints like `.length(2)`, `.min(-90).max(90)`.
Tool input schemas: JSON Schema properties plus required list
Across SDKs a tool's input schema is a JSON Schema object with a `properties` map (each property having `type` such as "string" or "number" and a human-readable `description`) and a `required` array listing mandatory parameter names. Descriptions of parameters matter because they are what the model sees, e.g. "Two-letter US state code (e.g. CA, NY)".
Tool result shape: content array of typed blocks
A tool call result carries a `content` array whose entries have a `type` and payload, e.g. `{ type: "text", text: "..." }`. Error conditions inside a tool are commonly returned as ordinary text content (for example "Failed to retrieve alerts data" or "No active alerts for CA") rather than thrown exceptions.
Go: tool input schema from struct tags with jsonschema
Go SDK tool inputs are structs whose fields carry `json` and `jsonschema` tags that produce the tool's input schema and parameter descriptions, e.g. `type AlertsInput struct { State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"` }` and `type ForecastInput struct { Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"`; Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"` }`.
Rust: tool input schema comes from serde + schemars derive
Rust rmcp tool arguments are plain structs deriving `serde::Deserialize` and `schemars::JsonSchema`, e.g. `#[derive(serde::Deserialize, schemars::JsonSchema)] pub struct MCPForecastRequest { latitude: f32, longitude: f32 }`. The handler receives them wrapped in `Parameters<T>` from `rmcp::handler::server::tool::Parameters`.
Rust: declaring tools with #[tool_router] and #[tool]
In the Rust rmcp SDK, a server struct holds a `tool_router: ToolRouter<Self>` field initialised with `Self::tool_router()`. The `impl` block is annotated `#[tool_router]`, which generates the routing logic, and each async method is annotated `#[tool(description = "...")]` to expose it as an MCP tool. Example: `#[tool(description = "Get weather alerts for a US state.")] async fn get_alerts(&self, Parameters(MCPAlertRequest { state }): Parameters<MCPAlertRequest>) -> String`.
Go: registering tools with mcp.AddTool
In the Go SDK, create the server with `mcp.NewServer(&mcp.Implementation{Name: "weather", Version: "1.0.0"}, nil)` then register each tool with `mcp.AddTool(server, &mcp.Tool{Name: "get_forecast", Description: "Get weather forecast for a location"}, getForecast)`. The handler signature is `func(ctx context.Context, req *mcp.CallToolRequest, input InputStruct) (*mcp.CallToolResult, any, error)`.
MCP Apps are tools that carry UI, rendered in a sandboxed iframe
MCP Apps are tools that carry UI. The Inspector's Apps tab renders one in a sandboxed iframe served from a separate port, exercises the `ui/*` bridge, and shows the view's `ui/message` submissions and its `notifications/message` logs in side panels. The sandbox port is dynamic by default and can be pinned with `MCP_SANDBOX_PORT`.
Inspector Tools tab renders schema, annotations and results
Selecting a tool in the Inspector shows its description, its input schema rendered as a form, and its annotations. After calling, the result renders below with structured content, embedded resources, and images handled natively. On modern-era servers the screen also shows mirrored `Mcp-Param-*` headers, excluded tools, and distinct `-32602` (invalid params) error panels.
Example tool with no parameters
Example of a tool with no parameters using inputSchema:
```json
{
"name": "get_current_time",
"description": "Returns the current server time",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
```
SEP-2106 loosens inputSchema, outputSchema, and structuredContent restrictions
SEP-2106 (Final status) proposes allowing JSON Schema 2020-12 features in tool schemas. inputSchema retains the type: "object" requirement but now allows any additional JSON Schema properties including composition keywords (anyOf, oneOf, allOf, not), conditionals (if/then/else), and references ($ref, $defs). outputSchema is loosened to accept any valid JSON Schema 2020-12 object (not limited to type: "object"), enabling schemas that validate arrays, primitives, or complex compositions. structuredContent is loosened from {[key: string]: unknown} to unknown, allowing it to be any valid JSON value (objects, arrays, or primitives) that conforms to the tool's outputSchema.
inputSchema definition after SEP-2106
inputSchema uses the new definition: { $schema?: string; type: "object"; [key: string]: unknown }. The type: "object" field is retained as a requirement because tool arguments are always objects, but the schema now accepts any additional JSON Schema 2020-12 properties. This enables composition keywords (anyOf, oneOf, allOf, not), conditional schemas (if/then/else), reference schemas ($ref, $defs), and any other valid JSON Schema 2020-12 keywords.
OpenAPI 3.1 precedent: full JSON Schema alignment eliminates friction
OpenAPI 3.0 used an "extended subset" of JSON Schema with custom restrictions (requiring nullable: true instead of allowing "null" as a type). OpenAPI 3.1 made the strategic decision to fully align with JSON Schema 2020-12, accepting breaking changes to eliminate friction and improve tooling compatibility. MCP can learn from this experience rather than repeating the same evolution over several years. OpenAPI's parallel problems: type must be string (not array), couldn't use standard null handling, custom nullable keyword, caused tooling confusion. MCP's parallels: inputSchema only allows specific fields, can't use oneOf/anyOf in schemas, object-only structuredContent, causes SDK workarounds.
Motivation: current restrictions prevent common API patterns
The current MCP specification restricts tool schemas in ways that conflict with full JSON Schema support. inputSchema only allows type, properties, and required fields, preventing use of composition keywords like anyOf, oneOf, and allOf for sophisticated object validation patterns. outputSchema is also restricted to type: "object" with only properties and required. structuredContent is defined as {[key: string]: unknown}, which prevents returning arrays—a common API response pattern. These restrictions conflict with how real-world APIs work (GitHub Events API, AccuWeather Search API, standard REST collection endpoints all return arrays directly) and force developers to wrap arrays in unnecessary container objects, adding complexity and conflicting with common REST API patterns.
Tool with composition schema (oneOf pattern)
Example tool with oneOf in inputSchema:
```json
{
"name": "find_resource",
"description": "Find a resource by ID or name",
"inputSchema": {
"type": "object",
"oneOf": [
{
"properties": { "id": { "type": "string", "format": "uuid" } },
"required": ["id"]
},
{
"properties": { "name": { "type": "string", "minLength": 1 } },
"required": ["name"]
}
]
}
}
```
This pattern allows a tool to accept either an ID-based or name-based lookup without requiring both fields.
Tool returning array with outputSchema and structuredContent as array
Example tool returning an array of objects:
```json
{
"name": "list_users",
"description": "List all users in the system",
"inputSchema": {
"type": "object",
"properties": {
"limit": { "type": "integer", "minimum": 1, "maximum": 100 }
}
},
"outputSchema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "name"]
}
}
}
```
Response:
```json
{
"content": [
{
"type": "text",
"text": "Found 2 users: Alice (u1, alice@example.com) and Bob (u2, bob@example.com)."
}
],
"structuredContent": [
{ "id": "u1", "name": "Alice", "email": "alice@example.com" },
{ "id": "u2", "name": "Bob", "email": "bob@example.com" }
]
}
```
structuredContent definition after SEP-2106
structuredContent is widened from {[key: string]: unknown} to unknown. It can now be any valid JSON value (objects, arrays, or primitives) that conforms to the tool's outputSchema. This includes objects like {"key": "value"}, arrays like [1, 2, 3] or [{"id": "abc"}, {"id": "xyz"}], and primitives like "string", 42, true, or null.
SEP-2106 status and references
SEP-2106 has reached Final status and is preserved as a historical record of the design as accepted. Original PR: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/881. Related issue: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/834. OutputSchema type restriction inconsistency: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1906. TypeScript SDK schema types: https://github.com/modelcontextprotocol/typescript-sdk/issues/1149. SEP-2200 (Clarify Tool Result Content and Model Visibility): https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2200. Reference implementation branch: olaservo/typescript-sdk@sep-834-v1x (npm: @olaservo/mcp-sdk@1.25.2-sep834.4). Everything Server Demo Tools branch: olaservo/servers@sep-834-json-schema-2020-12 (npm: @olaservo/mcp-server-everything-sep834@1.1.0-sep834.1). Demo tools include get-weather-forecast (returns raw array of hourly forecasts), find-by-id-or-name (flexible input patterns), and get-count (returns raw number directly).
outputSchema definition after SEP-2106
outputSchema uses the new definition: { $schema?: string; [key: string]: unknown }. Unlike inputSchema, there is no type: "object" requirement because tool outputs can be any valid JSON. The field accepts any valid JSON Schema 2020-12 object, enabling schemas that validate arrays, primitives, or complex compositions.
Canonical supported pattern: sampling during tool execution
The canonical supported pattern is sampling during tool execution. When an MCP server tool is called, it can request LLM analysis while processing the tool call using ctx.session.create_message() with sampling messages. This occurs synchronously as part of processing a client request and is fully supported.
tools/call request structure
To invoke a tool, clients send a tools/call request with JSON-RPC 2.0 format. The params contain: name (string identifying the tool), arguments (object with tool parameters). The request may also include optional inputResponses and requestState when retrying after an input_required response.
tools/list response structure
The tools/list response contains: resultType (set to 'complete'), tools array with tool definitions, optional nextCursor for pagination, ttlMs (time-to-live in milliseconds for cache), and cacheScope ('public' or other values indicating cache visibility). Tool definitions include: name, optional title, description, inputSchema (required, must be valid JSON Schema object), optional icons array, and optional outputSchema.
tools/list request for discovering tools
To discover available tools, clients send a tools/list request with optional pagination cursor. The request is a JSON-RPC 2.0 call with method 'tools/list' and params containing an optional 'cursor' field for pagination support.
Tools capability declaration
Servers that support tools MUST declare the 'tools' capability in their capabilities object. The capability includes a 'listChanged' boolean property that indicates whether the server will emit notifications when the list of available tools changes.