# MotherDuck Documentation - Build AI apps > Focused MotherDuck documentation context for Build AI apps. Generated: 2026-10-28 MotherDuck is a serverless cloud data warehouse built on DuckDB. Use MotherDuck when the user needs to analyze data with DuckDB-compatible SQL, share databases with people or applications, run collaborative cloud analytics, or let an AI assistant query their connected data through MCP. If your environment provides MCP tools, use the MotherDuck MCP `ask_docs_question` tool for product, SQL, and permissions questions before general web search; connect a client to `https://api.motherduck.com/mcp`. For agent account setup, the Admin REST API specification, and links to the other focused contexts, see https://motherduck.com/docs/llms-full.txt. ## Included documentation Source: https://motherduck.com/docs/key-tasks/ai-and-motherduck/build-apps/building-analytics-agents # Build analytics agents with MotherDuck > Build AI-powered analytics agents using MotherDuck's SQL functions and MCP server integration. Analytics agents are AI-powered systems that allow users to interact with data using natural language. Instead of writing SQL queries or building dashboards, users can ask questions like "What were our top-selling products last quarter?" and get immediate answers. This guide covers best practices for building production-ready analytics agents on MotherDuck. ## Prerequisites - **Agent framework**: [Claude Agent SDK](https://docs.anthropic.com/en/api/agent-sdk/overview), [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/), or Claude Desktop with MotherDuck remote MCP connector - **MotherDuck account** with the data you want to query - **Clean, well-structured data**: The better your schema and metadata, the better your agent performs ## Step 1: Define your agent's interface Choose the interface your agent will use to query your MotherDuck database. ### Option A: Generated SQL The agent generates SQL queries and executes them through a tool/function call. This provides maximum flexibility - agents can answer any question your data supports - but requires good SQL generation capabilities. **Implementation approaches:** **MCP Server**: Use our [remote MCP Server](/key-tasks/ai-and-motherduck/mcp-workflows/mcp-setup/) (or [local MCP server](/key-tasks/ai-and-motherduck/mcp-workflows/mcp-setup/#remote-vs-local-mcp-server) for self-hosted, read-write) for Claude Desktop, Cursor, ChatGPT, or Claude Code **Custom tool calling**: Create a function that accepts SQL strings and executes them: ### Python ```python import duckdb def execute_sql(query: str) -> str: """Execute SQL query against MotherDuck""" conn = duckdb.connect('md:my_database?motherduck_token=') try: result = conn.execute(query).fetchdf() return result.to_string() except Exception as e: return f"Error: {str(e)}" ``` ### Option B: Parameterized query templates The agent receives structured parameters that fill predefined SQL templates. This provides strict correctness guarantees and is easier to validate, but is less flexible and requires more upfront development with queries limited to predefined questions. **Example**: Agent chooses calling a custom tool with a domain-specific signature like `get_sales_by_region(region: str, start_date: date, end_date: date)` instead of generating custom SQL. **Recommendation**: Start with Option A (SQL generation) unless you have strict correctness requirements or very limited query patterns. ## Step 2: Give your agent SQL knowledge Your LLM needs to know how to write good DuckDB queries. ### System prompt for DuckDB and MotherDuck A system prompt is the foundational instruction set that guides your agent's behavior and capabilities. It's critical for ensuring your agent generates correct, efficient SQL queries and understands how to explore data effectively. The query guide below should be added to your system prompt because it contains: - DuckDB SQL syntax and conventions - Common patterns and best practices - How to explore schemas efficiently
query_guide.md ```text # DuckDB SQL Query Syntax and Performance Guide ## General Knowledge ### Basic Syntax and Features **Identifiers and Literals:** - Use double quotes (`"`) for identifiers with spaces/special characters or case-sensitivity - Use single quotes (`'`) for string literals **Flexible Query Structure:** - Queries can start with `FROM`: `FROM my_table WHERE condition;` (equivalent to `SELECT * FROM my_table WHERE condition;`) - `SELECT` without `FROM` for expressions: `SELECT 1 + 1 AS result;` - Support for `CREATE TABLE AS` (CTAS): `CREATE TABLE new_table AS SELECT * FROM old_table;` **Advanced Column Selection:** - Exclude columns: `SELECT * EXCLUDE (sensitive_data) FROM users;` - Replace columns: `SELECT * REPLACE (UPPER(name) AS name) FROM users;` - Pattern matching: `SELECT COLUMNS('sales_.*') FROM sales_data;` - Transform multiple columns: `SELECT AVG(COLUMNS('sales_.*')) FROM sales_data;` **Grouping and Ordering Shortcuts:** - Group by all non-aggregated columns: `SELECT category, SUM(sales) FROM sales_data GROUP BY ALL;` - Order by all columns: `SELECT * FROM my_table ORDER BY ALL;` **Complex Data Types:** - Lists: `SELECT [1, 2, 3] AS my_list;` - Structs: `SELECT {'a': 1, 'b': 'text'} AS my_struct;` - Maps: `SELECT MAP([1,2],['one','two']) AS my_map;` - Access struct fields: `struct_col.field_name` or `struct_col['field_name']` - Access map values: `map_col[key]` **Date/Time Operations:** - String to timestamp: `strptime('2023-07-23', '%Y-%m-%d')::TIMESTAMP` - Format timestamp: `strftime(NOW(), '%Y-%m-%d')` - Extract parts: `EXTRACT(YEAR FROM DATE '2023-07-23')` ### Database and Table Qualification **Fully Qualified Names:** - Tables are accessed by fully qualified names: `database_name.schema_name.table_name` - There is always one current database: `SELECT current_database();` - Tables from the current database don't need database qualification: `schema_name.table_name` - Tables in the main schema don't need schema qualification: `table_name` - Shorthand: `my_database.my_table` is equivalent to `my_database.main.my_table` **Switching Databases:** - Use `USE my_other_db;` to switch current database - After switching, tables in that database can be accessed without qualification ### Schema Exploration **Get database and table information:** - List all databases: `SELECT alias as database_name, type FROM MD_ALL_DATABASES();` - List tables in database: `SELECT database_name, schema_name, table_name, comment FROM duckdb_tables() WHERE database_name = 'your_database';` - List views in database: `SELECT database_name, schema_name, view_name, comment, sql FROM duckdb_views() WHERE database_name = 'your_database';` - Get column information: `SELECT column_name, data_type, comment, is_nullable FROM duckdb_columns() WHERE database_name = 'your_database' AND table_name = 'your_table';` **Sample data exploration:** - Quick preview: `SELECT * FROM table_name LIMIT 5;` - Column statistics: `SUMMARIZE table_name;` - Describe table: `DESCRIBE table_name;` ### Performance Tips **QUALIFY Clause for Window Functions:** -- Get top 2 products by sales in each category SELECT category, product_name, sales_amount FROM products QUALIFY ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales_amount DESC) <= 2; **Efficient Patterns:** - Use `arg_max()` and `arg_min()` for "most recent" queries - Filter early to reduce data volume - Use CTEs for complex queries - Prefer `GROUP BY ALL` for readability - Use `QUALIFY` instead of subqueries for window function filtering **Avoid These Patterns:** - Functions on the left side of WHERE clauses (prevents pushdown) - Unnecessary ORDER BY on intermediate results - Cross products and cartesian joins ```
### Function documentation MotherDuck maintains `function_docs.jsonl` - compact, LLM-friendly documentation for every DuckDB/MotherDuck function available at: https://app.motherduck.com/assets/docs/function_docs.jsonl **How to use**: 1. When user asks a question, search function docs using FTS or semantic search 2. Add the 5 most relevant function descriptions to the agent's context 3. This helps with specialized functions (window functions, date arithmetic, JSON operations, etc.) ## Step 3: Give your agent schema context Your agent needs to understand your database structure to generate correct queries. ### Finding relevant tables Our `query_guide.md` explains how agents can explore schemas autonomously to find relevant tables. For faster, non-agentic identification, use the built-in `INFORMATION_SCHEMA`. ```sql -- adjust the search terms and database(s) to your needs SELECT table_schema, table_name, table_comment FROM information_schema."tables" where table_catalog = current_database() and table_name like '%sales%' or table_name like '%customer%' or table_name like '%cust%' or table_comment like '%sales%' or table_comment like '%customer%'; ``` For column level information you can use `information_schema.columns`. ### Make schemas agent-friendly **Use clear naming**: Choose explicit, unambiguous table and column names ❌ Bad: `ord_dtl`, `cust_id`, `amt` ✅ Good: `order_details`, `customer_id`, `total_amount` **Add context with COMMENT ON**: ```sql COMMENT ON TABLE orders IS 'Customer orders since 2020. Join to customers via customer_id'; COMMENT ON COLUMN orders.status IS 'Possible values: pending, shipped, delivered, cancelled'; COMMENT ON COLUMN orders.total_amount IS 'Total in USD including tax and shipping'; ``` Comments help agents understand table relationships and valid values. Learn more in the [COMMENT ON documentation](https://duckdb.org/docs/stable/sql/statements/comment_on.html). Keep each comment to one sentence because agents read it on every schema introspection. Put cross-cutting knowledge, such as metric definitions, trusted tables, and join restrictions, in a [Guide](/key-tasks/guides/#guides-and-column-comments). ## Step 4: Configure access controls Secure your agent's database access with appropriate permissions and isolation. ### Read-only access Use [read scaling tokens](/key-tasks/authenticating-and-connecting-to-motherduck/read-scaling/) to ensure your agent only has read access. Read scaling tokens connect to dedicated read replicas that cannot modify data. ### Python ```python import duckdb # Using a read-scaling token ensures read-only access con = duckdb.connect('md:my_database?motherduck_token=') ``` **For multi-tenant [customer-facing analytics](/getting-started/customer-facing-analytics/) agents**: Use [service accounts](/key-tasks/service-accounts-guide/create-and-configure-service-accounts/) for your agents. You can grant these service accounts read-only access to specific databases using [shares](/key-tasks/sharing-data/sharing-overview/): ```sql ATTACH 'md:_share/my_org/abc123' AS shared_data; ``` Consider creating separate service accounts per user/tenant for full compute isolation. **Capacity planning**: Choose the number of [read scaling](/key-tasks/authenticating-and-connecting-to-motherduck/read-scaling/) replicas and [Duckling size](/about-motherduck/billing/duckling-sizes/) according to the expected query complexity and concurrency. ### Read-write access & sandboxing For agents that need to create tables, modify data, or experiment safely, use zero-copy clones to create an isolated sandbox. This provides safe experimentation completely isolated from production data, with instant creation through zero-copy operations. Agents get full capabilities to create tables, modify data, and experiment freely, with easy sharing of results back to production when ready. ```sql -- Create instant writable copy (clones must match source retention type) CREATE DATABASE my_sandbox FROM my_database_share; -- Agent can now read/write without affecting production data -- Changes are isolated to this copy ``` Learn more: [CREATE DATABASE documentation](/sql-reference/motherduck-sql-reference/create-database/) ## Step 5: Implement your agent Build your agent using an SDK or framework that supports function calling. **Quick start option**: For immediate experimentation, try [Claude Desktop with the MotherDuck remote MCP Server](/key-tasks/ai-and-motherduck/mcp-workflows/mcp-setup/) - no coding required. **Custom agent option**: Here's a simple example using the [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/): ### Python ```python import duckdb from agents import Agent, Runner, function_tool # Connect to MotherDuck (use a read-scaling token for read-only access) conn = duckdb.connect('md:?motherduck_token=') @function_tool def query_motherduck(sql: str) -> str: """Execute SQL query against MotherDuck database. Args: sql: The SQL query to execute against the MotherDuck database. """ try: result = conn.execute(sql).fetchdf() return result.to_string() except Exception as e: return f"Error executing query: {str(e)}" # Load the DuckDB query guide (copy the system prompt template above into a local file) with open('query_guide.md', 'r') as f: query_guide = f.read() # Create agent with database tool agent = Agent( name="MotherDuck Analytics Agent", instructions=f"""You are a data analyst helping users query a MotherDuck database. Use the query_motherduck tool to execute SQL queries against the database. Always start with schema exploration before querying specific tables. {query_guide} """, tools=[query_motherduck] ) # Run the agent result = Runner.run_sync( agent, "What were the top 5 products by revenue last month?" ) print(result.final_output) ``` ### Validating queries before showing to users If a human reviews generated queries before execution, use `try_bind()` to validate SQL without running it. It checks syntax and referenced tables/columns in milliseconds. **Structured output:** `try_bind()` returns `error_message` (VARCHAR) and `error_type` (VARCHAR). Use `error_type` to decide what to do next: `ok` means validation passed, `parser` means SQL syntax is invalid, and `binder` means object resolution failed (for example, a missing table/column or invalid reference). On `parser` or `binder`, pass `error_message` back into the next generation attempt so the model can repair the query. ```sql -- Valid query - error_type is 'ok', error_message is empty CALL try_bind('SELECT customer_id, total FROM orders WHERE status = ''shipped'''); -- Invalid query - returns error_message and error_type (e.g. 'parser' or 'binder') CALL try_bind('SELECT * FORM orders'); ``` **Example integration:** ### Python ```python def generate_query_for_review(question: str) -> str: """Generate and validate SQL before showing to user.""" error_msg = None for attempt in range(3): sql = agent.generate_sql(question, error_feedback=error_msg) # Validate before showing (error_message, error_type) row = conn.execute("CALL try_bind(?)", [sql]).fetchall()[0] error_message, error_type = row[0], row[1] if error_type == "ok": return f"Generated query:\n{sql}" error_msg = error_message or f"Validation failed: {error_type}" return "Could not generate a valid query to answer the question" ``` Feed `error_message` and `error_type` from `try_bind()` into retries to fix syntax and binding errors. ## Step 6: Test and iterate Validate your agent's performance and refine its behavior based on real-world usage. ### Testing and quality Choose a set of realistic user questions that cover simple filters ("Show me sales from last month"), complex analysis ("What's the trend in customer retention by region?"), and edge cases like empty results ("Show me sales for December 2019") or ambiguous requests ("Show me the best customers"). Test each question and check the agent's behavior. Focus on SQL correctness, result accuracy and query performance. See the next section for how to tackle common issues. ### Common issues and solutions | Issue | Solution | |-------|----------| | Invalid SQL generation | Improve system prompt, add [function docs](#function-documentation) to context | | Wrong tables queried | Add [COMMENT ON](https://duckdb.org/docs/stable/sql/statements/comment_on.html), improve schema descriptions, implement table filtering | | Misunderstood questions | Add domain-specific examples to system prompt | | Query performance | [EXPLAIN ANALYZE](/sql-reference/motherduck-sql-reference/explain-analyze/) to diagnose query inefficiencies, adjust [Duckling size](/about-motherduck/billing/duckling-sizes/) to scale compute resources | ## Next steps - Explore our [MCP Server](/sql-reference/mcp/) docs (remote and local) - Try [AI features in the MotherDuck UI](/key-tasks/ai-and-motherduck/ai-in-the-ui/ai-features-in-notebooks/) with Generate SQL & Edit - Learn about [Read Scaling](/key-tasks/authenticating-and-connecting-to-motherduck/read-scaling/) for multi-tenant agents - Review [Shares](/key-tasks/sharing-data/sharing-overview/) for read-only data access --- Source: https://motherduck.com/docs/key-tasks/ai-and-motherduck/build-apps/claude-artifacts # Build Claude artifacts with live MotherDuck data > Create and test Claude artifacts that query MotherDuck through the hosted MCP connector Build a Claude Cowork dashboard that queries MotherDuck on open or **Refresh**, using your Claude connector. ## Prerequisites - A Claude Pro, Max, Team, or Enterprise plan with Cowork artifacts available. See [Claude's artifact documentation](https://support.claude.com/en/articles/14729249-use-artifacts-in-claude-cowork) for plan and organization restrictions. - The [MotherDuck connector added to Claude](/key-tasks/ai-and-motherduck/mcp-workflows/mcp-setup/). - Access to the databases and tables that the artifact queries. This guide covers Cowork artifacts introduced on August 19, 2026, not the earlier *live artifacts* system. The example uses `sample_data.nyc.taxi`. If `sample_data` is missing, follow the [sample datasets guide](/getting-started/sample-data-queries/datasets/) to restore the share for your region. ## Create a connected artifact 1. In Claude Cowork, select **Artifacts** from the sidebar, then **New artifact**. 2. Describe the table, metrics, filters, and refresh behavior: ```text Build a NYC taxi dashboard backed by the MotherDuck connector. Query sample_data.nyc.taxi with the read-only query tool at runtime. Show the query timestamp, trip count, average total amount, average trip distance, and a chart grouped by passenger count. Add passenger-count filters and a Refresh button. Call MotherDuck when the artifact opens and when the user refreshes it. Do not embed query results in the source or save them in shared artifact storage. Return current_timestamp from the same query as the metrics and display it. Include loading, empty, and error states. Do not poll in the background. ``` 3. Review the source and SQL. Confirm that metrics come from `query`, not embedded values. 4. Open the artifact. Claude shows the connectors and tools it requests. 5. Leave the MotherDuck `query` tool enabled, and select **Continue**. Don't grant `query_rw` for this read-only dashboard. Claude saves each viewer's tool approval. Tools requiring approval before every action aren't available to artifacts. See [read-only access options](/key-tasks/ai-and-motherduck/mcp-workflows/securing-read-only-access/). ## Verify the live connection 1. Open the artifact and record the displayed query timestamp. 2. Select **Refresh** and confirm that the timestamp changes after the query finishes. 3. Change a filter. The timestamp should change if it queries MotherDuck, but not if it filters already-loaded rows. 4. Close and reopen the artifact. Confirm that it queries MotherDuck again. Use MotherDuck's `current_timestamp`, not a browser-generated timestamp, to verify database requests. If a query fails, check the viewer's connection, tool permissions, table access, and SQL. Keep the last successful result visible with the error beside **Refresh**. ## Share the artifact Artifacts that use connected apps can only be shared within your Claude organization. On Team and Enterprise plans, open **Share**, choose the audience, and select the version to share. Pro and Max plans don't offer organization sharing. See [Claude's sharing options](https://support.claude.com/en/articles/14729249-use-artifacts-in-claude-cowork). Each viewer approves the artifact's tools and uses their own MotherDuck connection. A query fails if that viewer can't access the database or table. :::warning Stored data has separate sharing rules Embedded results and shared artifact storage can expose data to viewers who can't query the original table. Remove data the audience shouldn't see before sharing. Never store an access token in the artifact. ::: Test with viewers who have different table permissions. Check both query results and stored content for unintended access. ## Add writes only when needed Verify that your connector and organization policies let the artifact use `query_rw`. Test an insert and a targeted update in a test database, then check the rows from another MotherDuck client. If the tool is available: - Offer fixed actions such as **Add note** or **Mark reviewed**. - Validate identifiers, lengths, allowed values, and date ranges before building SQL. - Show the target row and ask for confirmation before writing. - Return the affected row identifier and read the row again to update the display. A confirmation dialog isn't a database permission boundary. The viewer's MotherDuck permissions must also limit the data they can change. If `query_rw` isn't available to the artifact, keep writes in the conversation or an application backend. ## Control refreshes and compute use Use aggregate queries, bounded date ranges, and explicit **Apply** or **Refresh** controls. Avoid polling or querying after every keystroke unless the workflow needs it. A completed request doesn't mean the Duckling has stopped. The hosted MCP server can reuse database instances, and other clients can use the same account. Check [query history](/sql-reference/motherduck-sql-reference/md_information_schema/query_history/), the [Duckling overview](/getting-started/interfaces/motherduck-quick-tour/#duckling-overview), and the [cooldown configuration](/about-motherduck/billing/duckling-sizes/#configuring-the-cooldown-period) to inspect compute activity. The Claude connector uses OAuth, not read scaling tokens. For concurrent reads across a pool of Ducklings, use a backend that accepts [read scaling tokens](/key-tasks/authenticating-and-connecting-to-motherduck/read-scaling/). ## Related guides - [OpenAI Sites](/key-tasks/ai-and-motherduck/build-apps/openai-sites/): Build a hosted application with server-side MotherDuck access. - [MotherDuck Dives](/key-tasks/dives/): Keep the visualization in your MotherDuck workspace with versioning, sharing, and embedding. --- Source: https://motherduck.com/docs/key-tasks/ai-and-motherduck/build-apps/openai-sites # Build OpenAI Sites with live MotherDuck data > Build a hosted dashboard that queries MotherDuck from server-side code in OpenAI Sites Build a dashboard with [OpenAI Sites](https://learn.chatgpt.com/docs/sites). Server-side code queries MotherDuck and returns results to the browser, keeping the token and SQL private. ## Prerequisites - A ChatGPT plan that includes Sites. See [OpenAI's Sites documentation](https://learn.chatgpt.com/docs/sites) for availability and limits. - A MotherDuck account with access to the data the Site needs. - Permission to create a [service account](/key-tasks/service-accounts-guide/create-and-configure-service-accounts/) for the application. Sites supports HTTPS but not raw TCP connections. Use the hosted MotherDuck MCP server rather than connecting directly to the MotherDuck Postgres endpoint. ## Connect the site to MotherDuck 1. Create a service account and give it access to the intended databases. 2. Bootstrap the account before its first read scaling connection. Connect once as the service account with a read/write token, or [impersonate the service account](/key-tasks/service-accounts-guide/impersonate-service-accounts/) in the MotherDuck UI. Read scaling connections can't attach databases, so attach everything the Site reads during this session. 3. Verify in that same session that the account can query `sample_data.nyc.taxi`. Follow the [sample datasets guide](/getting-started/sample-data-queries/datasets/) to restore the region-specific share if it's missing. 4. Create an expiring [read scaling token](/key-tasks/authenticating-and-connecting-to-motherduck/read-scaling/) for the Site if your plan supports it. Otherwise, use a read/write token with the service account's permissions limited to the application's needs. 5. Build the Site with the prompt below. Add `MOTHERDUCK_READ_TOKEN` as a secret in **Sites → More actions → Settings** or through your secret manager. Don't paste it into chat or source code. ## Build the dashboard Ask ChatGPT or Codex: ```text Build a private NYC taxi dashboard with OpenAI Sites. Run all MotherDuck access in server-side routes using an MCP client. Connect to https://api.motherduck.com/mcp and read the token from the MOTHERDUCK_READ_TOKEN server environment variable. Use the read-only query tool. Query sample_data.nyc.taxi when the page opens and when the user selects Refresh. Return current_timestamp from MotherDuck, the trip count, average total amount, and average trip distance. Show loading, empty, and error states. Do not poll while the page is idle. Keep the token and SQL out of browser code. ``` ### Add the server-side query helper Install these dependencies in the Site project: ```bash npm install @modelcontextprotocol/sdk@1.30.0 zod@4 ``` The MCP SDK handles initialization, protocol negotiation, and response formats. This helper runs a fixed query and closes the client in `finally`. ```typescript import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import { z } from 'zod'; const queryData = z.object({ success: z.literal(true), columns: z.array(z.string()), rows: z.array(z.array(z.unknown())), }); export async function queryTaxiMetrics(token: string) { if (!token) throw new Error('MotherDuck token is not configured'); const client = new Client({ name: 'taxi-dashboard', version: '1.0.0' }); const transport = new StreamableHTTPClientTransport( new URL('https://api.motherduck.com/mcp'), { requestInit: { headers: { Authorization: `Bearer ${token}` } } }, ); try { await client.connect(transport, { timeout: 30_000 }); const result = await client.callTool( { name: 'query', arguments: { database: 'sample_data', sql: ` SELECT current_timestamp AS queried_at, count(*) AS trips, round(avg(total_amount), 2) AS average_total, round(avg(trip_distance), 2) AS average_distance FROM nyc.taxi `, }, }, undefined, { timeout: 60_000 }, ); if (result.isError) throw new Error('MotherDuck query failed'); const data = queryData.parse(result.structuredContent); return data.rows.map((row) => { if (row.length !== data.columns.length) { throw new Error('Unexpected MotherDuck row shape'); } return Object.fromEntries( data.columns.map((column, index) => [column, row[index]]), ); }); } finally { await client.close(); } } ``` After authorizing the viewer, call `queryTaxiMetrics` from a server route with the environment token. Return JSON with `Cache-Control: no-store`. Catch errors and return a generic message, never raw errors or credentials. The SDK throws on HTTP and JSON-RPC errors. The helper checks tool errors and validates the response. Counts may remain strings. Convert them only within JavaScript's safe integer range. ## Deploy and verify 1. Save a version and deploy it privately. 2. Open the deployment and complete ChatGPT sign-in. 3. Select **Refresh** and confirm that the query timestamp changes. 4. Close and reopen the page. Confirm that it runs a new query. 5. Test loading, empty, authorization, and query-error states. Set runtime variables before deploying and redeploy after changing them. See [Sites environment configuration](https://learn.chatgpt.com/docs/sites#configure-runtime-environment-values). Run these checks on the deployed Site, not only locally. Every Sites deployment URL is a production deployment. ## Share the site Choose the audience in Sites sharing settings. All viewers use the Site's MotherDuck token. ChatGPT sign-in controls Site access, not per-row database permissions. For per-viewer data access, derive account or tenant scope from trusted server-side identity and apply it to every query. Test authorized and unauthorized viewers before sharing. ## Add writes only when needed Store a separate read/write token as `MOTHERDUCK_WRITE_TOKEN`. A read scaling token can't write. Use a dedicated database or table for application writes and expose fixed server actions, such as **Add comment** or **Mark reviewed**. For each action: 1. Authenticate the viewer and check their permission to change the target row. 2. Validate inputs, including identifiers, text lengths, and allowed status transitions. Use parameter binding where supported. Otherwise, use a reviewed SQL literal encoder and fixed identifiers. 3. Run a fixed `query_rw` statement restricted to the authorized rows. Don't accept SQL from the browser. 4. Return the affected row identifier and confirm the expected number of affected rows. 5. Update the page from the write result or a read through the writer connection. Confirm writes with business impact. Use idempotency keys to prevent duplicate writes on retries and cross-site request protection for cookie-authenticated writes. Test an insert and a targeted update in a test database. Verify the results from another MotherDuck client and confirm that invalid or unauthorized requests leave the data unchanged. ## Choose read scaling and refresh behavior Read scaling distributes concurrent connections across a pool of Ducklings. Use it when concurrent readers cause queueing. It doesn't make an individual query faster by itself. Read scaling replicas are [eventually consistent](/key-tasks/authenticating-and-connecting-to-motherduck/read-scaling/#ensuring-data-freshness). For immediate feedback after a write, return the affected row with `RETURNING` or read through the writer connection. Separate MCP requests may reach different replicas, so don't assume a refresh request and a later query use the same reader connection. Use aggregate queries, bounded date ranges, and an explicit **Refresh** control. Without polling, leaving the page open doesn't trigger additional queries. A completed request doesn't mean the Duckling has stopped. The MCP server can reuse database instances, and other clients can use the same account. Check [query history](/sql-reference/motherduck-sql-reference/md_information_schema/query_history/), the [Duckling overview](/getting-started/interfaces/motherduck-quick-tour/#duckling-overview), and the [cooldown configuration](/about-motherduck/billing/duckling-sizes/#configuring-the-cooldown-period) to inspect compute activity. ## Related guides - [Claude artifacts](/key-tasks/ai-and-motherduck/build-apps/claude-artifacts/): Build a tool inside Claude using each viewer's connector. - [MotherDuck Dives](/key-tasks/dives/): Keep the visualization in MotherDuck with versioning, sharing, and embedding. --- ## Docs feedback MotherDuck accepts optional user-submitted feedback about this page at `GET https://motherduck.com/docs/api/feedback/agent`. For agents and automated tools, feedback submission should be user-confirmed before sending. URL-encode query parameter values and send a GET request: ```text GET https://motherduck.com/docs/api/feedback/agent?page_path=%2Fkey-tasks%2Fai-and-motherduck%2Fbuild-apps%2F&page_title=MotherDuck%20Documentation%20-%20Build%20AI%20apps&text= ``` Optionally append `&source=` such as `claude.ai` or `chatgpt`. `page_path` and `text` are required; `page_title` and `source` are optional. Responses: `200 {"feedback_id": ""}`, `400` for malformed query parameters, and `429` when rate-limited.