Soklet Logo

Model Context Protocol

MCP Tools and Schemas

Soklet tools are immutable registrations with a name, an input schema, an optional output schema, a thread-safe handler, and optional presentation or policy metadata. Annotation-driven and programmatic tools enter the same runtime model.

Choose a programmatic tool shape

McpToolRegistration.withName(...) requires a tool name and a nonempty set of exact McpProtocolVersion values, then returns a staged builder. The tool's revisions must be included in its endpoint's revisions. The next call fixes the argument and result model before a handler can be installed:

StageHandler inputHandler resultUse when
argumentAndOutputTypes(argumentType, outputType)Converted Java valueConverted Java valueThe tool always completes with structured output
argumentAndOutputTypes(...) plus operationHandler(...)Converted Java valueRecognized McpOperationResult, with the selected eventual output type retainedThe tool is statically task-required
argumentType(argumentType)Converted Java valueRecognized McpOperationResultThe tool needs explicit content or multi-round input
jsonObjectArguments()Immutable McpJsonObjectRecognized McpOperationResultThe handler wants raw JSON-object arguments
inputSchema(inputSchema)Validated immutable McpJsonObjectRecognized McpOperationResultJava derivation cannot express an input constraint or mirrored-header declaration

Class tokens handle ordinary types. Use TypeReference<T> overloads to retain nested generic types such as List<Item>.

record SearchArguments(String query, Optional<Integer> limit) {}
record SearchItem(String sku, String name) {}
record SearchResult(List<SearchItem> items) {}

McpToolRegistration<SearchArguments> search = McpToolRegistration
  .withName("catalog.search", Set.of(McpProtocolVersion.V2026_07_28))
  .argumentAndOutputTypes(SearchArguments.class, SearchResult.class)
  .handler((requestContext, arguments, invocationFeatures) ->
    new SearchResult(find(
      arguments.getConvertedArguments().query(),
      arguments.getConvertedArguments().limit().orElse(10)
    )))
  .title("Catalog search")
  .description("Searches visible catalog items")
  .toolAnnotations(McpToolAnnotations.builder()
    .readOnlyHint(true)
    .idempotentHint(true)
    .build())
  .build();

McpCompleteToolHandler<A,R> is for the typed complete path. McpToolHandler<A> is the advanced path and returns McpCompleteResult, McpInputRequiredResult, or McpTaskCreatedResult<R>. McpOperationResult is sealed to framework-owned result families; each tool invocation accepts only a compatible result.

Tool builder surface

Both complete and advanced builders support:

The advanced builder additionally supports inputRequestDeclarations(List) and requestStateMode(McpRequestStateMode). These do not appear on the typed-complete builder because that path always completes in one invocation. The typed task-required operationHandler(...) path retains the selected eventual output schema so Soklet can enforce it when a later tasks/get returns the completed result.

Tool annotations provide the corresponding name, title, description, named limiter, structured-content mirroring, input declarations, and request-state mode. @McpToolArgument can override an argument's published name, title, and description.

Handler context

An advanced programmatic handler has this shape:

McpOperationResult handle(
  McpRequestContext requestContext,
  McpToolArguments<A> arguments,
  McpInvocationFeatures invocationFeatures
) throws Exception;

arguments.getConvertedArguments() returns the converted value and arguments.getRawArguments() preserves the immutable validated JSON object. McpRequestContext exposes:

  • the originating immutable Soklet Request and selected McpEndpoint;
  • endpoint path parameters (currently empty because endpoint paths are exact);
  • high-level McpOperationType, exact JSON-RPC method, optional string-or-integer McpRequestId, protocol version, and optional selected operation name;
  • informational client implementation and validated client capabilities;
  • request _meta, input responses, and verified retry state;
  • validated MCP trace context and baggage; and
  • the accepted McpAdmissionIdentity.

Client implementation fields and self-reported metadata are informational, never authenticated identity.

For application branching, prefer McpRequestContext.getOperationType(). It classifies recognized Soklet operations without making application code compare protocol strings. getJsonRpcMethod() remains available when logging or extension handling needs the exact validated wire value; McpOperationType.OTHER covers an unrecognized, future, or extension method without discarding that value.

McpInvocationFeatures.find(Class) and require(Class) provide request-scoped optional behavior without expanding every handler signature. Current features include cooperative CancelationToken, an McpProgressReporter when requested by the client, and McpTaskCreationContext when the operation supports task augmentation and the client declared the Tasks extension. See MCP Tasks before retaining work beyond the request.

Java type derivation

Soklet derives schemas and performs conversion for this closed family:

Typed integer input conversion, including BigInteger, accepts at most 1,024 characters in the expanded decimal spelling, counting a minus sign. Each JSON-to-Java binding permits at most 4,194,304 expanded integer characters in total across records, arrays, lists and maps; repeated values count each time they are converted. Soklet checks these limits and fixed-width Java ranges before allocating the integer. A compact spelling such as 1e9999 cannot bypass them. These runtime binding limits apply in addition to schema validation and ordinary JSON limits. BigDecimal input is retained without integer expansion.

A typed input root must be a record, a Map<String,T>, or the synthetic object assembled from annotated method arguments. A bare typed String output is rejected because it would be ambiguous with explicit text content.

Arbitrary beans, Object, non-string map keys, raw generics, sets, unresolved wildcards or type variables, unsupported CharSequence implementations, and unsafe recursive records fail during registration or annotation processing. Soklet validates converted output against the derived output schema before writing it.

Input validation failures

For tools/call using 2025-11-25 or 2026-07-28, input-schema validation and Java binding failures return HTTP 200 with a completed isError: true tool result. The fixed message is "Arguments do not match the tool's inputSchema." It does not include submitted values, property names or exception details, and the handler is not invoked. Interception still precedes complete input validation; the generated error passes through McpToolResultSanitizer and the normal output limits. The same behavior applies if an interceptor tries to create a task with invalid arguments.

A 2025-06-18 call retains its JSON-RPC -32602 response. Malformed request envelopes/params and unknown tools remain protocol errors for every revision. Application handler exceptions retain their existing failure behavior. For safe, actionable business-validation feedback, return McpCompleteResult.fromToolErrorText(...).

Tool Schema Profile 1

McpToolSchema is an immutable inspection view of a schema compiled by Soklet. Programmatic registrations may provide an authored Profile 1 object-root input document through inputSchema(...); Soklet compiles it synchronously, publishes the exact immutable document, and validates every invocation before the handler runs. Applications cannot construct or replace McpToolSchema directly or author output schemas, and Soklet never fetches network references.

Profile 1 is a closed subset of JSON Schema Draft 2020-12. The complete recognized keyword set is:

$schema  $defs  $anchor  $ref  $comment
properties  additionalProperties  items
allOf  anyOf  if  then  else
type  enum  const  required  minimum  maximum
title  description  default  examples  deprecated
readOnly  writeOnly  format  x-mcp-header

$ref is limited to same-document # JSON Pointer fragments and local plain-name anchors. Every other keyword fails closed. Explicit exclusions include $id, $vocabulary, $dynamicAnchor, $dynamicRef, oneOf, not, dependent schemas, tuple/contains forms, pattern, patternProperties, property-name constraints, length/item/property-count constraints, multipleOf, exclusive numeric bounds, unevaluated keywords, and content-schema keywords.

Parsing, schema compilation, evaluation, and typed binding are independently bounded. Input JSON follows the configured MCP request-body limit: 10 MiB by default, configurable up to 16 MiB. Output JSON is capped at 4 MiB. Other production defaults include JSON depth 128, JSON or binding nodes 100,000, compiled schema nodes 4,096, schema depth 64, evaluation operations one million, and concurrent evaluation calls 128. These independent bounds are enforced even when a payload fits the byte limit. Framework state binding has separate canonical-parameter headroom; protected-state envelope limits remain independent.

For an authored input schema, x-mcp-header is a string containing the nonempty RFC 9110 field-name-token suffix, such as "Tenant", not the complete Mcp-Param-Tenant name. It is valid only on a property reached from the root solely through properties chains, whose direct type is string, boolean, or integer; suffixes must be unique case-insensitively within one tool schema.

JSON and schema limits

Each JSON string and object member name has independent ceilings of 1,048,576 UTF-16 code units for its decoded value and its escaped token spelling. Token accounting excludes the surrounding quotation marks. This is separate from the UTF-8 body and response byte limits:

Text unitDecoded unitsToken units when Soklet writes JSON
Ordinary ASCII character11
Quote, backslash, newline, tab, carriage return, backspace or form feed12
Other C0 control character, such as U+000116 (\u0001)
Supplementary Unicode character, such as 🚀22 (4 UTF-8 bytes)

A newline-only string therefore fits at 524,288 newlines. Incoming token accounting uses the client's actual spelling: \u0061 consumes six token units for one decoded a; \uD83D\uDE80 consumes twelve for one two-unit supplementary character. Soklet writes those ordinary characters directly. Constructing McpJsonString or McpTextContent does not prevalidate its eventual response.

An incoming JSON token/string limit failure produces the fixed parse error -32700 before the handler runs; an outgoing JSON limit failure produces the fixed internal error -32603. These errors do not reveal supplied values or exception details. The early JSON parse-limit rejection uses HTTP 400 for all three supported revisions. An application-output error uses HTTP 200 with the correlated JSON-RPC error on every supported profile, including an uncommitted modern response.

Schema evaluation also limits work independently of JSON size. Repeated applicators, references, equality checks and object-member sorting can exhaust the one-million-operation budget even when the value semantically satisfies the schema and fits the JSON ceilings. Valid traversal maintains one bounded diagnostic-path stack per call and copies full paths only for diagnostics. Branches with suppressed diagnostics require no path construction.

An exhausted input-schema budget uses the ordinary safe input-validation result described above: an isError tool result for 2025-11-25 and 2026-07-28, or JSON-RPC -32602 for 2025-06-18, before handler execution. Output-schema exhaustion returns the fixed internal error -32603 in HTTP 200 for application execution on every supported profile. Internal limit outcomes and diagnostic paths are not a public error API. Keep schemas and individual results bounded; use multiple calls or pagination for larger logical collections.

Operation results

Advanced tools return a recognized McpOperationResult:

return McpCompleteResult.fromToolText("No matching items");

return McpCompleteResult.fromToolStructuredContent(
  McpJsonObject.builder().put("count", 3).build()
);

return McpCompleteResult.fromToolErrorText(
  "The catalog is temporarily unavailable"
);

McpCompleteResult wraps one operation-specific McpCompletePayload and an extension metadata object. Its from... factories return completed instances. withToolOutput(...), withPromptOutput(...), and withResourceOutput(...) return builders with empty metadata. Use toBuilder() to preserve an existing result while selectively replacing its payload or metadata, then call build().

McpToolOutput.Builder accepts ordered content blocks, optional structured JSON, and error(Boolean). An application-level tool error is still a successful JSON-RPC exchange whose tool result has isError: true; protocol/framework failures use McpJsonRpcError and McpJsonRpcException instead.

McpTaskCreatedResult identifies durable work that the configured manager can already retrieve. Its generic output type is a schema contract, not an in-memory future. Soklet validates and sanitizes the eventual completed output when the client retrieves it. See MCP Tasks.

Results on 2025 revisions

For tools selecting 2025-06-18 or 2025-11-25, structured content and any declared output schema must have object shape. Annotated typed outputs enforce that schema shape at compile time; programmatic registrations enforce it when the endpoint is built, with the tool name, path and selected revisions in the error. Raw or advanced handlers must choose a representable value before performing side effects. Arrays, scalars, and JSON null are supported as structured content on the modern revision, but fail legacy result projection. Soklet does not silently wrap them, discard them, or rerun the handler. An optional text mirror does not make a non-object value valid legacy structured content.

Tool resource links omit optional icons on 2025-06-18 and retain them on 2025-11-25 and the modern revision. The URI, name, other link fields, ordered tool content, and result metadata survive projection; the application value is unchanged. See the June schema and November schema.

Content and JSON values

McpContentBlock is the sealed family used by tools and prompt messages:

TypePurpose
McpTextContentUTF-8 text plus optional annotations and metadata
McpImageContentDefensive-copy binary data and MIME type
McpAudioContentDefensive-copy binary data and MIME type
McpResourceLinkA URI, name, and optional resource descriptor fields
McpEmbeddedResourceInline McpTextResourceContents or McpBlobResourceContents

McpContentAnnotations carries an audience (McpRole), a priority, and last-modified time. McpIcon carries an absolute source URI plus optional MIME type, sizes, and light/dark theme.

McpIcon.withSource(URI) rejects relative sources and unpaired UTF-16 surrogates at the factory, without normalizing the URI. Its MIME setter uses the same ASCII media-type syntax parser as image/audio content. sizes(List) accepts ASCII decimal WxH dimensions with a lowercase x, or exactly any. Order, duplicates and decimal spelling are preserved; null or empty clears sizes, and invalid replacements leave the previous value intact. These checks do not verify actual image dimensions or prevalidate eventual JSON response size.

Soklet does not fetch icons, enforce a scheme/domain allowlist, validate inline image bytes or sanitize SVG. Prefer trusted HTTPS sources or image data URIs; applications and consuming clients own trusted-source and rendering policy. The protocol Icon definition describes consumer precautions. Public declaration validation does not make incoming client metadata trustworthy. November 2025 and modern catalogs and tool resource links retain icons; June 2025 catalogs and tool resource links omit them without changing application values. June prompt resource links with icons still fail safely because that shape is unrepresentable.

Text content, text resource contents, JSON strings/member names, prompt-output descriptions, and argument-completion values reject unpaired UTF-16 surrogates at the factory or setter with IllegalArgumentException. Well-formed text stays exact: empty strings, controls, emoji, combining characters, and distinct normalization forms are preserved. Construction validates well-formedness; JSON size limits still apply when the response is serialized. An uncaught construction exception in an application handler becomes a fixed internal error without exposing the supplied value.

Image and audio MIME types use Soklet's existing MCP Apps ASCII media-type parser. They require type/subtype syntax, support token or quoted parameters, and reject malformed syntax, controls, non-ASCII characters, and duplicate parameter names. Supplied spelling is retained. Soklet does not inspect the bytes or restrict formats to a registry; the application must label its data accurately and choose formats supported by its clients.

The built-in server can send at most 786,432 raw bytes per image, audio, or blob item. Base64 encoding must fit the 1,048,576-character JSON string ceiling. The complete response must also fit the independent 4 MiB UTF-8 JSON ceiling, including wrappers and metadata. Constructors copy bytes without prevalidating eventual response size. Oversized output returns fixed JSON-RPC -32603 in HTTP 200 for application execution on every supported profile. For larger files, return a McpResourceLink with an authorized delivery route; inline blobs and resources/read retain these limits.

The immutable JSON tree is McpJsonValue, with McpJsonObject, McpJsonArray, McpJsonString, McpJsonNumber, McpJsonBoolean, and singleton McpJsonNull. Object and array builders preserve insertion order; use these values for raw arguments, structured content, request metadata, result metadata, and protocol extensions.

Numeric equality and hashing ignore decimal scale: 1, 1.0, and 1E+0 compare equal and have the same hash code. getValue() preserves the supplied BigDecimal and its scale. Hashing removes trailing zeros from coefficient text without expanding exponents or repeatedly dividing large numbers; do not persist hash codes as identifiers.

Mirrored arguments

@McpHeader(name = "Tenant") on an eligible typed tool argument publishes x-mcp-header and requires Mcp-Param-Tenant to agree with the value already present in JSON arguments. The header never supplies a missing or null argument. Eligible properties have the direct schema type string, boolean, or integer. Both copies remain untrusted input. Mirrored tools must select only 2026-07-28; endpoint construction rejects either 2025 revision on such a tool.

Check the client and execution environment before exposing a mirrored tool to browser code. TypeScript Client 2.3.1 skips automatic Mcp-Param-* mirroring in browsers, even though it can read the tool's x-mcp-header schema. Browser callers must supply the matching header explicitly through that SDK's per-request header support or an equivalent transport hook, and the server's CORS policy must permit it. Keep the JSON argument too; a header alone is insufficient. Otherwise Soklet rejects the call with -32020 before handler entry. Node clients and Inspector can build these headers differently, so verify the actual request for the client version you deploy.

Each mirrored value is limited to 16 KiB of decoded UTF-8 bytes, including Mcp-Param-* strings. Plain and base64 forms share that limit; the complete headers must also fit the configured aggregate HTTP header ceiling.

Integer safety constrains each request value. long, Long, and BigInteger are accepted; derived long/Long schemas retain their full Java range, and BigInteger has no derived minimum/maximum. Mirrored values must fit the inclusive range -9007199254740991 through 9007199254740991. Authored schemas may specify narrower bounds.

Integer headers use canonical decimal spelling: 0, or digits without leading zeros, optionally preceded by -. +1, 01, and -0 are invalid. JSON 1.0 or 1e0 can match the header 1. Unsafe values and mismatches produce the fixed HTTP 400 / JSON-RPC -32020 "Header mismatch" after admission and before handler entry. See Security and Policy.

For multi-round results, continue to Input and Request State. For durable deferred results, continue to MCP Tasks. For every owned tool/content type, see the Public API Map.

Previous
Endpoint Authoring