pascal-mcp-sdk
Guides

Tools

Executive Summary

A tool is a function your server exposes for an AI agent to call: a name, a description the model reads, a JSON Schema for its arguments, and a Pascal handler. Register tools on a TMCPServer before serving; the library validates every call against the schema before your handler runs, turns handler exceptions into in-band errors the model can correct against, and stamps the protocol envelope — your handler contains domain logic and nothing else.

The registration model

Every tool registration names the tool, describes it, declares its argument schema, and binds a handler:

Server.RegisterTool('greet', 'Greet someone by name',
  ObjectSchema.AddString('name', 'Who to greet'),
  Greet);

The schema can be declared four ways — pick per tool, they coexist on one server:

  • Fluent builderObjectSchema.AddString(...).AddNumber(...) for the flat object schemas most tools need. See Schemas.
  • Typed argument class — a TMCPArgs descendant whose published properties are the schema; the handler receives a populated, validated instance instead of raw JSON. See Schemas.
  • Raw JSON string'{"type":"object",...}', parsed and validated at registration.
  • Definition object — a TJSONObject you assembled yourself (name/description/inputSchema), for tools whose definitions come from data.

Handlers come in two shapes per style — plain functions and of object methods — so both programs and class-based hosts register naturally. All registration must happen before the first session is created: the registries freeze when serving starts, and a late RegisterTool raises EMCPServer.

Handlers

A raw-schema handler receives the arguments as TJSONObject plus a request context:

function Greet(AArguments: TJSONObject;
  const ACtx: TMCPRequestContext): TMCPToolResult;
begin
  Result := MCPTextResult('Hello, ' + AArguments.Get('name', 'world') + '!');
end;

A typed handler receives the populated argument instance:

function Add(AArgs: TMCPArgs;
  const ACtx: TMCPRequestContext): TMCPToolResult;
begin
  with AArgs as TAddArgs do
    Result := MCPTextResult(FloatToStr(a + b));
end;

ACtx carries the per-request facts: the negotiated protocol version, client name/version and capabilities, the progress token and log level (see Progress and logging), the MRTR retry payload (below), and IsCancelled for cooperative cancellation. Handlers are synchronous — under stdio the loop is read-handle-write, so keep them short.

Results are built with the helpers in Results and content:

  • MCPTextResult('...') — plain text content.
  • MCPStructuredResult(Text, StructuredData) — text plus structuredContent matching your output schema.
  • MCPImageResult(Bytes, 'image/png') — one image content block; takes raw bytes (base64-encoded for you) or already-base64 data.
  • MCPErrorResult('...') — an explicit in-band error (isError: true).

Validation before your handler runs

Every call is checked against the registered schema's enforceable subset — type, properties, required, enum, default — before the handler runs. Violations become in-band isError results in the same shape for raw and typed tools, so the model sees what it got wrong and retries. Absent optional arguments are seeded with their schema default; unknown argument properties are ignored.

A raw schema that uses keywords outside the enforced subset fails at startup, naming the offending keyword, unless the registration is marked .ApplicationValidated — the documented escape hatch that hands argument validation to your handler (see Schemas).

Deeper, semantic validation stays your handler's job — report violations with MCPErrorResult, not exceptions, when you want the model to read and correct them. Exceptions work too: an escaped handler exception becomes an isError: true result automatically (with the message redactable — see Configuration).

Annotations

RegisterTool returns a TMCPToolOptions for fluent annotation chaining:

Server.RegisterTool('add', 'Add two numbers and return the sum',
  TAddArgs, TSumResult, AddHandler)
  .Title('Adder').ReadOnlyHint.IdempotentHint;

Title sets a display name; ReadOnlyHint, DestructiveHint, IdempotentHint, and OpenWorldHint set the spec's behavior-hint annotations clients may surface or act on. ApplicationValidated marks a raw schema as handler-validated (above).

Asking the client for more input (MRTR)

A handler that needs more input mid-call — a missing value, a user confirmation, a model completion — returns MCPInputRequired(...) instead of a final result. The client gathers the responses and retries the same call; your handler re-enters with the responses available on ACtx:

function GreetUserHandler(AArguments: TJSONObject;
  const ACtx: TMCPRequestContext): TMCPToolResult;
var
  Content: TJSONObject;
begin
  Content := MCPElicitationContent(ACtx, 'who');
  if Content = nil then
    Exit(MCPInputRequired(TJSONObject.Create(['who',
      MCPElicitFormRequest('Who should be greeted?',
      ObjectSchema.AddString('name', 'Name of the person to greet'))]),
      'greet-round-1'));
  Result := MCPTextResult('Hello, ' + Content.Get('name', 'stranger') + '!');
end;

The server stays stateless across rounds — your requestState string is echoed back by the client (treat it as untrusted input). Request kinds are capability-gated per request. The entry builders and accessors are documented in Client requests.

Cancellation

notifications/cancelled flips a per-request token; long-running handlers poll ACtx.IsCancelled and abandon work. The serial stdio transport cannot deliver a cancellation while a handler is running (one more reason to keep handlers short); transports with mid-request delivery points can.

These protocol behaviours implement spec revision 2026-07-28; the dated official-spec citations live in the architecture page's Spec grounding section.