> ## Documentation Index
> Fetch the complete documentation index at: https://docs-mcp.phake.app/llms.txt
> Use this file to discover all available pages before exploring further.

# defineTool

> Type-safe factory for creating MCP tool definitions with input validation, structured output, and optional authentication.

## Overview

`defineTool` creates a `SharedToolDefinition` that you can register with `createMCPServer`. It wraps your handler so that plain object return values are automatically converted to the `ToolResult` format the MCP protocol expects.

```typescript theme={null}
function defineTool<TShape extends ZodRawShape, OShape extends ZodRawShape>(
  def: ToolDefinitionInput<TShape, OShape>
): SharedToolDefinition<TShape>
```

## Parameters

<ParamField body="name" type="string" required>
  Unique identifier for the tool. Use lowercase letters and underscores (e.g.
  `get_profile`, `search_documents`). This name is what LLMs see when deciding
  which tool to call.
</ParamField>

<ParamField body="description" type="string" required>
  Human-readable description shown to the LLM. Write this as a clear, precise
  statement of what the tool does and when to use it.
</ParamField>

<ParamField body="inputSchema" type="ZodObject<TShape>" required>
  Zod object schema that validates and types the tool's input arguments. The
  inferred type is passed to your `handler` as the first argument.

  ```typescript theme={null}
  inputSchema: z.object({
    query: z.string().describe("Search query"),
    limit: z.number().int().min(1).max(100).optional(),
  })
  ```
</ParamField>

<ParamField body="handler" type="(args: z.infer<TSchema>, context: ToolContext) => Promise<ToolResult | Record<string, unknown>>" required>
  Async function that executes the tool. Receives validated `args` and a
  [`ToolContext`](/api/tool-context) object.

  Return either a full `ToolResult` or a plain object. Plain objects are
  automatically wrapped — see [Return values](#return-values).
</ParamField>

<ParamField body="title" type="string">
  Human-readable display name shown in client UIs. Separate from `name`, which
  is the machine identifier.
</ParamField>

<ParamField body="outputSchema" type="ZodRawShape | ZodObject<OShape>">
  Zod schema for the tool's structured output. Accepts either a raw shape
  (`{ field: z.string() }`) or a `ZodObject`. Normalized internally to
  `ZodRawShape`. Used by MCP clients that support structured content.
</ParamField>

<ParamField body="requiresAuth" type="boolean">
  When `true`, the dispatcher automatically rejects calls where
  `context.providerToken` is absent, returning an authentication error before
  your handler runs. Defaults to `false`.
</ParamField>

<ParamField body="annotations" type="object">
  Behavioral hints for MCP clients. These are informational — they are not
  enforced by the SDK, but well-behaved clients may use them to surface
  warnings or adjust behavior.

  <Expandable title="Annotation fields">
    <ParamField body="annotations.title" type="string">
      Display title override (redundant with the top-level `title` field in
      most clients).
    </ParamField>

    <ParamField body="annotations.readOnlyHint" type="boolean">
      Set to `true` if the tool does **not** modify any environment or
      persistent state. Default: `false`.
    </ParamField>

    <ParamField body="annotations.destructiveHint" type="boolean">
      Set to `true` if the tool may delete or permanently overwrite data.
      Default: `true`.
    </ParamField>

    <ParamField body="annotations.idempotentHint" type="boolean">
      Set to `true` if calling the tool multiple times with the same arguments
      produces the same result (no additional side effects). Default: `false`.
    </ParamField>

    <ParamField body="annotations.openWorldHint" type="boolean">
      Set to `true` if the tool interacts with external services or systems
      outside the server's direct control. Default: `true`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="meta" type="{ version?: string; last_update?: string }">
  Version metadata automatically injected into every handler result as
  `tool_version` and `tool_last_update` fields. Useful for tracking which
  version of a tool produced a given output.
</ParamField>

## Return values

Your handler can return either a full `ToolResult` or a plain object.

**Plain object** — `defineTool` wraps it automatically:

```typescript theme={null}
// You return:
return { greeting: "Hello, Alice!", count: 1 };

// The dispatcher receives:
{
  content: [{ type: "text", text: JSON.stringify({ greeting: "Hello, Alice!", count: 1 }, null, 2) }],
  structuredContent: { greeting: "Hello, Alice!", count: 1 },
}
```

**Full `ToolResult`** — passed through unchanged:

```typescript theme={null}
return {
  content: [{ type: "text", text: "Hello, Alice!" }],
  structuredContent: { greeting: "Hello, Alice!" },
  isError: false,
};
```

## Helpers

### `toolFail`

Creates a typed error factory with preset default fields. Call the returned function with an error message to produce a consistent error object.

```typescript theme={null}
import { toolFail } from "@phake/mcp";

const fail = toolFail({ ok: false, items: null });

// In your handler:
if (!args.spreadsheetId) {
  return fail("spreadsheet_id is required");
  // => { ok: false, items: null, error: "spreadsheet_id is required" }
}
```

### `assertProviderToken`

Narrows `context` so that `context.providerToken` is typed as `string` (not `string | undefined`). Throws `"Authentication required"` if the token is absent.

Use this in tools where you need a guaranteed token but prefer not to set `requiresAuth: true` on the definition itself.

```typescript theme={null}
import { assertProviderToken } from "@phake/mcp";

handler: async (_args, context) => {
  assertProviderToken(context); // throws if missing
  // context.providerToken is now string
  const res = await fetch("https://api.example.com/me", {
    headers: { Authorization: `Bearer ${context.providerToken}` },
  });
  return res.json();
},
```

## Example

A complete tool definition using all key fields:

```typescript theme={null}
import { z } from "zod";
import { defineTool, toolFail } from "@phake/mcp";

const VERSION = "1.2.0";
const fail = toolFail({ ok: false, results: null });

export const searchDocumentsTool = defineTool({
  name: "search_documents",
  title: "Search Documents",
  description:
    "Full-text search across all documents the user has access to. " +
    "Returns up to `limit` matching documents sorted by relevance.",
  inputSchema: z.object({
    query: z.string().min(1).describe("Search query"),
    limit: z.number().int().min(1).max(50).optional().describe("Max results (default 10)"),
  }),
  outputSchema: z.object({
    ok: z.boolean(),
    results: z
      .array(
        z.object({
          id: z.string(),
          title: z.string(),
          snippet: z.string(),
        })
      )
      .nullable(),
  }),
  requiresAuth: true,
  annotations: {
    readOnlyHint: true,
    destructiveHint: false,
    idempotentHint: true,
    openWorldHint: false,
  },
  meta: {
    version: VERSION,
    last_update: "2025-01-15",
  },
  handler: async (args, context) => {
    if (!args.query.trim()) {
      return fail("query must not be empty");
    }

    const response = await fetch(
      `https://api.example.com/search?q=${encodeURIComponent(args.query)}&limit=${args.limit ?? 10}`,
      { headers: context.resolvedHeaders }
    );

    if (!response.ok) {
      return fail(`API error: ${response.status}`);
    }

    const data = await response.json();
    return { ok: true, results: data.items };
  },
});
```
