{"slug": "a-board-game-agent-built-using-sanity-context-and-vercel-s-ai-sdk", "title": "A Board Game agent built using Sanity Context and Vercel's AI SDK", "summary": "Jarod Reyes built a CLI board game recommendation agent using Sanity Context and Vercel's AI SDK. The agent queries real-time data from BoardGameGeek via Sanity's Content Lake to recommend games based on natural-language requests. The project demonstrates how MCP servers enable AI agents to access external data for accurate, up-to-date responses.", "body_md": "# A Board Game agent built using Sanity Context and Vercel's AI SDK\n\nAn OpenAI Agent, a board game API, and Sanity Context walk into a bar… the result: a CLI agent that returns exact game recs with real-time data using queries\n\nJarod Reyes\n\nHead of Developer Experience & Community at Sanity\n\nPublished\n\nI love board games. They are a very good excuse to entice my kids away from their books and spend some time with the parents. Plus I think I read somewhere that they increase neuroplasticity and I could use plenty more of that.\n\nI decided to use [Sanity Context](/docs/ai/sanity-context) to build an agent that recommends games based on my interests. Sanity Context is an MCP server. MCP (Model Context Protocol) is an open standard for connecting AI agents to external data and tools. Any agent that supports MCP can connect to it: Claude, GPT-4o, whatever you're building with for your next project. In this tutorial I'll walk through the key steps so you can follow along or use your favorite coding assistant to build something similar.\n\n*If you'd like to follow along with the full code files checkout this repo:*\n\n```\ngit clone https://github.com/sanity-labs/boardgame-agent-cli\n```\n\nWhat we're actually building\n\nBefore we dive in any further let's see this thing in action:\n\nI asked the agent, using GPT-4o, to recommend a new cooperative board game for my family that uses narrative story telling and city-building. When I tried this same query with OpenAI's latest GPT 5.5 model it was not able to find me a board game made later than 2023. Instead my agent recommended a top-rated game from this year called Cozy Stickerville.\n\nNotice that my agent didn't guess. Behind the scenes it ran a [GROQ query](/docs/content-lake/groq-introduction) (GROQ is Sanity's open-source [query language](/glossary/query-language)) against our Content Lake and returned a game that was released in 2026, has both mechanics tagged in their records, directly from BoardGameGeek's (BGG) API. Rad, let's build it.\n\nThe recipe\n\nBy the end of this tutorial you'll have:\n\n- A Sanity project with a\n`boardGame`\n\nschema, populated from BGG's XML API - A configured Context plugin for Sanity Studio that scopes an AI agent to your board game data\n- A rather robust agent module that answers natural-language questions by running real GROQ queries against your own Content Lake (this is where you should spend the most time customizing).\n\nFor this demo specifically I wanted to focus on the build patterns of building an agent with Context and show that agents can live in different interfaces - which means there is no frontend. It's a CLI, ya' dig?\n\nPrerequisites\n\n—[Node.js](/glossary/node-js)20+[nodejs.org](https://nodejs.org). Run`node --version`\n\nto confirm.**A Sanity account**— free at[sanity.io](/)\n\nYou can create an account usingas shown below`npm create sanity@latest`\n\n**A BoardGameGeek XML API token**— registration is required. Create an application at[boardgamegeek.com/applications](https://boardgamegeek.com/applications), then create a token and send it as`Authorization: Bearer …`\n\non every API request. See[Using the XML API](https://boardgamegeek.com/using_the_xml_api).**An OpenAI API key**— the agent script uses GPT-4o by default. You can swap in any[Vercel AI SDK provider](https://sdk.vercel.ai/providers/ai-sdk-providers).- A\n**deployed Sanity Studio**— studio is where you configure Context for the agent.\n\nCreate a Sanity project\n\n```\nnpm create sanity@latest -- --template clean --dataset production \n--project-name=bgg-agent-tutorial --output-path bgg-agent\n```\n\nFollow the prompts. When asked to install the MCP server, choose **yes**, it allows you and your agent to interact with Sanity's docs and tools directly.\n\n**Expected output:**\n\n```\n✔ Running pnpm install\n✅ Success! Your Studio has been created.\n(cd ~/.../bgg-agent to navigate to your new project directory)\n\nGet started by running pnpm dev to launch your Studio's development server\nOther helpful commands:\n\nnpx sanity docs browse to open the documentation in a browser\nnpx sanity manage to open the project settings in a browser\nnpx sanity help to explore the CLI manua\n```\n\nYour project ID will now be in will now be in `sanity.config.ts`\n\n. Keep it handy, we'll need to add this to the .env file.\n\n**Or clone from GitHub:** Clone the repo, run `npm install`\n\n, then run:\n\n```\nnpx sanity init --env\n```\n\nThis will walk you through logging in and selecting (or creating) a project, and automatically write your `projectId`\n\nand `dataset`\n\nto a `.env`\n\nfile. Then copy any remaining variables from `.env.example`\n\nand update `sanity.config.ts`\n\nand `sanity.cli.ts`\n\nto match.\n\nDefine the board game schema\n\nIn Sanity, a **schema** is a TypeScript definition that describes the shape of your documents, what fields they have, what types those fields are, and how they're validated. It's the contract between your content and everything that reads it: Studio uses it to render the right editing form, your frontend uses it to know what to expect, and Context uses it to expose your data structure to the AI agent.\n\nThe default Studio template includes a placeholder `schemaTypes/index.ts`\n\nwith a sample type. We're going to replace that with the actual `boardGame`\n\ndocument type and split it into its own file while we're at it, which is the convention for maintainable Sanity projects.\n\nCreate a new file at `schemaTypes/documents/board-game.ts`\n\n:\n\n``` js\n// schemaTypes/documents/board-game.ts\nimport {defineField, defineType, defineArrayMember} from 'sanity'\nimport {ControlsIcon} from '@sanity/icons'\n\nexport const boardGame = defineType({\n  name: 'boardGame',\n  title: 'Board Game',\n  type: 'document',\n  icon: ControlsIcon,\n  fields: [\n    defineField({\n      name: 'bggId',\n      title: 'BGG ID',\n      type: 'number',\n      validation: (rule) => rule.required(),\n    }),\n    defineField({\n      name: 'name',\n      title: 'Name',\n      type: 'string',\n      validation: (rule) => rule.required(),\n    }),\n    defineField({name: 'yearPublished', title: 'Year Published', type: 'number'}),\n    defineField({name: 'minPlayers', title: 'Min Players', type: 'number'}),\n    defineField({name: 'maxPlayers', title: 'Max Players', type: 'number'}),\n    defineField({name: 'minPlaytime', title: 'Min Playtime (min)', type: 'number'}),\n    defineField({name: 'maxPlaytime', title: 'Max Playtime (min)', type: 'number'}),\n    defineField({name: 'averageRating', title: 'BGG Average Rating', type: 'number'}),\n    defineField({name: 'weight', title: 'Complexity Weight (1–5)', type: 'number'}),\n    defineField({\n      name: 'categories',\n      title: 'Categories',\n      type: 'array',\n      of: [defineArrayMember({type: 'string'})],\n    }),\n    defineField({\n      name: 'mechanics',\n      title: 'Mechanics',\n      type: 'array',\n      of: [defineArrayMember({type: 'string'})],\n    }),\n    defineField({\n      name: 'designers',\n      title: 'Designers',\n      type: 'array',\n      of: [defineArrayMember({type: 'string'})],\n    }),\n  ],\n})\n```\n\nThen update `schemaTypes/index.ts`\n\nto import from it:\n\n``` js\nimport {boardGame} from './board-game'\n\nexport const schemaTypes = [boardGame]\n```\n\nThe `mechanics`\n\nand `categories`\n\narrays are what make the GROQ queries genuinely useful later - they let the agent filter by structured tags rather than approximate text matching.\n\nNext we need to deploy the schema to Content Lake so the Sanity Context server knows your data shape, we'll use the following `sanity deploy`\n\ncommand which has the added benefit of deploying our studio as well.\n\n```\nnpx sanity deploy\n```\n\nPull board game data into Content Lake\n\nInstall the XML parsing package:\n\n```\nnpm install fast-xml-parser\n```\n\nCreate `ingest.mjs`\n\nat the project root. This is not the full file, but gives you the shape. You can see my version here: [https://github.com/jarodreyes/boardgame-sanity-cli/blob/main/ingest.mjs](https://github.com/jarodreyes/boardgame-sanity-cli/blob/main/ingest.mjs)\n\n``` js\n// ingest.mjs - Fetch top 50 games from BoardGameGeek using the XML API\nimport {getCliClient} from 'sanity/cli'\nimport {XMLParser} from 'fast-xml-parser'\n\n// getCliClient reads projectId, dataset, and apiVersion from your sanity.config.ts\n// automatically. Run this script with: sanity exec ingest.mjs --with-user-token\nconst client = getCliClient({useCdn: false})\n\nconst parser = new XMLParser({\n  ignoreAttributes: false,\n  attributeNamePrefix: '@_',\n  isArray: (name) => ['item', 'name', 'link'].includes(name),\n})\n\n// add function to set Auth Headers for BGG API\n// add function to batch request (20 IDs max per BGG call)\n// add function to retry on fetch fail\n...\n// This is not the full code sample needed to handle ingesting BGG data.\n// For the full file, which handles batching, auth, and\n// a bunch of other specific nuances for the BGG API, view it at:\n// https://github.com/jarodreyes/boardgame-sanity-cli/blob/main/ingest.mjs\n```\n\nCreate a `.env`\n\nfile at the project root. For Sanity, go to `sanity.io/manage`\n\n, open your project, click **API → Tokens**, and create one with **Editor** permissions. For BGG, use the bearer token from [Applications → Tokens](https://boardgamegeek.com/applications) for your registered app.\n\n```\nSANITY_PROJECT_ID=your_project_id\nBGG_API_TOKEN=your_bgg_bearer_token\n```\n\nRun the ingestion:\n\n```\nsanity exec ingest.mjs --with-user-token\n```\n\n#### Why we use getCliClient()\n\n`getCliClient()` picks up your projectId, dataset, and apiVersion directly from sanity.config.ts — no duplication. The `--with-user-tokenflag` passes your active sanity login session to the script, so you don't need a separate SANITY_API_TOKEN environment variable for local ingestion runs. You only need an API token when running in a non-interactive environment like CI.\n\n**Expected output:**\n\n```\nFetched 50 IDs from BGG hot list\nFetching details for 58 games...\nFetching game details batch 1/3 (20 games)...\nFetching game details batch 2/3 (20 games)...\nFetching game details batch 3/3 (18 games)...\nImported 58 board games into Content Lake\n```\n\nBGG's `thing`\n\nendpoint accepts at most **20 IDs per request**; the script batches automatically and waits 2 seconds between batches.\n\nStart the Studio (`npm run dev`\n\n, then open `localhost:3333`\n\n). After the default small ingest you should see on the order of **~60** board games; each with ratings, complexity weights, mechanics, categories, player counts, playtime ranges, and designer credits from BGG.\n\nInstall Sanity Context\n\nSanity Context is an MCP server. Once configured in your Studio, it gives any MCP-compatible agent three tools to work with: `initial_context`\n\n(a compressed overview of your schema and document count), `groq_query`\n\n(live GROQ access to your Content Lake), and `schema_explorer`\n\n(field-level inspection so the agent builds accurate queries without guessing at field names).\n\n[Sanity ships a skill](/docs/ai/skills) that automates the Context configuration. Run it from your coding assistant and it handles the plugin install, Context document creation, and MCP URL setup.\n\nTo set it up manually:\n\n```\nnpm install @sanity/agent-context\n```\n\nOpen `sanity.config.ts`\n\nand add the plugin:\n\n``` js\nimport {defineConfig} from 'sanity'\nimport {structureTool} from 'sanity/structure'\nimport {agentContextPlugin} from '@sanity/agent-context/studio'\nimport {schemaTypes} from './schemaTypes'\n\nexport default defineConfig({\n  name: 'default',\n  title: 'BGG Agent',\n  projectId: 'your-project-id',\n  dataset: 'production',\n  plugins: [structureTool(), agentContextPlugin()],\n  schema: {types: schemaTypes},\n})\n```\n\nRestart the Studio after the config change.\n\nCreate the Context document\n\nIn the Studio sidebar, you'll see a new **Context** section. Click it, then **Create new Sanity Context**. Fill in these fields:\n\nSave the document. The Studio generates an **MCP URL** (the API path includes a date version, e.g. `v2026-04-09`\n\n— use exactly what Studio shows, not a guess):\n\n```\nhttps://api.sanity.io/vYYYY-MM-DD/agent-context/your-project-id/production/board-games\n```\n\nCopy it.\n\nConnect the agent\n\nCreate `agent.mjs`\n\nat the project root:\n\n```\n// Requires: SANITY_CONTEXT_MCP_URL, SANITY_API_READ_TOKEN (Viewer), OPENAI_API_KEY\nimport 'dotenv/config'\nimport {randomUUID} from 'node:crypto'\nimport {generateText, stepCountIs} from 'ai'\nimport {createMCPClient} from '@ai-sdk/mcp'\nimport {openai} from '@ai-sdk/openai'\nimport {createClient} from '@sanity/client'\nimport {sanityInsightsIntegration} from './agent-insights-telemetry.mjs'\nimport boxen from 'boxen'\nimport chalk from 'chalk'\n```\n\n#### AGENT_SYSTEM_PROMPT\n\nThe system prompt is worth understanding because it controls how the agent queries your data. It sets the query strategy (what to try first, how to widen if a query returns zero results), the BGG-specific field conventions (exact tag strings like \"Co-operative Play\" rather than the casual phrase \"co-op\"), and the temporal logic (how the agent interprets \"new\" or \"recent\" relative to the current date).\n\nThe prompt in this repo was written with *help from the Sanity Context skill* and iterated against real queries. It's detailed because GROQ is precise. A vague \"find cooperative games\" won't match anything if the agent guesses at a field name. The tradeoff is token cost per call: a longer system prompt means slightly higher cost per query, but it cuts down on the number of follow-up tool calls the model needs to make.\n\nYou'll want to customize this for your own data. The field names, array values, and fallback logic are all specific to the BGG dataset. Swap those out for your schema and you have a solid starting point.\n\nThe agent script depends on two more packages: the **Vercel AI SDK** (`ai`\n\n) and an OpenAI provider (`@ai-sdk/openai`\n\n). Install them with npm:\n\n```\nnpm install ai @ai-sdk/openai\n```\n\nWorth pausing here on what an agent loop actually is. When you call generateText, the model runs in a loop rather than responding once and stopping. The model decides to call a tool, the SDK executes that tool against your MCP server, the result comes back to the model, and the model decides what to do next. That cycle continues until the model has enough information to give a final answer, or until it hits the stopWhen limit.\n\nIn this agent, that means the model is running live GROQ queries against your Content Lake mid-conversation, reading real results, and deciding whether it needs more data before responding. The retrieval happens inside the loop, driven by the model.\n\nThe OpenAI provider is the adapter that connects generateText to GPT-4o. The Vercel AI SDK supports other providers too, so if you'd rather use Anthropic or Gemini, swap the provider import and you're good.\n\nAdd three more variables to `.env`\n\n. Create a Sanity API token with **Viewer** permissions:\n\n```\nSANITY_CONTEXT_MCP_URL=<paste the full URL from the Sanity Context document in Studio>\nSANITY_API_READ_TOKEN=your_viewer_token\nOPENAI_API_KEY=your_openai_key\n```\n\nThis pattern works for your data too\n\nNow you can start building your own agents on top of your content. Here are a few queries that show just how precise this gets:\n\n```\nnpm run agent \"Find games that combine Worker Placement with Deck Building\"\n\nnpm run agent \"What is the BGG complexity weight and average rating of Wingspan?\"\n\nnpm run agent \"Which game in the database has the most mechanics listed?\"\n```\n\nThe agent runs a GROQ expression against real records, reads the result, and gives you an exact answer. It counts array lengths, finds maximums, traverses references, whatever your schema supports.\n\nSwap out board games for a product catalog, a documentation site, a recipe database, or a content library. The architecture is the same. The agents you build for your users get smarter the more you invest in your content, not because a new model dropped, but because your data got better.\n\nWhat's happening under the hood\n\nWhen you ask the agent a question, it reaches the Sanity Context MCP server. The agent doesn't retrieve context and answer from memory. It runs queries, reads the results, and builds its response from live data. The instructions in the Context document guide how it frames and presents those results.\n\nOn top of all this, you can wire up any agent, using any AI you want, and have complete control of the experience for you or your end user/customer… all using Javascript/Typescript/language of your choice.\n\nYour Sanity content becomes structured data the agent can query with the precision of a database.\n\nI am proud to say that after the agent recommended Cozy Stickerville, I actually went and bought it, excited to play it with the fam tonight.\n\nIf you are building with Sanity Context or want help figuring out how to use it for your work [join our Discord](https://discord.gg/Qzb9QpKuR). We will be having some live sessions showing off agents built with Sanity and it'll be a great place to ask questions.", "url": "https://wpnews.pro/news/a-board-game-agent-built-using-sanity-context-and-vercel-s-ai-sdk", "canonical_source": "https://www.sanity.io/blog/context-board-game-agent", "published_at": "2026-05-26 03:39:43+00:00", "updated_at": "2026-06-18 19:09:45.868246+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "natural-language-processing", "large-language-models"], "entities": ["Sanity", "Vercel", "OpenAI", "BoardGameGeek", "GPT-4o", "Jarod Reyes"], "alternates": {"html": "https://wpnews.pro/news/a-board-game-agent-built-using-sanity-context-and-vercel-s-ai-sdk", "markdown": "https://wpnews.pro/news/a-board-game-agent-built-using-sanity-context-and-vercel-s-ai-sdk.md", "text": "https://wpnews.pro/news/a-board-game-agent-built-using-sanity-context-and-vercel-s-ai-sdk.txt", "jsonld": "https://wpnews.pro/news/a-board-game-agent-built-using-sanity-context-and-vercel-s-ai-sdk.jsonld"}}