---
title: Todos playground
description: Hosted MCP service at mcp.ggui.ai/playground/todos — a per-user persistent todo list demo. Try generative UI rendering shared state.
---

:::caution[Coming soon]
This page describes the **managed hosted path** (`mcp.ggui.ai` / `console.ggui.ai`), which is **not yet live** — it is not part of GGUI Preview 0.1.0. The self-hosted path is available today — start with the [Quickstart](/oss-quickstart/). This page is kept as a preview of the managed path and goes live when hosted ggui ships.
:::

`mcp.ggui.ai/playground/todos` is a first-party hosted MCP service that exposes a per-user persistent todo list. Sign in to [console.ggui.ai](/clients/console/), point your agent at the URL, and watch the agent call a tool while ggui renders the list inline as generative UI. State survives refresh, re-login, and host swaps — todos are pinned to your Cognito identity, not the session.

This is the smallest "real" MCP-driven surface we ship. No code to write, no schema to learn — the four tools are described by the server's `tools/list` response, and the host (Claude Desktop, Goose, your own agent) drives them.

## What it demonstrates

- **Agent → tool → generative UI**: the agent calls a tool, the runtime feeds the result into a blueprint, the host renders the resulting list inline.
- **Per-user persistence**: log out, come back tomorrow from a different host — same todos.
- **Cognito identity at the MCP edge**: tool handlers read `ctx.userId` directly. No second auth layer; the `Authorization: Bearer ggui_user_*` header that authenticated the MCP session also scopes the data.

If you want the deep "why" of the service model that makes this work, see [MCP services](/architecture/mcp-services/) — `playground-todos` is one of three reference services in `cloud/mcp-services/`.

## The four tools

All four are scoped to the caller's `ctx.userId`. Anonymous calls (no bearer token) return an explicit `authentication required` error.

| Tool           | Input      | Output                                 | Notes                                                                              |
| -------------- | ---------- | -------------------------------------- | ---------------------------------------------------------------------------------- |
| `todos_list`   | `{}`       | `{ todos: Todo[] }`                    | Oldest-first by `createdAt`. Empty array when the user has none — never `null`.    |
| `todos_add`    | `{ text }` | `{ todo }`                             | `text` trimmed; empty or 500+ char bodies rejected at the schema boundary.         |
| `todos_toggle` | `{ id }`   | `{ found: boolean, todo: Todo\|null }` | Flips `done`. Missing id and cross-user id collapse to one shape (see Auth model). |
| `todos_delete` | `{ id }`   | `{ ok: boolean }`                      | `ok: true` only when the caller owned a todo with that id.                         |

A `Todo` row is `{ id, text, done, createdAt }`. `createdAt` is an ISO-8601 string; `id` is opaque (treat it as a black-box handle returned by `todos_add` or `todos_list`).

## Auth model

Cognito-gated. Every handler resolves `ctx.userId` from the bearer key minted by [console.ggui.ai](/clients/console/) and rejects calls without one. The console is the only place those keys come from — either through an OAuth ceremony from an MCP-Apps-aware host or by minting one manually at `/keys/connector`.

Cross-user probing is structurally prevented. `todos_toggle` and `todos_delete` collapse two failure modes into one wire shape:

- The id doesn't exist.
- The id exists but belongs to a different user.

Both return the same negative response (`found: false` / `ok: false`). A caller can't enumerate other users' id space.

## Try it from Claude Desktop

If you've already connected `mcp.ggui.ai` from the [Claude Desktop walkthrough](/clients/claude-desktop/), Claude Desktop will pick up `playground-todos` once you add the second connector. In Settings → Connectors → Add custom connector, paste:

```
https://mcp.ggui.ai/playground/todos
```

The OAuth ceremony runs again against `console.ggui.ai`; approve it the same way you did for the main `mcp.ggui.ai` connector. A new `ggui_user_*` row appears in [`/keys/connector`](https://console.ggui.ai/keys/connector) labelled with the connector name.

Once connected, try prompts like:

> What's on my todo list?

> Add "ship the launch post" to my todos, then show me everything pending.

> Mark the first todo as done.

Claude calls `todos_list` / `todos_add` / `todos_toggle`, and the table updates in the chat. Refresh Claude Desktop, ask again — same list comes back.

## Try it from your own agent

The Todos playground is a vanilla remote MCP server. Anything that speaks `streamable-http` MCP + bearer auth works. For Claude Agent SDK:

```typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

const apiKey = process.env.GGUI_USER_KEY!; // ggui_user_*

const mcpServers = {
  todos: {
    type: "http" as const,
    url: "https://mcp.ggui.ai/playground/todos",
    headers: { Authorization: `Bearer ${apiKey}` },
  },
};

for await (const msg of query({
  prompt: "Add 'try the playground' to my todos, then list everything.",
  options: {
    model: "claude-sonnet-4-6",
    mcpServers,
    allowedTools: [
      "mcp__todos__todos_list",
      "mcp__todos__todos_add",
      "mcp__todos__todos_toggle",
      "mcp__todos__todos_delete",
    ],
  },
})) {
  // consume the SDK message stream
}
```

The Claude Agent SDK namespaces every MCP tool as `mcp__<serverName>__<toolName>` — with `serverName: "todos"` here, the tool ids land as `mcp__todos__todos_list`, `mcp__todos__todos_add`, and so on. The pattern is the same for any other host that takes an MCP-server URL.

For the full agent-side wiring (system prompt, error handling, streaming), see the [Claude Agent example](/examples/claude-agent/). The same pattern applies — swap `mcp.ggui.ai` for `mcp.ggui.ai/playground/todos`, narrow the tool whitelist to the four todos tools.

## How it's built

The service is closed-source (`@ggui-private/mcp-playground-todos` — not published or mirrored), but its shape is simple enough to describe:

- a package entry exporting a `createPlaygroundTodosHandlers({ store })` convenience factory,
- one file per tool (list / add / toggle / delete), each ~50 lines,
- a `TodoStore` interface with an in-memory implementation for tests; production swaps in a DynamoDB-backed store against the same interface.

To build your own service in this shape, use the OSS **`McpService` mount primitive** in [`@ggui-ai/mcp-server`](/architecture/mcp-services/) — the same seam this service mounts through. The [MCP services architecture](/architecture/mcp-services/) page covers mount points, the `AuthAdapter` contract, and how `ctx.userId` is resolved end-to-end.