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.
- airtable_write_mcp-0.1.0/LICENSE +21 -0
- airtable_write_mcp-0.1.0/PKG-INFO +183 -0
- airtable_write_mcp-0.1.0/README.md +163 -0
- airtable_write_mcp-0.1.0/pyproject.toml +55 -0
- airtable_write_mcp-0.1.0/src/airtable_write_mcp/__init__.py +3 -0
- airtable_write_mcp-0.1.0/src/airtable_write_mcp/__main__.py +15 -0
- airtable_write_mcp-0.1.0/src/airtable_write_mcp/airtable.py +854 -0
- airtable_write_mcp-0.1.0/src/airtable_write_mcp/server.py +1301 -0
|
@@ -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,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()
|