airtable-write-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moai Loops
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.3
2
+ Name: airtable-write-mcp
3
+ Version: 0.1.0
4
+ Summary: Safe Airtable writes from Claude — dry-run previews by default; bulk upsert & undo on the roadmap.
5
+ License: MIT
6
+ Keywords: airtable,mcp,model-context-protocol,claude,dry-run
7
+ Author: Moai Loops
8
+ Requires-Python: >=3.12
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: httpx (>=0.27,<1.0)
14
+ Requires-Dist: loguru (>=0.7,<1.0)
15
+ Requires-Dist: mcp (>=1.28,<1.29)
16
+ Project-URL: Changelog, https://github.com/archypelago/airtable-write-mcp/blob/main/CHANGELOG.md
17
+ Project-URL: Repository, https://github.com/archypelago/airtable-write-mcp
18
+ Description-Content-Type: text/markdown
19
+
20
+ # airtable-write-mcp
21
+
22
+ **Safe Airtable writes from Claude — dry-run previews by default. Bulk upsert & undo on the roadmap.**
23
+
24
+ An MCP (Model Context Protocol) server that lets Claude Desktop, Claude Code, Cursor, or Cline
25
+ write to your Airtable bases *safely*: every write is previewed as a dry-run plan first, and
26
+ nothing changes in Airtable until you confirm. No LLM runs inside the server — it is a
27
+ deterministic tool server; your MCP client's model does the language work.
28
+
29
+ ```text
30
+ You: Add Acme Corp to my CRM Leads table, status New.
31
+ Claude: Here's the plan — create 1 record in Leads: {Name: "Acme Corp", Status: "New"}.
32
+ Nothing written yet. Confirm?
33
+ You: Yes.
34
+ Claude: Done — rec0123456789abcd created. https://airtable.com/app…/tbl…/rec…
35
+ ```
36
+
37
+ ## Why this server
38
+
39
+ Reading and writing Airtable from an MCP client is table stakes — the official Airtable MCP server
40
+ and several community servers already do it. What none of them offer is a **safety layer for
41
+ writes**. That is the only thing this project is about.
42
+
43
+ | Server | Write records | Bulk upsert + dedup | Dry-run preview | Per-record failure report | Undo | Auth |
44
+ |---|---|---|---|---|---|---|
45
+ | Official Airtable MCP | ✅ create + update | ❌ | ❌ | ❌ | ❌ | OAuth or PAT |
46
+ | domdomegg/airtable-mcp-server (top community) | ✅ create/update/delete | ❌ | ❌ | ❌ | ❌ | PAT |
47
+ | **airtable-write-mcp (this server, v0.1.0)** | ✅ create + update (no delete) | 🔜 planned (v0.2.0) | ✅ **default-on** | 🔜 planned (v0.2.0) | 🔜 planned (v0.3.0) | PAT |
48
+
49
+ *Incumbent capabilities audited 2026-07-03. Our row states shipped-version truth only — planned
50
+ features link to the [roadmap](#roadmap) and are never claimed before `tools/list` proves them.*
51
+
52
+ ## Tools (v0.1.0)
53
+
54
+ | Tool | What it does |
55
+ |---|---|
56
+ | `list_bases` | List every base your PAT can access — call first to resolve `base_id`. |
57
+ | `list_tables` | List a base's tables and field schemas (types, writable flags, select options). |
58
+ | `list_records` | List records with a structured filter, view, and pagination (values escaped safely). |
59
+ | `create_record` | Create one record — **dry-run by default**, writes only after confirmation. |
60
+ | `update_record` | Update fields on one record (others untouched) — **dry-run by default**. |
61
+
62
+ ## How write safety works
63
+
64
+ Every mutating tool defaults to `dry_run=true`:
65
+
66
+ 1. **Dry-run** — the server validates your fields against the live table schema (unknown fields
67
+ get a did-you-mean, read-only fields are skipped and reported, select options are checked) and
68
+ returns a `would_write` plan plus a single-use `confirm_token`. **Zero writes happen.**
69
+ 2. **Confirm** — the model re-calls the tool with that token and `dry_run=false`. The server
70
+ verifies the plan hash still matches the arguments (any drift aborts), then writes.
71
+ 3. Tokens are single-use and expire after a short TTL; a replayed or stale token is refused with
72
+ a structured error.
73
+
74
+ There is no way to skip the preview by accident — you'd have to set an opt-out env var yourself
75
+ (see [Configuration](#configuration)).
76
+
77
+ ## Install
78
+
79
+ ### 1. Create an Airtable Personal Access Token
80
+
81
+ Create a PAT at [airtable.com/create/tokens](https://airtable.com/create/tokens) with these scopes,
82
+ granted to the bases you want to use:
83
+
84
+ - `data.records:read`
85
+ - `data.records:write`
86
+ - `schema.bases:read`
87
+
88
+ Your token stays on your machine — it is read from an env var (or the macOS Keychain) at tool-call
89
+ time, never bundled, logged, or echoed back in errors.
90
+
91
+ ### 2. Add the server to your MCP client
92
+
93
+ The same `mcpServers` JSON shape works everywhere:
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "airtable-write-mcp": {
99
+ "command": "uvx",
100
+ "args": ["airtable-write-mcp"],
101
+ "env": { "AIRTABLE_PAT": "<your PAT>" }
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ | Client | Where to put it |
108
+ |---|---|
109
+ | Claude Code | `.mcp.json` at your repo root — or `claude mcp add airtable-write-mcp -e AIRTABLE_PAT=<pat> -- uvx airtable-write-mcp` |
110
+ | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) |
111
+ | Cursor | `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) |
112
+ | Cline | MCP Servers settings UI (`cline_mcp_settings.json`) |
113
+
114
+ Restart the client after editing; the server appears with its five tools.
115
+
116
+ **macOS Keychain instead of a plaintext PAT** — store the token once, keep the config secret-free:
117
+
118
+ ```bash
119
+ security add-generic-password -U -s airtable_mcp_key -a "$USER" -w "<your PAT>"
120
+ ```
121
+
122
+ then use `"env": { "AIRTABLE_PAT_KEYCHAIN_SERVICE": "airtable_mcp_key" }` in the snippet instead
123
+ of `AIRTABLE_PAT`. The Keychain is read lazily on your first tool call — never at client startup —
124
+ so adding the server won't trigger a Keychain prompt until you actually use it.
125
+
126
+ **Local development** — run from a checkout instead of PyPI:
127
+
128
+ ```bash
129
+ git clone https://github.com/archypelago/airtable-write-mcp && cd airtable-write-mcp
130
+ make install
131
+ poetry env info --path # → <venv>
132
+ ```
133
+
134
+ then point `command` at `<venv>/bin/airtable-write-mcp` (the direct binary avoids slow
135
+ `poetry run` startup shims that some clients time out on).
136
+
137
+ ### 3. First run (step 0)
138
+
139
+ Ask something like *"List my Airtable bases."* Your client will ask permission for each tool the
140
+ first time it's used — choose **Always allow** for the three read tools (`list_bases`,
141
+ `list_tables`, `list_records`). Writes then cost at most two approvals: the dry-run and the
142
+ confirm — that's the safety feature, not friction.
143
+
144
+ ## Configuration
145
+
146
+ | Env var | Required | Meaning |
147
+ |---|---|---|
148
+ | `AIRTABLE_PAT` | one of these two | Your Airtable Personal Access Token. |
149
+ | `AIRTABLE_PAT_KEYCHAIN_SERVICE` | one of these two | macOS: name of a Keychain generic-password item holding the PAT (read lazily at first tool call). Ignored when `AIRTABLE_PAT` is set. |
150
+ | `AIRTABLE_RPS` | no | Lower the per-base request rate below the default 4 rps (values above 4 clamp to 4). |
151
+ | `CONFIRM_SKIP_UNDER` | no | **Opt-out, default 0 (off):** skip the dry-run/confirm round-trip for writes touching fewer than N records (hard cap 25). Leave unset unless you know you want this. |
152
+
153
+ The server starts and answers `tools/list` with **no** secrets present — a missing PAT fails the
154
+ individual tool call with a structured setup error naming the exact env var, never the server.
155
+
156
+ ## Roadmap
157
+
158
+ | Version | Adds | Status |
159
+ |---|---|---|
160
+ | **v0.1.0** | The five tools above; dry-run/confirm write safety; schema validation; rate limiting + 429/5xx resilience | ✅ shipped |
161
+ | **v0.2.0** | `bulk_upsert` — paste 200 rows in chat, dedup against existing records by key field, batched under the rate limit, per-record report (N created / M updated / K failed, 0 silently dropped) | 🔜 next |
162
+ | **v0.3.0** | `list_operations` + `undo_operation` — an operation journal and conflict-safe undo | planned |
163
+ | Hosted | OAuth connect (no token handling), works in Claude web/mobile, retained audit + undo history | planned, gated on demand |
164
+
165
+ ## Development
166
+
167
+ ```bash
168
+ make install # poetry lock + install (self-provisions the venv)
169
+ make test # pytest — in-memory MCP client harness, no network
170
+ make lint # ruff check + format check
171
+ make check # the full pre-merge gate
172
+ make run # start the stdio server locally
173
+ make build # wheel + sdist; smoke with: uvx --from dist/airtable_write_mcp-*.whl airtable-write-mcp
174
+ ```
175
+
176
+ Tests talk to the server through a real in-memory MCP client session — no subprocess, no model
177
+ mocking. Design rules that will not change: no LLM calls inside the server, stderr-only logging
178
+ (stdout belongs to the protocol), and every mutating tool defaults to dry-run.
179
+
180
+ ## License
181
+
182
+ [MIT](LICENSE)
183
+
@@ -0,0 +1,163 @@
1
+ # airtable-write-mcp
2
+
3
+ **Safe Airtable writes from Claude — dry-run previews by default. Bulk upsert & undo on the roadmap.**
4
+
5
+ An MCP (Model Context Protocol) server that lets Claude Desktop, Claude Code, Cursor, or Cline
6
+ write to your Airtable bases *safely*: every write is previewed as a dry-run plan first, and
7
+ nothing changes in Airtable until you confirm. No LLM runs inside the server — it is a
8
+ deterministic tool server; your MCP client's model does the language work.
9
+
10
+ ```text
11
+ You: Add Acme Corp to my CRM Leads table, status New.
12
+ Claude: Here's the plan — create 1 record in Leads: {Name: "Acme Corp", Status: "New"}.
13
+ Nothing written yet. Confirm?
14
+ You: Yes.
15
+ Claude: Done — rec0123456789abcd created. https://airtable.com/app…/tbl…/rec…
16
+ ```
17
+
18
+ ## Why this server
19
+
20
+ Reading and writing Airtable from an MCP client is table stakes — the official Airtable MCP server
21
+ and several community servers already do it. What none of them offer is a **safety layer for
22
+ writes**. That is the only thing this project is about.
23
+
24
+ | Server | Write records | Bulk upsert + dedup | Dry-run preview | Per-record failure report | Undo | Auth |
25
+ |---|---|---|---|---|---|---|
26
+ | Official Airtable MCP | ✅ create + update | ❌ | ❌ | ❌ | ❌ | OAuth or PAT |
27
+ | domdomegg/airtable-mcp-server (top community) | ✅ create/update/delete | ❌ | ❌ | ❌ | ❌ | PAT |
28
+ | **airtable-write-mcp (this server, v0.1.0)** | ✅ create + update (no delete) | 🔜 planned (v0.2.0) | ✅ **default-on** | 🔜 planned (v0.2.0) | 🔜 planned (v0.3.0) | PAT |
29
+
30
+ *Incumbent capabilities audited 2026-07-03. Our row states shipped-version truth only — planned
31
+ features link to the [roadmap](#roadmap) and are never claimed before `tools/list` proves them.*
32
+
33
+ ## Tools (v0.1.0)
34
+
35
+ | Tool | What it does |
36
+ |---|---|
37
+ | `list_bases` | List every base your PAT can access — call first to resolve `base_id`. |
38
+ | `list_tables` | List a base's tables and field schemas (types, writable flags, select options). |
39
+ | `list_records` | List records with a structured filter, view, and pagination (values escaped safely). |
40
+ | `create_record` | Create one record — **dry-run by default**, writes only after confirmation. |
41
+ | `update_record` | Update fields on one record (others untouched) — **dry-run by default**. |
42
+
43
+ ## How write safety works
44
+
45
+ Every mutating tool defaults to `dry_run=true`:
46
+
47
+ 1. **Dry-run** — the server validates your fields against the live table schema (unknown fields
48
+ get a did-you-mean, read-only fields are skipped and reported, select options are checked) and
49
+ returns a `would_write` plan plus a single-use `confirm_token`. **Zero writes happen.**
50
+ 2. **Confirm** — the model re-calls the tool with that token and `dry_run=false`. The server
51
+ verifies the plan hash still matches the arguments (any drift aborts), then writes.
52
+ 3. Tokens are single-use and expire after a short TTL; a replayed or stale token is refused with
53
+ a structured error.
54
+
55
+ There is no way to skip the preview by accident — you'd have to set an opt-out env var yourself
56
+ (see [Configuration](#configuration)).
57
+
58
+ ## Install
59
+
60
+ ### 1. Create an Airtable Personal Access Token
61
+
62
+ Create a PAT at [airtable.com/create/tokens](https://airtable.com/create/tokens) with these scopes,
63
+ granted to the bases you want to use:
64
+
65
+ - `data.records:read`
66
+ - `data.records:write`
67
+ - `schema.bases:read`
68
+
69
+ Your token stays on your machine — it is read from an env var (or the macOS Keychain) at tool-call
70
+ time, never bundled, logged, or echoed back in errors.
71
+
72
+ ### 2. Add the server to your MCP client
73
+
74
+ The same `mcpServers` JSON shape works everywhere:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "airtable-write-mcp": {
80
+ "command": "uvx",
81
+ "args": ["airtable-write-mcp"],
82
+ "env": { "AIRTABLE_PAT": "<your PAT>" }
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ | Client | Where to put it |
89
+ |---|---|
90
+ | Claude Code | `.mcp.json` at your repo root — or `claude mcp add airtable-write-mcp -e AIRTABLE_PAT=<pat> -- uvx airtable-write-mcp` |
91
+ | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) |
92
+ | Cursor | `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global) |
93
+ | Cline | MCP Servers settings UI (`cline_mcp_settings.json`) |
94
+
95
+ Restart the client after editing; the server appears with its five tools.
96
+
97
+ **macOS Keychain instead of a plaintext PAT** — store the token once, keep the config secret-free:
98
+
99
+ ```bash
100
+ security add-generic-password -U -s airtable_mcp_key -a "$USER" -w "<your PAT>"
101
+ ```
102
+
103
+ then use `"env": { "AIRTABLE_PAT_KEYCHAIN_SERVICE": "airtable_mcp_key" }` in the snippet instead
104
+ of `AIRTABLE_PAT`. The Keychain is read lazily on your first tool call — never at client startup —
105
+ so adding the server won't trigger a Keychain prompt until you actually use it.
106
+
107
+ **Local development** — run from a checkout instead of PyPI:
108
+
109
+ ```bash
110
+ git clone https://github.com/archypelago/airtable-write-mcp && cd airtable-write-mcp
111
+ make install
112
+ poetry env info --path # → <venv>
113
+ ```
114
+
115
+ then point `command` at `<venv>/bin/airtable-write-mcp` (the direct binary avoids slow
116
+ `poetry run` startup shims that some clients time out on).
117
+
118
+ ### 3. First run (step 0)
119
+
120
+ Ask something like *"List my Airtable bases."* Your client will ask permission for each tool the
121
+ first time it's used — choose **Always allow** for the three read tools (`list_bases`,
122
+ `list_tables`, `list_records`). Writes then cost at most two approvals: the dry-run and the
123
+ confirm — that's the safety feature, not friction.
124
+
125
+ ## Configuration
126
+
127
+ | Env var | Required | Meaning |
128
+ |---|---|---|
129
+ | `AIRTABLE_PAT` | one of these two | Your Airtable Personal Access Token. |
130
+ | `AIRTABLE_PAT_KEYCHAIN_SERVICE` | one of these two | macOS: name of a Keychain generic-password item holding the PAT (read lazily at first tool call). Ignored when `AIRTABLE_PAT` is set. |
131
+ | `AIRTABLE_RPS` | no | Lower the per-base request rate below the default 4 rps (values above 4 clamp to 4). |
132
+ | `CONFIRM_SKIP_UNDER` | no | **Opt-out, default 0 (off):** skip the dry-run/confirm round-trip for writes touching fewer than N records (hard cap 25). Leave unset unless you know you want this. |
133
+
134
+ The server starts and answers `tools/list` with **no** secrets present — a missing PAT fails the
135
+ individual tool call with a structured setup error naming the exact env var, never the server.
136
+
137
+ ## Roadmap
138
+
139
+ | Version | Adds | Status |
140
+ |---|---|---|
141
+ | **v0.1.0** | The five tools above; dry-run/confirm write safety; schema validation; rate limiting + 429/5xx resilience | ✅ shipped |
142
+ | **v0.2.0** | `bulk_upsert` — paste 200 rows in chat, dedup against existing records by key field, batched under the rate limit, per-record report (N created / M updated / K failed, 0 silently dropped) | 🔜 next |
143
+ | **v0.3.0** | `list_operations` + `undo_operation` — an operation journal and conflict-safe undo | planned |
144
+ | Hosted | OAuth connect (no token handling), works in Claude web/mobile, retained audit + undo history | planned, gated on demand |
145
+
146
+ ## Development
147
+
148
+ ```bash
149
+ make install # poetry lock + install (self-provisions the venv)
150
+ make test # pytest — in-memory MCP client harness, no network
151
+ make lint # ruff check + format check
152
+ make check # the full pre-merge gate
153
+ make run # start the stdio server locally
154
+ make build # wheel + sdist; smoke with: uvx --from dist/airtable_write_mcp-*.whl airtable-write-mcp
155
+ ```
156
+
157
+ Tests talk to the server through a real in-memory MCP client session — no subprocess, no model
158
+ mocking. Design rules that will not change: no LLM calls inside the server, stderr-only logging
159
+ (stdout belongs to the protocol), and every mutating tool defaults to dry-run.
160
+
161
+ ## License
162
+
163
+ [MIT](LICENSE)
@@ -0,0 +1,55 @@
1
+ # pyproject.toml — mcp-server project type. See docs/specs/mcp-server-project-type.md §A.
2
+ #
3
+ # PEP 621 [project] metadata + poetry-core build backend. `mcp` is pinned to a released minor
4
+ # (never `main` — the SDK is mid-rename; see /mcp-conventions §1 before touching the pin).
5
+ # [project.scripts] is the console entry `uvx airtable-write-mcp` runs.
6
+
7
+ [project]
8
+ name = "airtable-write-mcp"
9
+ version = "0.1.0"
10
+ description = "Safe Airtable writes from Claude — dry-run previews by default; bulk upsert & undo on the roadmap."
11
+ readme = "README.md"
12
+ license = "MIT"
13
+ keywords = ["airtable", "mcp", "model-context-protocol", "claude", "dry-run"]
14
+ authors = [{ name = "Moai Loops" }]
15
+ requires-python = ">=3.12"
16
+ dependencies = [
17
+ "mcp (>=1.28,<1.29)",
18
+ "loguru (>=0.7,<1.0)",
19
+ "httpx (>=0.27,<1.0)",
20
+ ]
21
+
22
+ [project.scripts]
23
+ airtable-write-mcp = "airtable_write_mcp.__main__:main"
24
+
25
+ [project.urls]
26
+ Repository = "https://github.com/archypelago/airtable-write-mcp"
27
+ Changelog = "https://github.com/archypelago/airtable-write-mcp/blob/main/CHANGELOG.md"
28
+
29
+ [tool.poetry]
30
+ packages = [{ include = "airtable_write_mcp", from = "src" }]
31
+
32
+ [tool.poetry.group.dev.dependencies]
33
+ pytest = ">=8.0,<9.0"
34
+ anyio = ">=4.0,<5.0"
35
+ ruff = ">=0.6,<1.0"
36
+ mypy = ">=1.11,<2.0"
37
+
38
+ [build-system]
39
+ requires = ["poetry-core>=2.1.0,<3.0.0"]
40
+ build-backend = "poetry.core.masonry.api"
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
44
+ addopts = "-ra"
45
+
46
+ [tool.ruff]
47
+ line-length = 100
48
+ target-version = "py312"
49
+
50
+ [tool.ruff.lint]
51
+ select = ["E", "F", "I", "UP"]
52
+
53
+ [tool.mypy]
54
+ python_version = "3.12"
55
+ strict = true
@@ -0,0 +1,3 @@
1
+ """airtable-write-mcp — an MCP (Model Context Protocol) server."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,15 @@
1
+ """Console entry point — `poetry run <project>` / `python -m <pkg>` / `uvx <project>`.
2
+
3
+ Runs the stdio transport by default (see /mcp-conventions §7 for the --hosted seam — transport
4
+ is a run() argument, never baked into tool code).
5
+ """
6
+
7
+ from .server import mcp
8
+
9
+
10
+ def main() -> None:
11
+ mcp.run()
12
+
13
+
14
+ if __name__ == "__main__":
15
+ main()