> ## Documentation Index
> Fetch the complete documentation index at: https://toolgrain.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Link tools and widgets

> Attach built HTML widgets to tools in an existing Toolgrain server.

Start with [Build your first widget](https://toolgrain.mintlify.site/mcp-apps/quickstart.md) for a complete new project. In an existing Toolgrain server, the integration has three parts.

## 1. Define the HTML resource

Use `resource()` to return the HTML produced by your Vite build:

```ts theme={null}
import { readFile } from "node:fs/promises";
import { resource } from "@toolgrain/mcp";
import { APPS_MIME_TYPE, type AppResourceMeta } from "@toolgrain/protocol/apps";

const widgetUi = { prefersBorder: true } satisfies AppResourceMeta;

const weatherWidget = resource({
  uri: "ui://weather/widget.html",
  name: "weather-widget",
  mimeType: APPS_MIME_TYPE,
  read: async (uri) => ({
    contents: [{
      uri,
      mimeType: APPS_MIME_TYPE,
      text: await readFile(new URL("../dist/widgets/weather.html", import.meta.url), "utf8"),
      _meta: { ui: widgetUi },
    }],
  }),
});
```

This file path assumes the server module lives in a `server` directory, as in the quickstart. The URI identifies the resource; the reader loads the file. Supply the HTML MIME type on both the definition and its returned content.

## 2. Point the tool at the resource

Add this field to your existing `tool()` definition:

```ts theme={null}
_meta: { ui: { resourceUri: weatherWidget.uri } },
```

The URI must match exactly. A tool can return ordinary JSON, which Toolgrain converts to a structured result and assistant-facing text. Use `mcpResult()` when you also need rich content or [extra widget data](https://toolgrain.mintlify.site/mcp-apps/security.md#extra-widget-data).

## 3. Register both definitions

Pass the tool map and readonly resource list to your existing `createServer()` call:

```ts theme={null}
const server = createServer({
  name: "weather-app",
  version: "1.0.0",
  tools: { weather },
  resources: [weatherWidget],
});
```

Use the server's normal [stdio or HTTP transport](https://toolgrain.mintlify.site/transports/overview.md), auth accessors, providers, and cancellation. A widget calls tools through its host; the browser does not need a second authenticated connection to the MCP endpoint.

## Tool linking and visibility

| Tool `_meta.ui` field | What it controls |
| - | - |
| `resourceUri` | The registered widget resource to display |
| `visibility: ["model", "app"]` | Assistant and widget access; also the default |
| `visibility: ["model"]` | Assistant access only |
| `visibility: ["app"]` | Widget access only |

For example, register a filter/refresh tool that the assistant does not need to discover:

```ts theme={null}
const refreshWeather = tool({
  input: z.object({ city: z.string() }),
  _meta: { ui: { resourceUri: weatherWidget.uri, visibility: ["app"] } },
  execute: ({ city }) => ({ city, temperature: 29 }),
});
```

Register it under an exposed key such as `refresh_weather`. Export that map's type to React, then call `useToolCall("refresh_weather")`. A model-visible initiating tool supplies the first widget result.

The host enforces visibility. Every call still needs the server's usual authentication and application authorization. Keep registration static and provide useful assistant-facing results.

Permissions and CSP go on the returned HTML's `_meta.ui`, as the `widgetUi` object above shows. See [permissions and metadata](https://toolgrain.mintlify.site/mcp-apps/security.md) and the generic [resources](https://toolgrain.mintlify.site/guides/resources.md), [tool results](https://toolgrain.mintlify.site/tools/results.md), and [authorization](https://toolgrain.mintlify.site/guides/authorization.md) guides.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.