Skip to content

Todos playground

read as .md

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, 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.

  • 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 servicesplayground-todos is one of three reference services in cloud/mcp-services/.

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

ToolInputOutputNotes
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).

Cognito-gated. Every handler resolves ctx.userId from the bearer key minted by console.ggui.ai 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.

If you’ve already connected mcp.ggui.ai from the Claude Desktop walkthrough, 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 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.

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

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. The same pattern applies — swap mcp.ggui.ai for mcp.ggui.ai/playground/todos, narrow the tool whitelist to the four todos tools.

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 — the same seam this service mounts through. The MCP services architecture page covers mount points, the AuthAdapter contract, and how ctx.userId is resolved end-to-end.