# @grackle-ai/mcp

**MCP (Model Context Protocol) server for [Grackle](https://github.com/nick-pape/grackle)** — exposes Grackle's full capabilities as MCP tools so any AI agent can manage environments, spawn sessions, orchestrate tasks, and share knowledge.

This package translates MCP tool calls into [ConnectRPC](https://connectrpc.com/) requests to the Grackle server. It implements the [MCP Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) and supports multiple concurrent client sessions.

## Installation

```bash
npm install @grackle-ai/mcp
```

Or run the standalone server directly:

```bash
npx @grackle-ai/mcp
```

## Quick Start

The MCP server connects to an already-running Grackle server. Start the Grackle server first, then launch the MCP server:

```bash
# 1. Start the Grackle server (installs the CLI if needed)
npx @grackle-ai/cli serve

# 2. Start the MCP server (reads the API key automatically)
npx @grackle-ai/mcp
```

The MCP server listens on `http://127.0.0.1:7435/mcp` by default.

## Configuration

All configuration is via environment variables:

| Variable           | Default                 | Description                                                                                  |
| ------------------ | ----------------------- | -------------------------------------------------------------------------------------------- |
| `GRACKLE_MCP_PORT` | `7435`                  | Port the MCP server listens on                                                               |
| `GRACKLE_HOST`     | `127.0.0.1`             | Bind address (must be a loopback address)                                                    |
| `GRACKLE_URL`      | `http://127.0.0.1:7434` | URL of the Grackle gRPC server to connect to                                                 |
| `GRACKLE_API_KEY`  | _(auto-loaded)_         | API key for authenticating with the gRPC server. If not set, reads from `~/.grackle/api-key` |
| `LOG_LEVEL`        | `info`                  | Logging level (`debug`, `info`, `warn`, `error`)                                             |

## Programmatic Usage

The package also exports `createMcpServer` for embedding the MCP server in your own application:

```ts
import { createMcpServer } from "@grackle-ai/mcp";

const server = createMcpServer({
  bindHost: "127.0.0.1",
  mcpPort: 7435,
  grpcPort: 7434,
  apiKey: "your-api-key",
});

server.listen(7435, "127.0.0.1", () => {
  console.log("MCP server ready");
});
```

## Authentication

The MCP server supports three authentication modes:

- **API key** — Full access. Pass as `Authorization: Bearer <api-key>`.
- **OAuth** — Full access. Token issued by the Grackle OAuth authorization server.
- **Scoped token** — Limited tool access. Issued to agents working on a specific task. Only a subset of tools is available (see [Scoped Access](#scoped-access) below).

## Client Configuration

### Claude Desktop / Claude Code

Add to your MCP configuration (`claude_desktop_config.json` or `.mcp.json`):

```json
{
  "mcpServers": {
    "grackle": {
      "url": "http://127.0.0.1:7435/mcp",
      "headers": {
        "Authorization": "Bearer <your-api-key>"
      }
    }
  }
}
```

### Any MCP-compatible client

Point your client at `http://127.0.0.1:7435/mcp` using the Streamable HTTP transport with a Bearer token in the `Authorization` header.

---

## Tool Reference

The MCP server exposes a rich set of tools organized into groups. Each tool validates its inputs against a strict schema and returns structured JSON results.

### Environment Tools

Manage compute environments where agents run (Docker, SSH, Codespace, local).

| Tool                         | Description                                                                                                                                                                                                        | Parameters                                                                                                                                                    |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `env_list`                   | List all registered environments with status, adapter type, and runtime.                                                                                                                                           | _(none)_                                                                                                                                                      |
| `env_list_docker_containers` | List running Docker containers that can be attached to (Docker adapter attach mode).                                                                                                                               | _(none)_                                                                                                                                                      |
| `env_add`                    | Register a new environment. The `adapterConfig` fields depend on `adapterType` (see below). For the Docker adapter, set `adapterConfig.attach` to an existing container name/ID to attach instead of creating one. | `displayName` (string), `adapterType` (`local` \| `ssh` \| `codespace` \| `docker`), `adapterConfig?` (object), `githubAccountId?` (string, codespace/docker) |
| `env_provision`              | Provision an environment — start resources, install the agent, and connect.                                                                                                                                        | `environmentId` (string), `force?` (boolean)                                                                                                                  |
| `env_stop`                   | Stop a running environment without destroying its resources.                                                                                                                                                       | `environmentId` (string)                                                                                                                                      |
| `env_destroy`                | Destroy an environment's backing resources (e.g., delete the container).                                                                                                                                           | `environmentId` (string)                                                                                                                                      |
| `env_remove`                 | Remove an environment registration. Must be stopped first.                                                                                                                                                         | `environmentId` (string)                                                                                                                                      |
| `env_wake`                   | Wake a stopped environment by re-provisioning it.                                                                                                                                                                  | `environmentId` (string)                                                                                                                                      |

`env_add` is a discriminated union on `adapterType` — the tool's input schema advertises the exact `adapterConfig` fields valid for each adapter, and unknown fields are rejected. Pick the adapter that matches how the environment is reached:

- **`local`** — a PowerLine already running on this machine. `adapterConfig` (all optional): `host`, `port`.
- **`ssh`** — any host reachable over SSH. This is also how you attach to an **already-running container** that exposes an SSH endpoint. `adapterConfig`: `host` (**required**), `user?`, `sshPort?` (default 22), `identityFile?`, `sshOptions?` (string map), `localPort?`, `env?` (string map).
- **`codespace`** — an existing GitHub Codespace. `adapterConfig`: `codespaceName` (**required**, from `gh codespace list`), `localPort?`, `env?` (string map). Optional top-level `githubAccountId` selects a stored GitHub account for `gh`.
- **`docker`** — spawn a **new** container from an image, or **attach** to an existing one. `adapterConfig` (all optional): `attach` (existing container name/ID — when set, Grackle never creates/stops/removes the container and `image`/`repo`/`volumes` are ignored; use `env_list_docker_containers` to discover candidates), `image` (default `grackle-powerline:latest`), `containerName`, `repo` (`owner/repo` or HTTPS URL), `volumes` (string array), `gpus`, `localPort`, `env` (string map). Optional top-level `githubAccountId` authenticates `gh` for private repo clones.

### Session Tools

Manage AI agent sessions — spawn, monitor, interact, and terminate.

| Tool                 | Description                                                                     | Parameters                                                                                                          |
| -------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `session_spawn`      | Spawn a new agent session with a prompt and optional model config.              | `environmentId` (string), `prompt` (string), `maxTurns?` (int), `personaId?` (string), `workingDirectory?` (string) |
| `session_resume`     | Resume a stopped agent session.                                                 | `sessionId` (string)                                                                                                |
| `session_status`     | List sessions with optional filtering by environment and status.                | `environmentId?` (string), `all?` (boolean, default false)                                                          |
| `session_kill`       | Terminate a running session. Hard kill by default; graceful=true sends SIGTERM. | `sessionId` (string), `graceful?` (boolean, default false)                                                          |
| `session_attach`     | Stream events from a running session for a limited duration.                    | `sessionId` (string), `timeoutSeconds?` (int, default 30, max 300), `maxEvents?` (int)                              |
| `session_send_input` | Send a text message to a session waiting for user input.                        | `sessionId` (string), `text` (string)                                                                               |

### Workspace Tools

Manage workspaces that group tasks, agents, and repositories.

| Tool                           | Description                                                      | Parameters                                                                                                                                                                     |
| ------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `workspace_list`               | List all workspaces with names, descriptions, repos, and status. | `environmentId?` (string)                                                                                                                                                      |
| `workspace_create`             | Create a new workspace.                                          | `name` (string), `environmentId` (string), `description?` (string), `repoUrl?` (string), `workingDirectory?` (string), `useWorktrees?` (boolean), `defaultPersonaId?` (string) |
| `workspace_get`                | Get full details of a workspace by ID.                           | `workspaceId` (string)                                                                                                                                                         |
| `workspace_update`             | Update a workspace's name, description, repo, or settings.       | `workspaceId` (string), `name?`, `description?`, `repoUrl?`, `environmentId?`, `workingDirectory?`, `useWorktrees?`, `defaultPersonaId?`                                       |
| `workspace_archive`            | Archive a workspace, marking it as inactive.                     | `workspaceId` (string)                                                                                                                                                         |
| `workspace_link_environment`   | Link an additional environment to a workspace's pool.            | `workspaceId` (string), `environmentId` (string)                                                                                                                               |
| `workspace_unlink_environment` | Remove a linked environment from a workspace's pool.             | `workspaceId` (string), `environmentId` (string)                                                                                                                               |

### Task Tools

Create, manage, and run tasks within workspaces. Supports hierarchical task trees and dependency gating.

| Tool            | Description                                                                                                                                                                                                     | Parameters                                                                                                                                                                     |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `task_list`     | List tasks with optional search and status filters.                                                                                                                                                             | `workspaceId?` (string), `search?` (string), `status?` (string: `not_started`, `working`, `paused`, `complete`, `failed`)                                                      |
| `task_search`   | Fuzzy search tasks by title or description, ranked by relevance. Returns results with a `relevanceScore` (0.0–1.0, higher = better match). Prefer this over `task_list` when matching approximate descriptions. | `query` (string), `workspaceId?` (string), `limit?` (number, default 10), `status?` (string)                                                                                   |
| `task_create`   | Create a new task with dependencies and parent hierarchy.                                                                                                                                                       | `workspaceId?` (string), `title` (string), `description?` (string), `dependsOn?` (string[]), `parentTaskId?` (string), `canDecompose?` (boolean), `defaultPersonaId?` (string) |
| `task_show`     | Get full details of a task.                                                                                                                                                                                     | `taskId` (string)                                                                                                                                                              |
| `task_update`   | Update a task's title, description, status, or dependencies.                                                                                                                                                    | `taskId` (string), `title?`, `description?`, `status?` (enum), `dependsOn?` (string[]), `sessionId?` (string)                                                                  |
| `task_start`    | Start a task by spawning an agent session. Supports IPC pipe modes.                                                                                                                                             | `taskId` (string), `personaId?` (string), `environmentId?` (string), `notes?` (string), `pipe?` (`sync` \| `async` \| `detach`)                                                |
| `task_delete`   | Permanently delete a task.                                                                                                                                                                                      | `taskId` (string)                                                                                                                                                              |
| `task_complete` | Mark a task as complete (sticky status).                                                                                                                                                                        | `taskId` (string)                                                                                                                                                              |
| `task_resume`   | Resume the latest session for a task.                                                                                                                                                                           | `taskId` (string)                                                                                                                                                              |

### Persona Tools

Manage agent personas — reusable templates defining system prompt, runtime, and model.

| Tool             | Description                                               | Parameters                                                                                                                                                                       |
| ---------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `persona_list`   | List all available personas.                              | _(none)_                                                                                                                                                                         |
| `persona_create` | Create a new persona template (`agent` or `script` type). | `name` (string), `systemPrompt?` (string), `description?` (string), `runtime?` (string), `model?` (string), `maxTurns?` (int), `type?` (`agent` \| `script`), `script?` (string) |
| `persona_show`   | Get full details of a persona.                            | `personaId` (string)                                                                                                                                                             |
| `persona_edit`   | Update an existing persona.                               | `personaId` (string), `name?`, `systemPrompt?`, `description?`, `runtime?`, `model?`, `maxTurns?`, `type?`, `script?`                                                            |
| `persona_delete` | Delete a persona permanently.                             | `personaId` (string)                                                                                                                                                             |

### Knowledge Graph Tools

Search a semantic knowledge graph across sessions and task context.

| Tool                    | Description                                                                                                                                 | Parameters                                                                                                                                  |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `knowledge_search`      | Find how related/prior work in the workspace was approached. Semantic search over the knowledge graph (tasks, sessions, transcript chunks). | `query` (string), `limit?` (int, max 50), `workspaceId?` (string), `expand?` (boolean), `expandDepth?` (int, max 5)                         |
| `knowledge_get_node`    | Retrieve a specific node by ID with optional neighbor expansion.                                                                            | `id` (string), `expand?` (boolean), `expandDepth?` (int, max 5)                                                                             |
| `knowledge_create_node` | Create a new knowledge entry (decision, insight, concept, snippet).                                                                         | `title` (string), `content` (string), `category?` (string), `tags?` (string[]), `workspaceId?` (string), `edges?` (array of `{toId, type}`) |

### IPC Tools

Inter-process communication between parent and child agent sessions.

| Tool                | Description                                                                                                                                                            | Parameters                                                                                                                                       |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ipc_spawn`         | Spawn a child agent session with an IPC pipe.                                                                                                                          | `prompt` (string), `pipe` (`sync` \| `async` \| `detach`), `environmentId` (string), `personaId?` (string), `maxTurns?` (int)                    |
| `ipc_write`         | Write a message to a child session via a file descriptor.                                                                                                              | `fd` (int), `message` (string)                                                                                                                   |
| `ipc_close`         | Close a file descriptor, optionally stopping the child.                                                                                                                | `fd` (int)                                                                                                                                       |
| `ipc_terminate`     | Send SIGTERM to a child session via its fd for graceful shutdown.                                                                                                      | `fd` (int)                                                                                                                                       |
| `ipc_list_fds`      | List your open file descriptors (IPC connections).                                                                                                                     | _(none)_                                                                                                                                         |
| `ipc_list_streams`  | List all active IPC streams with subscriber details and buffer depth.                                                                                                  | _(none)_                                                                                                                                         |
| `ipc_create_stream` | Create a named stream for inter-session communication. Returns an rw fd.                                                                                               | `name` (string), `selfEcho?` (boolean, default `false`)                                                                                          |
| `ipc_attach`        | Grant another session access to a stream you hold an fd on.                                                                                                            | `fd` (int), `targetSessionId` (string), `permission?` (`r` \| `w` \| `rw`), `deliveryMode?` (`sync` \| `async` \| `detach`)                      |
| `ipc_share_stream`  | Share a stream with your parent session. Auto-discovers the parent via the inherited pipe fd, grants access, and sends a `[stream-ref]` notification through the pipe. | `fd?` (int) or `streamName?` (string; exactly one required), `permission?` (`r` \| `w` \| `rw`), `deliveryMode?` (`sync` \| `async` \| `detach`) |

### Log Tools

Retrieve session logs — raw events, formatted transcripts, or live tails.

| Tool       | Description                                                  | Parameters                                                                                                                        |
| ---------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `logs_get` | Retrieve session logs in raw, transcript, or live-tail mode. | `sessionId` (string), `transcript?` (boolean), `tail?` (boolean), `timeoutSeconds?` (int, default 10, max 60), `maxEvents?` (int) |

### Token Tools

Manage secrets that are auto-forwarded to environments.

| Tool           | Description                                         | Parameters                                                                                                 |
| -------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `token_list`   | List configured tokens (values are never returned). | _(none)_                                                                                                   |
| `token_set`    | Set a token for auto-forwarding to environments.    | `name` (string), `value` (string), `type?` (`env_var` \| `file`), `envVar?` (string), `filePath?` (string) |
| `token_delete` | Delete a configured token.                          | `name` (string)                                                                                            |

### Credential Provider Tools

Configure which credential providers (Claude, GitHub, Copilot, Codex) are auto-forwarded.

| Tool                       | Description                          | Parameters                                                                                                        |
| -------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| `credential_provider_list` | List current provider configuration. | _(none)_                                                                                                          |
| `credential_provider_set`  | Set a provider mode.                 | `provider` (`claude` \| `github` \| `copilot` \| `codex`), `value` (`off` \| `on` \| `subscription` \| `api_key`) |

### Config Tools

Read and write global configuration settings.

| Tool                         | Description                               | Parameters           |
| ---------------------------- | ----------------------------------------- | -------------------- |
| `config_get_default_persona` | Get the default persona for new sessions. | _(none)_             |
| `config_set_default_persona` | Set the default persona for new sessions. | `personaId` (string) |

### Usage Tools

Query aggregated token usage and cost data.

| Tool        | Description                                                                         | Parameters                                                                                  |
| ----------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `usage_get` | Get token usage and cost for a session, task, task tree, workspace, or environment. | `scope` (`session` \| `task` \| `task_tree` \| `workspace` \| `environment`), `id` (string) |

### Document Tools

Open files in the user's Grackle document pane (live docs v0, #1396). Unlike widgets, `show_file` carries a **URI reference** rather than baked content — the web reads the file over the AHP resource bridge and live-refreshes it as the file changes on disk.

| Tool        | Description                                                                                                                                                     | Parameters      |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `show_file` | Open a read-only, live-updating view of a file (absolute path) in the user's document pane. Markdown renders richly; other text/code renders with highlighting. | `path` (string) |

### Component & Widget Tools

[MCP Apps](#mcp-apps-ui-widgets) UI rendered inline by capable hosts (and always by Grackle's own chat pane via the broker).

`show_hello_widget` is the Grackle-served demo: it references a static `ui://` resource and appears in `tools/list` **only** when the host advertises the `io.modelcontextprotocol/ui` extension. The **component registry** tools (#1269) let an agent author and reuse components at runtime; they are ordinary scoped tools (always listed) and the rendered output is captured by the broker into the session's chat.

| Tool                 | Description                                                                                                                                                                                                                         | Parameters                                                                                                                            |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `show_hello_widget`  | Display the Grackle hello widget — a minimal interactive MCP Apps UI that echoes the provided message.                                                                                                                              | `message` (string, optional)                                                                                                          |
| `component_register` | Register a reusable component in the workspace. `source` is JSX (`grackle-react`, default) or HTML (`mcp-app-html`). Returns the component id.                                                                                      | `name` (string), `source` (string), `rendererKind` (optional), `description` (optional), `propsSchema` (JSON Schema string, optional) |
| `component_update`   | Update a registered component's source/name/description/props schema; bumps its version.                                                                                                                                            | `id` (string), `source`/`name`/`description`/`propsSchema` (optional)                                                                 |
| `component_list`     | List the reusable components registered in the workspace.                                                                                                                                                                           | —                                                                                                                                     |
| `component_search`   | Keyword-search registered components **and** Grackle's built-in components by name/description. Find a component to reuse before authoring. `builtin:true` results are composed in JSX; others are rendered via `component_render`. | `query` (string), `limit` (optional), `workspaceId` (auto)                                                                            |
| `component_promote`  | Promote a registered component to its own `render_<name>` MCP tool (or demote with `promoted:false`). Resolve by `id` or `name`.                                                                                                    | `id` (optional), `name` (optional), `promoted` (optional, default `true`)                                                             |
| `component_render`   | Render a registered component inline (by `id` or `name`), optionally passing `props`. Props are validated against the component's `propsSchema`.                                                                                    | `id` (optional), `name` (optional), `props` (object, optional)                                                                        |
| `component_show`     | Render a one-off React/JSX component inline against the Grackle component library (no persistence). `source` calls `render(<Component {...props}/>)`; `React`, `props`, and Grackle components are in scope.                        | `source` (string), `props` (object, optional)                                                                                         |
| `widget_show`        | Render a one-off raw-HTML body inline, without persisting it.                                                                                                                                                                       | `body` (string), `props` (object, optional)                                                                                           |

Components are **workspace-scoped**: a session may only register/render components in its own workspace (the `workspaceId` is taken from the session's scoped token). A component's `propsSchema` (a JSON Schema) is validated at register, and render-time `props` are validated against it (via zod's `fromJSONSchema`). Renderers run in the cross-origin sandbox: `mcp-app-html` allows inline scripts (`script-src 'unsafe-inline'`); `grackle-react` runs the React runtime that transpiles + evaluates JSX (`script-src 'unsafe-eval'`) — both kept safe by the iframe origin isolation + restricted `connect-src`.

Grackle's own context-free components (Button, Callout, Spinner, Skeleton, Tooltip, …) are already in the runtime scope and are surfaced by `component_search` as built-ins (`builtin: true`) with their prop schemas — so agents discover and compose them in JSX rather than re-authoring them.

**Composition — components reference each other.** A `grackle-react` component's body can use another registered component as a JSX tag (`<RevenueChart period="Q1"/>`). At render time the server scans the body for capitalized tags, resolves the ones matching a workspace component (built-ins excluded), and recurses — a mini-bundler over the registry graph — shipping the resolved dependency sources to the runtime, which composes them into scope. References are **late-bound**: a reference resolves to the dependency's current version, so updating a shared component updates every consumer. Resolution is workspace-scoped, cycle-safe, and bounded (depth/count/size); only `grackle-react` components compose (raw-HTML can't). Applies to `component_render`, `component_show`, and promoted `render_<name>` tools alike.

**Promoted components → dynamic `render_<name>` tools.** Calling `component_promote` on a registered component surfaces it as its own MCP tool named `render_<name>` (the name is slugged to `[A-Za-z0-9_]`), whose `inputSchema` is the component's `propsSchema` — so it's natively discoverable in `tools/list` and its props are typed and validated. These tools are synthesized **per workspace** for scoped sessions and reflect the current promoted set on each `tools/list`; they render identically to `component_render` ("many tools, one resource"). When a workspace's promoted set changes (promote/demote, or a promoted component edited), the server pushes **`notifications/tools/list_changed`** to that workspace's live sessions so conformant clients re-list automatically (the server advertises the `tools.listChanged` capability). Like the other registry render tools they rely on the in-process broker, so they're listed **only when the MCP server is co-located with the Grackle server** (not on the standalone `npx @grackle-ai/mcp` server). They're authorized by **registry ownership** (the caller's workspace must own the component), not the static tool allowlist. On a name collision the most-recently-updated component wins.

---

## MCP Apps (UI widgets)

Grackle's MCP server is a conformant **MCP Apps** ([SEP-1865](https://modelcontextprotocol.io/)) provider. Hosts that support MCP Apps can fetch and render interactive HTML widgets inline.

How it works:

- **Capability negotiation** — A host advertises support during `initialize` via the `io.modelcontextprotocol/ui` extension (with `mimeTypes` including `text/html;profile=mcp-app`). The server detects this and only lists [Widget Tools](#widget-tools) to capable hosts; other clients see the data-only tools.
- **Resources** — The server declares the `resources` capability and implements `resources/list` and `resources/read`. Widget UIs are served as `ui://…` resources with MIME `text/html;profile=mcp-app`.
- **Tool ↔ resource link** — A widget tool carries `_meta.ui.resourceUri` (and the legacy `_meta["ui/resourceUri"]`) pointing at its `ui://` resource. The host reads the resource and renders it, passing the tool's input and result into the widget.
- **Widget assets** — The widget's browser scripts are served (unauthenticated, since they are non-sensitive static JS) from `/widgets/<name>/…` on the MCP server's HTTP origin.

- **Grackle chat-pane capture** — When an agent in a Grackle session calls a widget tool, the MCP server also pushes a self-contained "widget" event (resource HTML + tool input/result) into that session's event stream, so the widget renders inline in Grackle's own chat UI — independent of whether the agent runtime preserves MCP `_meta`. This covers both the static `show_hello_widget` and the **agent-authored registry** (`widget_register`/`widget_render`/`widget_show`), whose render descriptor the broker reads from the tool result. (This in-process capture only runs when the MCP server is co-located with the Grackle server; the standalone `npx @grackle-ai/mcp` server does not emit chat widget events.)

> **Note:** the widget's scripts load from the MCP server's origin, so a host whose sandbox CSP forbids cross-origin scripts won't render them. Grackle's own chat pane allows the MCP origin in its sandbox CSP, so widgets render there.

The current built-in widget is `show_hello_widget` (resource `ui://grackle/hello-widget`). Try it with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), Claude Desktop, or Grackle's own chat.

---

## Scoped Access

When an agent authenticates with a **scoped token** (issued automatically when a task session is started), tool access is controlled by the task's **persona configuration**.

### Persona-Scoped Tool Filtering

Each persona can define an `allowed_mcp_tools` list that restricts which MCP tools its agents can use. When a scoped token connects:

1. The server looks up the persona's `allowed_mcp_tools` from the token's `personaId` claim.
2. If the persona defines a non-empty tool list, only those tools are exposed via `tools/list`.
3. If the persona has no explicit tool list (empty `allowed_mcp_tools`), the **default scoped set** is used:
   - `task_create`, `task_list`, `task_show`, `task_start`, `task_complete`
   - `session_attach`, `session_send_input`
   - `persona_list`, `persona_show`
   - `ipc_spawn`, `ipc_write`, `ipc_close`, `ipc_terminate`, `ipc_list_fds`, `ipc_create_stream`, `ipc_attach`, `ipc_share_stream`
   - `knowledge_search`, `knowledge_get_node`
   - `logs_get`
   - `workpad_write`, `workpad_read`
   - `schedule_list`, `schedule_show`

### Preset Tool Sets

Predefined presets are available for convenience (via CLI `--mcp-tools-preset` or the web UI):

| Preset         | Description                                                               |
| -------------- | ------------------------------------------------------------------------- |
| `default`      | The 25-tool default scoped set (backward compatible)                      |
| `worker`       | Subset of default — no task creation capabilities                         |
| `orchestrator` | Default + task management, session spawning, persona creation, scheduling |
| `admin`        | Full access to all available tools                                        |

Scoped tokens also enforce workspace isolation — agents can only see tasks within their own workspace. Subtasks created by a scoped agent are automatically parented to the agent's own task. Tool calls to non-permitted tools return an error with a descriptive message listing the available tools.

### Cross-Task / Cross-Session Authorization

Beyond the per-persona allowlist, tools that target a specific task or session are authorized centrally and **fail closed** for scoped (agent) callers: a non-root agent may only act on its **own descendant** tasks/sessions. This covers the mutating tools (`task_update`, `task_delete`, `task_resume`, `task_complete`, `task_start`, `session_kill`, `session_resume`, `session_attach`, `session_send_input`) — an agent cannot delete a sibling's task or kill another agent's session even if it learns the ID. Read tools that resolve a record by ID (`task_show`, `schedule_show`) are gated by workspace membership; a caller with no workspace may read only workspaceless records. The root/system task (the central orchestrator) is exempt. When a task is **deleted**, its scoped tokens are revoked; complete/stop do not revoke (the task can be resumed and resume reuses the original token), so those tokens expire via their TTL.

## Requirements

- Node.js >= 22
- A running [Grackle](https://github.com/nick-pape/grackle) server (`@grackle-ai/cli`)

## License

MIT
