@rashidazarang/airtable-mcp 2.1.0 → 2.2.0

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.
Files changed (155) hide show
  1. package/README.md +1 -1
  2. package/airtable_simple_production.js +387 -5
  3. package/examples/claude_simple_config.json +0 -9
  4. package/package.json +10 -1
  5. package/.github/ISSUE_TEMPLATE/bug-report.yml +0 -173
  6. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  7. package/.github/ISSUE_TEMPLATE/custom.md +0 -10
  8. package/.github/ISSUE_TEMPLATE/feature-request.yml +0 -209
  9. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  10. package/.github/ISSUE_TEMPLATE/security-report.yml +0 -216
  11. package/.github/pull_request_template.md +0 -245
  12. package/.github/workflows/ci-cd.yml +0 -408
  13. package/.github/workflows/security-audit.yml +0 -316
  14. package/API_DOCUMENTATION.md +0 -897
  15. package/CAPABILITY_REPORT.md +0 -118
  16. package/CLAUDE_INTEGRATION.md +0 -96
  17. package/CODE_OF_CONDUCT.md +0 -181
  18. package/CONTRIBUTING.md +0 -81
  19. package/DEVELOPMENT.md +0 -190
  20. package/Dockerfile +0 -39
  21. package/Dockerfile.node +0 -20
  22. package/Dockerfile.production +0 -127
  23. package/IMPROVEMENT_PROPOSAL.md +0 -371
  24. package/INSTALLATION.md +0 -183
  25. package/ISSUE_RESPONSES.md +0 -171
  26. package/MCP_REVIEW_SUMMARY.md +0 -142
  27. package/QUICK_START.md +0 -60
  28. package/RELEASE_NOTES_v1.2.0.md +0 -50
  29. package/RELEASE_NOTES_v1.2.1.md +0 -40
  30. package/RELEASE_NOTES_v1.2.2.md +0 -48
  31. package/RELEASE_NOTES_v1.2.3.md +0 -105
  32. package/RELEASE_NOTES_v1.2.4.md +0 -60
  33. package/RELEASE_NOTES_v1.4.0.md +0 -104
  34. package/RELEASE_NOTES_v1.5.0.md +0 -185
  35. package/RELEASE_NOTES_v1.6.0.md +0 -248
  36. package/SECURITY_NOTICE.md +0 -40
  37. package/airtable-clipper/CHANGELOG.md +0 -198
  38. package/airtable-clipper/CHROME_STORE_SUBMISSION.md +0 -343
  39. package/airtable-clipper/LAUNCH_STRATEGY.md +0 -495
  40. package/airtable-clipper/LICENSE +0 -21
  41. package/airtable-clipper/OAUTH_SETUP.md +0 -51
  42. package/airtable-clipper/PRIVACY_POLICY.md +0 -187
  43. package/airtable-clipper/README.md +0 -575
  44. package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +0 -273
  45. package/airtable-clipper/build.sh +0 -85
  46. package/airtable-clipper/docs/QUICK_START.md +0 -99
  47. package/airtable-clipper/docs/SETUP.md +0 -291
  48. package/airtable-clipper/extension/background.js +0 -337
  49. package/airtable-clipper/extension/base-setup.html +0 -324
  50. package/airtable-clipper/extension/base-setup.js +0 -471
  51. package/airtable-clipper/extension/content.js +0 -771
  52. package/airtable-clipper/extension/icons/README.md +0 -69
  53. package/airtable-clipper/extension/icons/icon-16.png +0 -3
  54. package/airtable-clipper/extension/manifest.json +0 -73
  55. package/airtable-clipper/extension/popup.html +0 -144
  56. package/airtable-clipper/extension/popup.js +0 -475
  57. package/airtable-clipper/extension/styles/content.css +0 -229
  58. package/airtable-clipper/extension/styles/popup.css +0 -477
  59. package/airtable-clipper/privacy-policy.md +0 -63
  60. package/airtable-clipper/releases/v1.0.0/background.js +0 -337
  61. package/airtable-clipper/releases/v1.0.0/base-setup.html +0 -324
  62. package/airtable-clipper/releases/v1.0.0/base-setup.js +0 -471
  63. package/airtable-clipper/releases/v1.0.0/content.js +0 -771
  64. package/airtable-clipper/releases/v1.0.0/icons/README.md +0 -69
  65. package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +0 -2
  66. package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +0 -3
  67. package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +0 -2
  68. package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +0 -2
  69. package/airtable-clipper/releases/v1.0.0/manifest.json +0 -73
  70. package/airtable-clipper/releases/v1.0.0/popup.html +0 -144
  71. package/airtable-clipper/releases/v1.0.0/popup.js +0 -475
  72. package/airtable-clipper/releases/v1.0.0/sidepanel.html +0 -25
  73. package/airtable-clipper/releases/v1.0.0/styles/content.css +0 -229
  74. package/airtable-clipper/releases/v1.0.0/styles/popup.css +0 -477
  75. package/airtable-clipper/releases/v1.0.1/background.js +0 -337
  76. package/airtable-clipper/releases/v1.0.1/base-setup.html +0 -324
  77. package/airtable-clipper/releases/v1.0.1/base-setup.js +0 -471
  78. package/airtable-clipper/releases/v1.0.1/content.js +0 -771
  79. package/airtable-clipper/releases/v1.0.1/icons/README.md +0 -69
  80. package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +0 -2
  81. package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +0 -3
  82. package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +0 -2
  83. package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +0 -2
  84. package/airtable-clipper/releases/v1.0.1/manifest.json +0 -70
  85. package/airtable-clipper/releases/v1.0.1/popup.html +0 -157
  86. package/airtable-clipper/releases/v1.0.1/popup.js +0 -562
  87. package/airtable-clipper/releases/v1.0.1/sidepanel.html +0 -25
  88. package/airtable-clipper/releases/v1.0.1/styles/content.css +0 -229
  89. package/airtable-clipper/releases/v1.0.1/styles/popup.css +0 -647
  90. package/airtable-clipper/releases/v1.0.2/background.js +0 -337
  91. package/airtable-clipper/releases/v1.0.2/base-setup.html +0 -324
  92. package/airtable-clipper/releases/v1.0.2/base-setup.js +0 -471
  93. package/airtable-clipper/releases/v1.0.2/content.js +0 -771
  94. package/airtable-clipper/releases/v1.0.2/icons/README.md +0 -69
  95. package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +0 -2
  96. package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +0 -3
  97. package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +0 -2
  98. package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +0 -2
  99. package/airtable-clipper/releases/v1.0.2/manifest.json +0 -62
  100. package/airtable-clipper/releases/v1.0.2/popup.html +0 -157
  101. package/airtable-clipper/releases/v1.0.2/popup.js +0 -567
  102. package/airtable-clipper/releases/v1.0.2/sidepanel.html +0 -25
  103. package/airtable-clipper/releases/v1.0.2/styles/content.css +0 -229
  104. package/airtable-clipper/releases/v1.0.2/styles/popup.css +0 -647
  105. package/airtable-clipper/terms-of-service.md +0 -124
  106. package/airtable-clipper/test-credentials.md +0 -61
  107. package/airtable-clipper/test-extension/background.js +0 -337
  108. package/airtable-clipper/test-extension/base-setup.html +0 -324
  109. package/airtable-clipper/test-extension/base-setup.js +0 -471
  110. package/airtable-clipper/test-extension/content.js +0 -873
  111. package/airtable-clipper/test-extension/icons/README.md +0 -69
  112. package/airtable-clipper/test-extension/icons/icon-128.png +0 -2
  113. package/airtable-clipper/test-extension/icons/icon-16.png +0 -3
  114. package/airtable-clipper/test-extension/icons/icon-32.png +0 -2
  115. package/airtable-clipper/test-extension/icons/icon-48.png +0 -2
  116. package/airtable-clipper/test-extension/manifest.json +0 -72
  117. package/airtable-clipper/test-extension/popup.html +0 -274
  118. package/airtable-clipper/test-extension/popup.js +0 -729
  119. package/airtable-clipper/test-extension/sidepanel.html +0 -25
  120. package/airtable-clipper/test-extension/styles/content.css +0 -229
  121. package/airtable-clipper/test-extension/styles/popup.css +0 -794
  122. package/airtable_mcp/__init__.py +0 -5
  123. package/airtable_mcp/src/server.py +0 -329
  124. package/airtable_mcp_v2.js +0 -1505
  125. package/airtable_mcp_v2_oauth.js +0 -1048
  126. package/airtable_mcp_v3_advanced.js +0 -1161
  127. package/cleanup.sh +0 -71
  128. package/docker-compose.production.yml +0 -366
  129. package/helm/airtable-mcp/Chart.yaml +0 -122
  130. package/helm/airtable-mcp/values.yaml +0 -538
  131. package/index.js +0 -179
  132. package/inspector.py +0 -148
  133. package/inspector_server.py +0 -337
  134. package/k8s/deployment.yaml +0 -402
  135. package/k8s/namespace.yaml +0 -108
  136. package/k8s/service.yaml +0 -194
  137. package/monitoring/alerts.yml +0 -289
  138. package/monitoring/prometheus.yml +0 -224
  139. package/publish-steps.txt +0 -27
  140. package/quick_test.sh +0 -30
  141. package/requirements.txt +0 -10
  142. package/setup.py +0 -29
  143. package/simple_airtable_server.py +0 -151
  144. package/smithery.yaml +0 -45
  145. package/test_all_features.sh +0 -146
  146. package/test_all_operations.sh +0 -120
  147. package/test_client.py +0 -70
  148. package/test_enhanced_features.js +0 -389
  149. package/test_mcp_comprehensive.js +0 -163
  150. package/test_mock_server.js +0 -180
  151. package/test_v1.4.0_final.sh +0 -131
  152. package/test_v1.5.0_comprehensive.sh +0 -96
  153. package/test_v1.5.0_final.sh +0 -224
  154. package/test_v1.6.0_comprehensive.sh +0 -187
  155. package/test_webhooks.sh +0 -105
@@ -1,5 +0,0 @@
1
- """
2
- Airtable MCP - Airtable integration for AI via Model Context Protocol
3
- """
4
-
5
- __version__ = "0.1.0"
@@ -1,329 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Airtable MCP Server
4
- -------------------
5
- This is a Model Context Protocol (MCP) server that exposes Airtable operations as tools.
6
- """
7
- import os
8
- import sys
9
- import json
10
- import asyncio
11
- import logging
12
- import argparse
13
- from contextlib import asynccontextmanager
14
- from typing import Any, Dict, List, Optional, AsyncIterator, Callable
15
- from dotenv import load_dotenv
16
-
17
- print(f"Python version: {sys.version}")
18
- print(f"Python executable: {sys.executable}")
19
- print(f"Python path: {sys.path}")
20
-
21
- # Import MCP-related modules - will be available when run with Python 3.10+
22
- try:
23
- from mcp.server.fastmcp import FastMCP
24
- from mcp.server import stdio
25
- print("Successfully imported MCP modules")
26
- except ImportError as e:
27
- print(f"Error importing MCP modules: {e}")
28
- print("Error: MCP SDK requires Python 3.10+")
29
- print("Please install Python 3.10 or newer and try again.")
30
- sys.exit(1)
31
-
32
- # Set up logging
33
- logging.basicConfig(level=logging.INFO)
34
- logger = logging.getLogger("airtable-mcp")
35
-
36
- # Parse command line arguments
37
- def parse_args():
38
- parser = argparse.ArgumentParser(description="Airtable MCP Server")
39
- parser.add_argument("--token", dest="api_token", help="Airtable Personal Access Token")
40
- parser.add_argument("--base", dest="base_id", help="Airtable Base ID")
41
- parser.add_argument("--port", type=int, default=8080, help="MCP server port for dev mode")
42
- parser.add_argument("--host", default="127.0.0.1", help="MCP server host for dev mode")
43
- parser.add_argument("--dev", action="store_true", help="Run in development mode")
44
- return parser.parse_args()
45
-
46
- # Load environment variables as fallback
47
- load_dotenv()
48
-
49
- # Create MCP server
50
- mcp = FastMCP("Airtable Tools")
51
-
52
- # Server state will be initialized in main()
53
- server_state = {
54
- "base_id": "",
55
- "token": "",
56
- }
57
-
58
- # Authentication middleware
59
- @mcp.middleware
60
- async def auth_middleware(context, next_handler):
61
- # Skip auth check for tool listing
62
- if hasattr(context, 'operation') and context.operation == "list_tools":
63
- return await next_handler(context)
64
-
65
- # Allow all operations without a token check - actual API calls will be checked later
66
- return await next_handler(context)
67
-
68
- # Helper functions for Airtable API calls
69
- async def api_call(endpoint, method="GET", data=None, params=None):
70
- """Make an Airtable API call"""
71
- import requests
72
-
73
- # Check if token is available before making API calls
74
- if not server_state["token"]:
75
- return {"error": "No Airtable API token provided. Please set via --token or AIRTABLE_PERSONAL_ACCESS_TOKEN"}
76
-
77
- headers = {
78
- "Authorization": f"Bearer {server_state['token']}",
79
- "Content-Type": "application/json"
80
- }
81
-
82
- url = f"https://api.airtable.com/v0/{endpoint}"
83
-
84
- try:
85
- if method == "GET":
86
- response = requests.get(url, headers=headers, params=params)
87
- elif method == "POST":
88
- response = requests.post(url, headers=headers, json=data)
89
- elif method == "PATCH":
90
- response = requests.patch(url, headers=headers, json=data)
91
- elif method == "DELETE":
92
- response = requests.delete(url, headers=headers, params=params)
93
- else:
94
- raise ValueError(f"Unsupported method: {method}")
95
-
96
- response.raise_for_status()
97
- return response.json()
98
- except Exception as e:
99
- logger.error(f"API call error: {str(e)}")
100
- return {"error": str(e)}
101
-
102
-
103
- # Define MCP tool functions
104
-
105
- @mcp.tool()
106
- async def list_bases() -> str:
107
- """List all accessible Airtable bases"""
108
- if not server_state["token"]:
109
- return "Please provide an Airtable API token to list your bases."
110
-
111
- result = await api_call("meta/bases")
112
-
113
- if "error" in result:
114
- return f"Error: {result['error']}"
115
-
116
- bases = result.get("bases", [])
117
- if not bases:
118
- return "No bases found accessible with your token."
119
-
120
- base_list = [f"{i+1}. {base['name']} (ID: {base['id']})" for i, base in enumerate(bases)]
121
- return "Available bases:\n" + "\n".join(base_list)
122
-
123
-
124
- @mcp.tool()
125
- async def list_tables(base_id: Optional[str] = None) -> str:
126
- """List all tables in the specified base or the default base"""
127
- if not server_state["token"]:
128
- return "Please provide an Airtable API token to list tables."
129
-
130
- base = base_id or server_state["base_id"]
131
-
132
- if not base:
133
- return "Error: No base ID provided. Please specify a base_id or set AIRTABLE_BASE_ID in your .env file."
134
-
135
- result = await api_call(f"meta/bases/{base}/tables")
136
-
137
- if "error" in result:
138
- return f"Error: {result['error']}"
139
-
140
- tables = result.get("tables", [])
141
- if not tables:
142
- return "No tables found in this base."
143
-
144
- table_list = [f"{i+1}. {table['name']} (ID: {table['id']}, Fields: {len(table.get('fields', []))})"
145
- for i, table in enumerate(tables)]
146
- return "Tables in this base:\n" + "\n".join(table_list)
147
-
148
-
149
- @mcp.tool()
150
- async def list_records(table_name: str, max_records: Optional[int] = 100, filter_formula: Optional[str] = None) -> str:
151
- """List records from a table with optional filtering"""
152
- if not server_state["token"]:
153
- return "Please provide an Airtable API token to list records."
154
-
155
- base = server_state["base_id"]
156
-
157
- if not base:
158
- return "Error: No base ID set. Please set a base ID."
159
-
160
- params = {"maxRecords": max_records}
161
-
162
- if filter_formula:
163
- params["filterByFormula"] = filter_formula
164
-
165
- result = await api_call(f"{base}/{table_name}", params=params)
166
-
167
- if "error" in result:
168
- return f"Error: {result['error']}"
169
-
170
- records = result.get("records", [])
171
- if not records:
172
- return "No records found in this table."
173
-
174
- # Format the records for display
175
- formatted_records = []
176
- for i, record in enumerate(records):
177
- record_id = record.get("id", "unknown")
178
- fields = record.get("fields", {})
179
- field_text = ", ".join([f"{k}: {v}" for k, v in fields.items()])
180
- formatted_records.append(f"{i+1}. ID: {record_id} - {field_text}")
181
-
182
- return "Records:\n" + "\n".join(formatted_records)
183
-
184
-
185
- @mcp.tool()
186
- async def get_record(table_name: str, record_id: str) -> str:
187
- """Get a specific record from a table"""
188
- if not server_state["token"]:
189
- return "Please provide an Airtable API token to get records."
190
-
191
- base = server_state["base_id"]
192
-
193
- if not base:
194
- return "Error: No base ID set. Please set a base ID."
195
-
196
- result = await api_call(f"{base}/{table_name}/{record_id}")
197
-
198
- if "error" in result:
199
- return f"Error: {result['error']}"
200
-
201
- fields = result.get("fields", {})
202
- if not fields:
203
- return f"Record {record_id} found but contains no fields."
204
-
205
- # Format the fields for display
206
- formatted_fields = []
207
- for key, value in fields.items():
208
- formatted_fields.append(f"{key}: {value}")
209
-
210
- return f"Record ID: {record_id}\n" + "\n".join(formatted_fields)
211
-
212
-
213
- @mcp.tool()
214
- async def create_records(table_name: str, records_json: str) -> str:
215
- """Create records in a table from JSON string"""
216
- if not server_state["token"]:
217
- return "Please provide an Airtable API token to create records."
218
-
219
- base = server_state["base_id"]
220
-
221
- if not base:
222
- return "Error: No base ID set. Please set a base ID."
223
-
224
- try:
225
- records_data = json.loads(records_json)
226
-
227
- # Format the records for Airtable API
228
- if not isinstance(records_data, list):
229
- records_data = [records_data]
230
-
231
- records = [{"fields": record} for record in records_data]
232
-
233
- data = {"records": records}
234
- result = await api_call(f"{base}/{table_name}", method="POST", data=data)
235
-
236
- if "error" in result:
237
- return f"Error: {result['error']}"
238
-
239
- created_records = result.get("records", [])
240
- return f"Successfully created {len(created_records)} records."
241
-
242
- except json.JSONDecodeError:
243
- return "Error: Invalid JSON format in records_json parameter."
244
- except Exception as e:
245
- return f"Error creating records: {str(e)}"
246
-
247
-
248
- @mcp.tool()
249
- async def update_records(table_name: str, records_json: str) -> str:
250
- """Update records in a table from JSON string"""
251
- if not server_state["token"]:
252
- return "Please provide an Airtable API token to update records."
253
-
254
- base = server_state["base_id"]
255
-
256
- if not base:
257
- return "Error: No base ID set. Please set a base ID."
258
-
259
- try:
260
- records_data = json.loads(records_json)
261
-
262
- # Format the records for Airtable API
263
- if not isinstance(records_data, list):
264
- records_data = [records_data]
265
-
266
- records = []
267
- for record in records_data:
268
- if "id" not in record:
269
- return "Error: Each record must have an 'id' field."
270
-
271
- rec_id = record.pop("id")
272
- fields = record.get("fields", record) # Support both {id, fields} format and direct fields
273
- records.append({"id": rec_id, "fields": fields})
274
-
275
- data = {"records": records}
276
- result = await api_call(f"{base}/{table_name}", method="PATCH", data=data)
277
-
278
- if "error" in result:
279
- return f"Error: {result['error']}"
280
-
281
- updated_records = result.get("records", [])
282
- return f"Successfully updated {len(updated_records)} records."
283
-
284
- except json.JSONDecodeError:
285
- return "Error: Invalid JSON format in records_json parameter."
286
- except Exception as e:
287
- return f"Error updating records: {str(e)}"
288
-
289
-
290
- @mcp.tool()
291
- async def set_base_id(base_id: str) -> str:
292
- """Set the current Airtable base ID"""
293
- server_state["base_id"] = base_id
294
- return f"Base ID set to: {base_id}"
295
-
296
-
297
- def main():
298
- """Run the MCP server"""
299
- try:
300
- # Parse command line arguments
301
- args = parse_args()
302
-
303
- # Set server state from command line args or fallback to env vars
304
- server_state["token"] = args.api_token or os.getenv("AIRTABLE_PERSONAL_ACCESS_TOKEN", "")
305
- server_state["base_id"] = args.base_id or os.getenv("AIRTABLE_BASE_ID", "")
306
-
307
- if not server_state["token"]:
308
- logger.warning("No Airtable API token provided. Please set via --token or AIRTABLE_PERSONAL_ACCESS_TOKEN")
309
- logger.info("Tool listing will work but API calls will require a token")
310
-
311
- # Setup asyncio event loop
312
- if sys.platform == 'win32':
313
- asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
314
-
315
- # Run the server
316
- if args.dev:
317
- # Development mode
318
- mcp.run(host=args.host, port=args.port)
319
- else:
320
- # Production mode - stdio interface for MCP
321
- mcp.run()
322
-
323
- except Exception as e:
324
- logger.error(f"Server error: {str(e)}")
325
- sys.exit(1)
326
-
327
-
328
- if __name__ == "__main__":
329
- main()