Metadata-Version: 2.5
Name: mcp-coda
Version: 0.10.0
Summary: MCP server for Coda — docs, pages, tables, rows, formulas, permissions, and automation
Project-URL: Homepage, https://github.com/vish288/mcp-coda
Project-URL: Repository, https://github.com/vish288/mcp-coda
Project-URL: Issues, https://github.com/vish288/mcp-coda/issues
Project-URL: Changelog, https://github.com/vish288/mcp-coda/releases
Project-URL: Documentation, https://github.com/vish288/mcp-coda#readme
Author: vish288
License: MIT
License-File: LICENSE
Keywords: ai,ai-agents,ai-integration,automation,claude,coda,coda-api,coda-io,cursor,document-automation,fastmcp,formulas,llm,mcp,mcp-server,model-context-protocol,permissions,rows,tables,vscode,windsurf
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Pydantic :: 2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: click<9,>=8.1.7
Requires-Dist: fastmcp<5,>=4.0.10
Requires-Dist: httpx<1,>=0.28.0
Requires-Dist: pydantic<3.0,>=2.10.0
Requires-Dist: python-dotenv<2,>=1.0.1
Description-Content-Type: text/markdown

# mcp-coda

[![PyPI version](https://img.shields.io/pypi/v/mcp-coda)](https://pypi.org/project/mcp-coda/)
[![PyPI downloads](https://img.shields.io/pypi/dm/mcp-coda)](https://pypi.org/project/mcp-coda/)
[![Python](https://img.shields.io/pypi/pyversions/mcp-coda)](https://pypi.org/project/mcp-coda/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![CI](https://github.com/vish288/mcp-coda/actions/workflows/tests.yml/badge.svg)](https://github.com/vish288/mcp-coda/actions/workflows/tests.yml)
[![MCP Registry](https://img.shields.io/badge/MCP-Registry-blue)](https://registry.modelcontextprotocol.io)

<!-- mcp-name: io.github.vish288/mcp-coda -->

**Install:** `uvx mcp-coda` | [PyPI](https://pypi.org/project/mcp-coda/) | [MCP Registry](https://registry.modelcontextprotocol.io) | [Changelog](https://github.com/vish288/mcp-coda/releases)

**mcp-coda** is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for [Coda](https://coda.io). It lets an AI assistant read and write Coda docs, pages, tables, and rows through the [Coda API](https://coda.io/developers/apis/v1). It also runs formulas, manages permissions and folders, publishes docs, and triggers automations. The server exposes **54 tools**, **12 resources**, and **5 prompts**. It supports the MCP 2026-07-28 specification (often called MCP 2.0) and stays compatible with 2025-11-25 clients. It works with Claude Desktop, Claude Code, Cursor, Windsurf, VS Code Copilot, and any MCP-compatible client.

mcp-coda uses [FastMCP](https://github.com/jlowin/fastmcp), [httpx](https://www.python-httpx.org/), and [Pydantic](https://docs.pydantic.dev/).

## 1-Click Installation

[![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://vish288.github.io/mcp-install?server=mcp-coda&install=cursor)

[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vish288.github.io/mcp-install?server=mcp-coda&install=vscode) [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://vish288.github.io/mcp-install?server=mcp-coda&install=vscode-insiders)

> **Tip:** For other AI assistants (Claude Code, Windsurf, IntelliJ, Gemini CLI), visit the **[Coda MCP Installation Gateway](https://vish288.github.io/mcp-install?server=mcp-coda)**.

<details>
<summary><b>Manual Setup Guides (Click to expand)</b></summary>
<br/>

> Prerequisite: Install `uv` first (required for all `uvx` install flows). [Install uv](https://docs.astral.sh/uv/getting-started/installation/).

### Claude Code

```bash
claude mcp add coda -- uvx mcp-coda
```

### Windsurf & IntelliJ

**Windsurf:** Add to `~/.codeium/windsurf/mcp_config.json`
**IntelliJ:** Add to `Settings | Tools | MCP Servers`

> **Note:** The actual server config starts at `coda` inside the `mcpServers` object.

```json
{
  "mcpServers": {
    "coda": {
      "command": "uvx",
      "args": ["mcp-coda"],
      "env": {
        "CODA_API_TOKEN": "your-token-here"
      }
    }
  }
}
```

### Gemini CLI

```bash
gemini mcp add -e CODA_API_TOKEN=your-token-here coda uvx mcp-coda
```

### pip / uv

```bash
uv pip install mcp-coda
```

</details>

## Configuration

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `CODA_API_TOKEN` | **Yes** | - | Coda API token ([get one here](https://coda.io/account#apiSettings)) |
| `CODA_READ_ONLY` | No | `false` | Set to `true` to disable write operations |
| `CODA_BASE_URL` | No | `https://coda.io/apis/v1` | API base URL |
| `CODA_TIMEOUT` | No | `30` | Request timeout in seconds |
| `CODA_HTTP_BEARER_TOKEN` | No | - | Required to bind an HTTP transport to a non-loopback host. Clients send `Authorization: Bearer <token>`. Minimum 16 characters |

### Supported Token Types

The server checks these environment variables in order — first match wins:

1. `CODA_API_TOKEN`
2. `CODA_TOKEN`
3. `CODA_PAT`

Generate tokens at [coda.io/account#apiSettings](https://coda.io/account#apiSettings). Tokens grant access to all docs accessible by the token owner. Tokens have no scope restrictions. Coda's sharing settings control doc-level access.

## Compatibility

| Client | Supported | Install Method |
|--------|-----------|----------------|
| Claude Desktop | Yes | `claude_desktop_config.json` |
| Claude Code | Yes | `claude mcp add` |
| Cursor | Yes | One-click deeplink or `.cursor/mcp.json` |
| VS Code Copilot | Yes | One-click deeplink or `.vscode/mcp.json` |
| Windsurf | Yes | `~/.codeium/windsurf/mcp_config.json` |
| Any MCP client | Yes | stdio or HTTP transport |

## Protocol support

mcp-coda implements the **Model Context Protocol 2026-07-28** specification, often called MCP 2.0. It also stays compatible with **2025-11-25** clients. A FastMCP client pinned to 2026-07-28 and a legacy client both list all tools and reach the Coda API over stdio. The companion code PR adds a regression test that checks `tools/list` with an in-memory client pinned to 2026-07-28.

- **Spec version**: MCP 2026-07-28 (MCP 2.0), backward compatible with 2025-11-25.
- **Built on**: FastMCP 4.x and the MCP Python SDK 2.x.
- **Transports**: `stdio` (default) and `streamable-http` (recommended for remote). `sse` (HTTP+SSE) still works, but the 2026-07-28 specification deprecates it, so the server prints a warning.
- **Capabilities**: tools, resources, and prompts. The server uses no Roots, Sampling, Logging, elicitation, or resource subscriptions, so the 2026-07-28 deprecations do not affect it.

### Error results

An expected failure — a 404, a 401, a rate limit, or a write blocked by read-only mode — comes back as a tool result with `isError: true` rather than a value the caller must inspect for an `error` key. The result body is the text content: a JSON object with `error`, an actionable `hint`, and, where relevant, `status_code`, `body`, and `retry_after`. The result carries no `structuredContent` — the TypeScript MCP SDK validates `structuredContent` against the tool's output schema even when `isError` is true, so an error payload there would make strict clients reject the whole result. Unexpected exceptions (a bug in a tool) are also `isError: true`, their text is `TypeName: message`, and the server logs them with a traceback.

A successful result carries the Coda JSON object as `structuredContent` beside the pretty-printed text, and tools advertise no `outputSchema`: payloads pass the upstream fields through untyped, so strict clients validate nothing and receive the object directly rather than a double-encoded `{"result": "<json>"}` wrapper. A typed schema is opt-in per tool via `@mcp.tool(output_schema=…)`.

## Tools (54)

| Category | Count | Tools |
|----------|-------|-------|
| **Account** | 3 | whoami, resolve browser link, mutation status |
| **Docs** | 5 | list, get, create, update, delete |
| **Pages** | 9 | list, get, create, update, delete, get content, delete content, export, export status |
| **Tables** | 4 | list tables, get table, list columns, get column |
| **Rows** | 7 | list, get, insert/upsert, update, delete, bulk delete, push button |
| **Permissions** | 6 | sharing metadata, list, add, delete, search principals, ACL settings |
| **Folders** | 5 | list, get, create, update, delete |
| **Misc** | 8 | formulas (list, get), controls (list, get), publishing (list categories, publish, unpublish), trigger automation |
| **Analytics** | 7 | doc analytics, doc summary, page analytics, pack analytics, pack summary, formula analytics, analytics updated |

<details>
<summary>Full tool reference (click to expand)</summary>

### Account
| Tool | Description |
|------|-------------|
| `coda_whoami` | Get current user info |
| `coda_resolve_browser_link` | Convert browser URL to API IDs |
| `coda_get_mutation_status` | Check async write status |

### Docs
| Tool | Description |
|------|-------------|
| `coda_list_docs` | List accessible docs |
| `coda_get_doc` | Get doc metadata |
| `coda_create_doc` | Create a new doc |
| `coda_update_doc` | Update doc title/icon |
| `coda_delete_doc` | Delete a doc |

### Pages
| Tool | Description |
|------|-------------|
| `coda_list_pages` | List pages in a doc |
| `coda_get_page` | Get page metadata |
| `coda_create_page` | Create a page |
| `coda_update_page` | Update page name/content |
| `coda_delete_page` | Delete a page |
| `coda_get_page_content` | Read page content |
| `coda_delete_page_content` | Clear page content |
| `coda_export_page` | Export page as HTML or markdown |
| `coda_get_export_status` | Poll an export started by coda_export_page |

### Tables
| Tool | Description |
|------|-------------|
| `coda_list_tables` | List tables and views |
| `coda_get_table` | Get table metadata |
| `coda_list_columns` | List columns in a table |
| `coda_get_column` | Get column metadata |

### Rows
| Tool | Description |
|------|-------------|
| `coda_list_rows` | List and filter rows |
| `coda_get_row` | Get a single row |
| `coda_insert_rows` | Insert or upsert rows |
| `coda_update_row` | Update a row |
| `coda_delete_row` | Delete a row |
| `coda_delete_rows` | Bulk delete rows by filter |
| `coda_push_button` | Push a button column value |

### Formulas
| Tool | Description |
|------|-------------|
| `coda_list_formulas` | List named formulas |
| `coda_get_formula` | Get formula value |

### Controls
| Tool | Description |
|------|-------------|
| `coda_list_controls` | List controls |
| `coda_get_control` | Get control value |

### Permissions
| Tool | Description |
|------|-------------|
| `coda_get_sharing_metadata` | Get sharing config |
| `coda_list_permissions` | List ACL entries |
| `coda_add_permission` | Grant access |
| `coda_delete_permission` | Revoke access |
| `coda_search_principals` | Search users/groups |
| `coda_get_acl_settings` | Get ACL settings |

### Publishing
| Tool | Description |
|------|-------------|
| `coda_list_categories` | List publishing categories |
| `coda_publish_doc` | Publish a doc |
| `coda_unpublish_doc` | Unpublish a doc |

### Folders
| Tool | Description |
|------|-------------|
| `coda_list_folders` | List folders |
| `coda_get_folder` | Get folder details |
| `coda_create_folder` | Create a folder |
| `coda_update_folder` | Rename a folder |
| `coda_delete_folder` | Delete a folder |

### Automations
| Tool | Description |
|------|-------------|
| `coda_trigger_automation` | Trigger an automation rule |

### Analytics
| Tool | Description |
|------|-------------|
| `coda_list_doc_analytics` | Doc usage metrics |
| `coda_get_doc_analytics_summary` | Aggregated doc metrics |
| `coda_list_page_analytics` | Page usage metrics |
| `coda_list_pack_analytics` | Pack usage metrics |
| `coda_get_pack_analytics_summary` | Aggregated pack metrics |
| `coda_list_pack_formula_analytics` | Formula-level metrics |
| `coda_get_analytics_updated` | Analytics freshness timestamp |

</details>

## Resources (12)

The server exposes [MCP resources](https://modelcontextprotocol.io/docs/concepts/resources) that provide ambient context and do not consume tool calls.

### Data Resources (live API)

| URI | Name | Description |
|-----|------|-------------|
| `coda://docs` | Coda Docs | List of docs accessible to the current API token |
| `coda://docs/{doc_id}/schema` | Coda Doc Schema | Table and column definitions for a doc |

### Rules (static knowledge)

| URI | Name | Description |
|-----|------|-------------|
| `resource://rules/coda-doc-structure` | Coda Doc Structure | Doc/page hierarchy, page types, naming, when to split docs vs folders |
| `resource://rules/coda-table-design` | Coda Table Design | Column types, relations, display columns, row limits, table vs view |
| `resource://rules/coda-permissions` | Coda Permission Model | Doc-level vs page-level locking, ACL, domain sharing, principal types |
| `resource://rules/coda-automations` | Coda Automation Patterns | Webhooks, button triggers, rate limits, payload design, idempotency |
| `resource://rules/coda-api-patterns` | Coda API Best Practices | Rate limits, pagination, async mutations, error handling, retry |

### Guides (how-to)

| URI | Name | Description |
|-----|------|-------------|
| `resource://guides/row-operations` | Row Operations Guide | Insert vs upsert, bulk ops, key columns, cell formats, delete strategies |
| `resource://guides/page-content` | Page Content Guide | HTML vs markdown, insert modes, export workflows |
| `resource://guides/formula-controls` | Formulas & Controls Guide | Named formulas, control types, reading values |
| `resource://guides/publishing-analytics` | Publishing & Analytics Guide | Publishing categories, gallery settings, analytics date filtering |
| `resource://guides/folder-organization` | Folder Organization Guide | Folder CRUD, doc-folder relationships, hierarchy, bulk organization |

## Prompts (5)

The server provides [MCP prompts](https://modelcontextprotocol.io/docs/concepts/prompts) — reusable task templates that clients can invoke.

| Prompt | Parameters | Description |
|--------|-----------|-------------|
| `analyze_doc_structure` | `doc_id` | Analyze a doc's page hierarchy, table layout, and organization |
| `design_table_schema` | `description` | Design a table schema from a natural language description |
| `migrate_spreadsheet` | `doc_id`, `source_format` | Guide for migrating CSV/Excel/Sheets data into Coda |
| `setup_automation` | `doc_id`, `trigger_type` | Set up a webhook/button/time automation with error handling |
| `audit_permissions` | `doc_id` | Audit sharing and permissions, suggest tightening |

## Usage Examples

### Docs & Pages

```
"List all my Coda docs"
→ coda_list_docs(is_owner=True)

"Get the content of page 'Sprint Planning' in doc d1"
→ coda_list_pages(doc_id="d1") → find page ID
→ coda_get_page_content(doc_id="d1", page_id_or_name="canvas-abc")

"Create a new doc from a template"
→ coda_create_doc(title="Q1 Planning", source_doc="template-doc-id")
```

### Tables & Rows

```
"List all tables in doc d1"
→ coda_list_tables(doc_id="d1")

"Find rows where Status is 'Done'"
→ coda_list_rows(doc_id="d1", table_id_or_name="Tasks", query="Done")

"Insert a new row into the Tasks table"
→ coda_insert_rows(doc_id="d1", table_id_or_name="Tasks", rows=[{"cells": [{"column": "Name", "value": "New task"}]}])

"Update a row's status"
→ coda_update_row(doc_id="d1", table_id_or_name="Tasks", row_id_or_name="i-abc", row={"cells": [{"column": "Status", "value": "In Progress"}]})
```

### Formulas & Controls

```
"Get the value of the TotalBudget formula"
→ coda_get_formula(doc_id="d1", formula_id_or_name="TotalBudget")

"Check the current value of the DateFilter control"
→ coda_get_control(doc_id="d1", control_id_or_name="DateFilter")
```

### Permissions & Sharing

```
"Share a doc with a teammate"
→ coda_add_permission(doc_id="d1", access="write", principal_email="alice@example.com")

"List who has access to a doc"
→ coda_list_permissions(doc_id="d1")
```

## Security Considerations

- **Token scope**: Coda API tokens grant access to all docs the token owner can access. Use a dedicated service account for production deployments to limit exposure.
- **Read-only mode**: Set `CODA_READ_ONLY=true` to disable all write operations (create, update, delete). The server enforces read-only mode before any API call.
- **MCP tool annotations**: Each tool declares `readOnlyHint`, `destructiveHint`, and `idempotentHint` for client-side permission prompts.
- **No credential storage**: The server does not persist tokens. It reads credentials from environment variables at startup.
- **HTTP transport auth**: Host/Origin protection is not authentication. Binding an HTTP transport to a non-loopback host requires `CODA_HTTP_BEARER_TOKEN` (min 16 chars); clients send `Authorization: Bearer <token>` and anything else gets a 401. Without it the server refuses to start on a non-loopback host.

## Upgrading to 0.9.0

Two defaults changed. Both are breaking for callers that relied on the old behaviour:

- **`coda_publish_doc` no longer lists docs in the public gallery by default.** `discoverable` now defaults to `False` — publishing alone makes a doc reachable by URL; pass `discoverable=True` to list it in the Coda gallery. `mode` is now restricted to `view`, `play`, or `edit`.
- **An HTTP transport on a non-loopback host now requires a bearer token.** `uvx mcp-coda --transport streamable-http --host 0.0.0.0` (or any non-loopback host) exits with an error unless `CODA_HTTP_BEARER_TOKEN` (min 16 chars) is set; clients then send `Authorization: Bearer <token>`. Binding to `127.0.0.1` is unchanged.

## Rate Limits & Permissions

### Rate Limits

Coda enforces per-token rate limits (varies by plan). When Coda rate-limits a request, the tool returns a 429 error with `retry_after` seconds. The server does not retry. Wait that many seconds, then retry. Most paginated endpoints default to 50 results per page (`coda_list_docs` 25, `coda_list_columns` 100). Use `limit` to adjust.

### Async Mutations

Coda processes write operations (insert, update, delete rows) asynchronously. Tools return a `requestId`. Check it with `coda_get_mutation_status` to confirm completion.

### Required Permissions

| Operation | Minimum Coda Doc Role |
|-----------|----------------------|
| List docs, read pages, tables, rows, formulas, controls | Viewer |
| Read permissions, analytics | Viewer (doc owner for analytics) |
| Create/update pages, insert/update/delete rows | Editor |
| Create/delete docs, manage folders | Editor (or doc owner) |
| Add/remove permissions, publish/unpublish | Doc Owner |
| Trigger automations | Editor |

### Access Control

| Layer | Mechanism |
|-------|-----------|
| Server-level | `CODA_READ_ONLY=true` blocks all write tools |
| MCP annotations | `readOnlyHint`, `destructiveHint`, `idempotentHint` for client-side prompts |
| Coda token | Doc-level access enforced by Coda's sharing settings |

## CLI & Transport Options

```bash
# Default: stdio transport (for MCP clients)
uvx mcp-coda

# HTTP transport (SSE or streamable-http)
uvx mcp-coda --transport sse --host 127.0.0.1 --port 8000
uvx mcp-coda --transport streamable-http --port 9000

# Binding to a non-loopback host requires a bearer token
CODA_HTTP_BEARER_TOKEN=$(openssl rand -hex 32) \
  uvx mcp-coda --transport streamable-http --host 0.0.0.0 --port 9000
# Clients authenticate with the token:
#   curl -H "Authorization: Bearer $CODA_HTTP_BEARER_TOKEN" http://host:9000/mcp

# Config comes from env vars
CODA_API_TOKEN=your-token uvx mcp-coda --read-only
```

`--coda-token` still works but is deprecated and hidden from `--help`: it exposes the token to `ps` and shell history. Use `CODA_API_TOKEN`.

The server loads `.env` files from the working directory automatically via `python-dotenv`.

## FAQ

### Does mcp-coda support MCP 2.0 (the 2026-07-28 spec)?

Yes. mcp-coda implements the MCP 2026-07-28 specification, commonly called MCP 2.0. It also works with 2025-11-25 clients.

### Which transports does mcp-coda support?

The server supports `stdio` (the default) and `streamable-http`. It also supports `sse`, but the 2026-07-28 specification deprecates it. Prefer `streamable-http` for remote connections.

### Is mcp-coda read-only safe?

Yes. Set `CODA_READ_ONLY=true` to disable every write tool. The server enforces read-only mode before any API call.

### What token scopes does mcp-coda need?

Coda tokens have no scopes. A token reaches every doc its owner can reach. Coda's sharing settings control doc-level access. Use a dedicated service account to limit exposure.

### Does mcp-coda work with self-hosted Coda?

Coda is a hosted service, so there is no self-hosted deployment. Set `CODA_BASE_URL` only to target a proxy or a non-default API base.

### Which clients work with mcp-coda?

mcp-coda works with Claude Desktop, Claude Code, Cursor, Windsurf, VS Code Copilot, and any MCP-compatible client over stdio or HTTP.

### How many tools, resources, and prompts does it provide?

mcp-coda provides 54 tools, 12 resources, and 5 prompts. They cover docs, pages, tables, rows, formulas, controls, permissions, folders, publishing, automations, and analytics.

## Related MCP Servers

- [mcp-gitlab](https://github.com/vish288/mcp-gitlab) — GitLab integration (83 tools, 7 resources, 6 prompts)
- [mcp-atlassian-extended](https://github.com/vish288/mcp-atlassian-extended) — Jira + Confluence integration (22 tools, 15 resources, 5 prompts)
- [mcp-argocd](https://github.com/vish288/mcp-argocd) — Argo CD integration (37 tools, 7 resources, 7 prompts)

## Development

```bash
git clone https://github.com/vish288/mcp-coda.git
cd mcp-coda
uv sync --all-extras

uv run pytest --cov
uv run ruff check .
uv run ruff format --check .
```

## License

MIT
