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

# registerTool

> Register a tool, with or without a view

`registerTool` adds an action the model can call to your server, optionally rendering its result through a [view](#view).

## Example

`search-products` searches the store catalog for the signed-in user and renders the matches in the `carousel` view.

```ts server.ts theme={null}
import { McpServer } from "skybridge/server";
import { z } from "zod";

const server = new McpServer({ name: "shop", version: "1.0" }).registerTool(
  {
    name: "search-products",
    title: "Search products",
    description: "Search the store catalog by keyword and optional price ceiling.",
    inputSchema: {
      query: z.string().describe("What the shopper is looking for"),
    },
    outputSchema: {
      productIds: z.array(z.string()).describe("The IDs of products matching the query."),
    },
    annotations: { readOnlyHint: true },
    securitySchemes: [{ type: "oauth2", scopes: ["catalog.read"] }],
    view: {
      component: "carousel",
      csp: { connectDomains: ["https://api.myshop.com"] },
    },
  },
  async ({ query, maxPrice }, { authInfo }) => {
    const userId = authInfo.extra?.sub as string;
    const products = await search(query, { maxPrice, userId });
    return {
      content: `Found ${products.length} products for "${query}".`,
      structuredContent: { products },
    };
  },
);
```

## Signature

```ts theme={null}
server.registerTool(config: ToolConfig, handler: ToolHandler): McpServer;
```

## Config

The first argument declares the tool.

```ts theme={null}
type ToolConfig = {
  name: string;
  title?: string; // human-readable label
  description?: string; // model-facing: decides when the model calls the tool
  inputSchema?: ZodRawShape; // Zod shape; validates the call and types the handler input
  outputSchema?: ZodRawShape; // Zod shape; types structuredContent
  annotations?: ToolAnnotations; // standard MCP hints
  view?: ViewConfig; // bind a view to render the result
  securitySchemes?: SecurityScheme[]; // declare per-tool auth
  _meta?: ToolMeta; // extra metadata
};
```

### `name`, `title`, `description`

All three are the tool's prompt surface: the model reads them to decide when to call it, so write each for the model. `name` is also the identifier the call uses, and `title` a short display name.

### `inputSchema`, `outputSchema`

[Zod](https://zod.dev/) raw shapes. `inputSchema` validates the call arguments and types the handler's `input`. Add `.describe()` to a field to tell the model what it's for. `outputSchema` tells the model the shape to expect back, it does not type the returned value.

### `annotations`

Standard MCP hints describing what the tool does, so the host can label it and order calls. They are hints: the host may surface or ignore them, and never gates a call on them.

```ts theme={null}
type ToolAnnotations = {
  title?: string;
  readOnlyHint?: boolean;
  destructiveHint?: boolean;
  idempotentHint?: boolean;
  openWorldHint?: boolean;
};
```

| Hint              | Meaning                                                                                                           | Default |
| ----------------- | ----------------------------------------------------------------------------------------------------------------- | ------- |
| `title`           | Human-readable label; the host gives it precedence over `name`.                                                   | none    |
| `readOnlyHint`    | The tool does not modify its environment.                                                                         | `false` |
| `destructiveHint` | The tool may perform destructive updates, not only additive ones. Meaningful only when `readOnlyHint` is `false`. | `true`  |
| `idempotentHint`  | Repeating the call with the same arguments has no further effect. Meaningful only when `readOnlyHint` is `false`. | `false` |
| `openWorldHint`   | The tool interacts with an open world of external entities, not a closed domain.                                  | `true`  |

### `view`

Bind the tool to a React view to render its result instead of plain text. Each view backs exactly one tool.

```ts theme={null}
type ViewConfig = {
  component: ViewName;
  description?: string;
  hosts?: Array<"apps-sdk" | "mcp-app">;
  prefersBorder?: boolean;
  domain?: string;
  csp?: ViewCsp;
  _meta?: Record<string, unknown>;
};
```

| Key                                       | Purpose                                                  |
| ----------------------------------------- | -------------------------------------------------------- |
| `component`                               | The view's file name, type-checked against your views.   |
| `description`                             | Label the host may show during view discovery.           |
| `hosts`                                   | Restrict where the view renders; defaults to all.        |
| `prefersBorder`                           | Apps SDK only: request a visible border around the view. |
| `domain`                                  | Apps SDK only: override the served domain (advanced).    |
| [`csp`](/api-reference/register-tool#csp) | Per-view CSP overrides; see below.                       |
| `_meta`                                   | Free-form metadata forwarded on the view resource.       |

#### `csp`

A view runs in a sandboxed iframe; your server's domain is allowlisted automatically. Add external origins per directive:

```ts theme={null}
type ViewCsp = {
  resourceDomains?: string[];
  connectDomains?: string[];
  frameDomains?: string[];
  redirectDomains?: string[];
  baseUriDomains?: string[];
};
```

| Directive         | Purpose                                               |
| ----------------- | ----------------------------------------------------- |
| `resourceDomains` | Static assets: images, fonts, scripts, styles.        |
| `connectDomains`  | Fetch / XHR targets.                                  |
| `frameDomains`    | Iframe embed origins (opts into stricter app review). |
| `redirectDomains` | `openExternal` targets that skip the safe-link modal. |
| `baseUriDomains`  | `<base href>` origins (MCP Apps only).                |

### `securitySchemes`

Declare which auth a tool supports so clients can label it public vs. requires-sign-in before invocation, and request the right scopes during sign-in.

```ts theme={null}
type SecurityScheme =
  | { type: "noauth" }
  | { type: "oauth2"; scopes?: string[] };
```

* **Across the array, match any.** Each entry is an alternative.
* **Within an `oauth2` entry's `scopes`, match all.** The token must carry every listed scope.
* **`noauth` and `oauth2` together** means "works anonymously, but auth unlocks more."

<Warning>
  `securitySchemes` is client-facing metadata, not self-enforcing. Pair it with an auth middleware ([`requireBearerAuth`](/api-reference/require-bearer-auth) or [`optionalBearerAuth`](/api-reference/optional-bearer-auth)) to authenticate requests, and enforce the tool's own requirements in the handler from `extra.authInfo`.
</Warning>

### `_meta`

Metadata on the tool. Skybridge recognizes the keys below; any other key you set is forwarded on the tool's `_meta` untouched.

```ts theme={null}
type ToolMeta = {
  ui?: { visibility?: Array<"model" | "app"> };
  "openai/toolInvocation/invoking"?: string;
  "openai/toolInvocation/invoked"?: string;
  "openai/fileParams"?: string[];
  [key: string]: unknown;
};
```

| Key                                         | Purpose                                                                                      |
| ------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `ui.visibility`                             | Expose the tool to the model, the app, or both.                                              |
| `openai/toolInvocation/invoking`, `invoked` | Status text shown while the tool runs and after it completes.                                |
| `openai/fileParams`                         | Top-level input fields that carry file references; see [`FileRef`](/api-reference/file-ref). |
| `openai/widgetAccessible`                   | Deprecated, use `ui.visibility`.                                                             |

<Info>
  The `openai/*` keys are read only on **ChatGPT**. `openai/widgetAccessible`is deprecated, prefer `ui.visibility` instead.
</Info>

## Handler

Runs when the tool is called. It receives the validated `input` (typed from [`inputSchema`](#inputschema-outputschema)) and the request context `extra`:

```ts theme={null}
type ToolHandler = (input: Input, extra: RequestHandlerExtra) => Promise<{
  content?: string | ContentBlock | ContentBlock[];
  structuredContent?: Record<string, unknown>;
  isError?: boolean;
  _meta?: Record<string, unknown>;
}>;
```

### `extra`

The request context.

| Field         | Purpose                                                                 |
| ------------- | ----------------------------------------------------------------------- |
| `authInfo`    | Validated token from an auth middleware: `clientId`, `scopes`, `extra`. |
| `requestInfo` | The HTTP request: headers and URL.                                      |
| `signal`      | An `AbortSignal`, aborted when the call is cancelled.                   |
| `_meta`       | Client hints (below).                                                   |

### Return

| Field               | Purpose                                                                                     |
| ------------------- | ------------------------------------------------------------------------------------------- |
| `content`           | Text for the model, shown in the conversation. A string, a `ContentBlock`, or an array.     |
| `structuredContent` | Typed data the model and the view read (via [`useToolInfo`](/api-reference/use-tool-info)). |
| `isError`           | Marks the call as failed.                                                                   |
| `_meta`             | View-only response metadata, hidden from the model.                                         |

<Info>
  For a view tool, Skybridge adds a `viewUUID` to `_meta`, a reserved key used for state persistence.
</Info>

### Client hints

**ChatGPT** attaches conversation context to `extra._meta`. They are hints: tolerate their absence and never use them for authorization.

```ts theme={null}
type ClientHintsMeta = {
  "openai/locale"?: string;
  "openai/userAgent"?: string;
  "openai/userLocation"?: {
    city?: string;
    region?: string;
    country?: string;
    timezone?: string;
    longitude?: number;
    latitude?: number;
  };
  "openai/subject"?: string;
  "openai/session"?: string;
  "openai/organization"?: string;
  "openai/widgetSessionId"?: string;
};
```

| Key                      | Purpose                                                                                            |
| ------------------------ | -------------------------------------------------------------------------------------------------- |
| `openai/locale`          | Requested locale, BCP-47, e.g. `en-US`.                                                            |
| `openai/userAgent`       | Browser user-agent of the ChatGPT client.                                                          |
| `openai/userLocation`    | Coarse location: `city`, `region`, `country`, `timezone`, `latitude`, `longitude` (each optional). |
| `openai/subject`         | Anonymized user id.                                                                                |
| `openai/session`         | Anonymized conversation id, stable within a session.                                               |
| `openai/organization`    | Anonymized organization id, when the user account is in an organization.                           |
| `openai/widgetSessionId` | Stable id for the mounted view instance.                                                           |

<Info>
  These hints are **ChatGPT** only.
</Info>

### Content helpers

`skybridge/server` exports helpers that build `ContentBlock`s for the handler's `content`:

```ts theme={null}
import { audio, embeddedResource, image, resourceLink, text } from "skybridge/server";

return {
  content: [text("Here's your chart:"), image(pngBuffer, "image/png")],
  structuredContent: { /* ... */ },
};
```

| Helper                                     | Block                   |
| ------------------------------------------ | ----------------------- |
| `text(value, annotations?)`                | `TextContent`           |
| `image(data, mimeType, annotations?)`      | `ImageContent` (base64) |
| `audio(data, mimeType, annotations?)`      | `AudioContent` (base64) |
| `embeddedResource(resource, annotations?)` | `EmbeddedResource`      |
| `resourceLink(link, annotations?)`         | `ResourceLink`          |

Returning plain `ContentBlock` objects works too; the helpers are optional.

<CardGroup cols={3}>
  <Card title="McpServer" icon="server" href="/api-reference/mcp-server">
    The server you register tools on
  </Card>

  <Card title="useToolInfo" icon="database" href="/api-reference/use-tool-info">
    Read the tool's result in the view
  </Card>

  <Card title="FileRef" icon="file" href="/api-reference/file-ref">
    Pass files in and out of a tool
  </Card>
</CardGroup>
