@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
package/inspector.py DELETED
@@ -1,148 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- MCP Tool Inspector
4
- -----------------
5
- A simple script to list tools in a format Smithery can understand
6
- """
7
- import json
8
-
9
- # Define the tools manually
10
- tools = [
11
- {
12
- "name": "list_bases",
13
- "description": "List all accessible Airtable bases",
14
- "parameters": {
15
- "type": "object",
16
- "properties": {},
17
- "required": []
18
- },
19
- "returns": {
20
- "type": "string"
21
- }
22
- },
23
- {
24
- "name": "list_tables",
25
- "description": "List all tables in the specified base or the default base",
26
- "parameters": {
27
- "type": "object",
28
- "properties": {
29
- "base_id": {
30
- "type": "string",
31
- "description": "Optional base ID to use instead of the default"
32
- }
33
- },
34
- "required": []
35
- },
36
- "returns": {
37
- "type": "string"
38
- }
39
- },
40
- {
41
- "name": "list_records",
42
- "description": "List records from a table with optional filtering",
43
- "parameters": {
44
- "type": "object",
45
- "properties": {
46
- "table_name": {
47
- "type": "string",
48
- "description": "Name of the table to list records from"
49
- },
50
- "max_records": {
51
- "type": "integer",
52
- "description": "Maximum number of records to return (default: 100)"
53
- },
54
- "filter_formula": {
55
- "type": "string",
56
- "description": "Optional Airtable formula to filter records"
57
- }
58
- },
59
- "required": ["table_name"]
60
- },
61
- "returns": {
62
- "type": "string"
63
- }
64
- },
65
- {
66
- "name": "get_record",
67
- "description": "Get a specific record from a table",
68
- "parameters": {
69
- "type": "object",
70
- "properties": {
71
- "table_name": {
72
- "type": "string",
73
- "description": "Name of the table"
74
- },
75
- "record_id": {
76
- "type": "string",
77
- "description": "ID of the record to retrieve"
78
- }
79
- },
80
- "required": ["table_name", "record_id"]
81
- },
82
- "returns": {
83
- "type": "string"
84
- }
85
- },
86
- {
87
- "name": "create_records",
88
- "description": "Create records in a table from JSON string",
89
- "parameters": {
90
- "type": "object",
91
- "properties": {
92
- "table_name": {
93
- "type": "string",
94
- "description": "Name of the table"
95
- },
96
- "records_json": {
97
- "type": "string",
98
- "description": "JSON string containing the records to create"
99
- }
100
- },
101
- "required": ["table_name", "records_json"]
102
- },
103
- "returns": {
104
- "type": "string"
105
- }
106
- },
107
- {
108
- "name": "update_records",
109
- "description": "Update records in a table from JSON string",
110
- "parameters": {
111
- "type": "object",
112
- "properties": {
113
- "table_name": {
114
- "type": "string",
115
- "description": "Name of the table"
116
- },
117
- "records_json": {
118
- "type": "string",
119
- "description": "JSON string containing the records to update with IDs"
120
- }
121
- },
122
- "required": ["table_name", "records_json"]
123
- },
124
- "returns": {
125
- "type": "string"
126
- }
127
- },
128
- {
129
- "name": "set_base_id",
130
- "description": "Set the current Airtable base ID",
131
- "parameters": {
132
- "type": "object",
133
- "properties": {
134
- "base_id": {
135
- "type": "string",
136
- "description": "Base ID to set as the current base"
137
- }
138
- },
139
- "required": ["base_id"]
140
- },
141
- "returns": {
142
- "type": "string"
143
- }
144
- }
145
- ]
146
-
147
- # Print the tools as JSON
148
- print(json.dumps({"tools": tools}, indent=2))
@@ -1,337 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Airtable MCP Inspector Server
4
- -----------------------------
5
- A simple MCP server that implements the Airtable tools
6
- """
7
- import os
8
- import sys
9
- import json
10
- import logging
11
- import requests
12
- import argparse
13
- import traceback
14
- from typing import Optional, Dict, Any, List
15
-
16
- try:
17
- from mcp.server.fastmcp import FastMCP
18
- except ImportError:
19
- print("Error: MCP SDK not found. Please install with 'pip install mcp'")
20
- sys.exit(1)
21
-
22
- # Parse command line arguments
23
- def parse_args():
24
- parser = argparse.ArgumentParser(description="Airtable MCP Server")
25
- parser.add_argument("--token", dest="api_token", help="Airtable Personal Access Token")
26
- parser.add_argument("--base", dest="base_id", help="Airtable Base ID")
27
- parser.add_argument("--config", dest="config_json", help="Configuration as JSON (for Smithery integration)")
28
- return parser.parse_args()
29
-
30
- # Set up logging
31
- logging.basicConfig(level=logging.INFO)
32
- logger = logging.getLogger("airtable-mcp")
33
-
34
- # Parse arguments
35
- args = parse_args()
36
-
37
- # Handle config JSON from Smithery if provided
38
- config = {}
39
- if args.config_json:
40
- try:
41
- # Strip any trailing quotes or backslashes that might be present
42
- config_str = args.config_json.rstrip('\\"')
43
- # Additional sanitization for JSON format
44
- config_str = config_str.strip()
45
- # Handle escaped quotes
46
- if config_str.startswith('"') and config_str.endswith('"'):
47
- config_str = config_str[1:-1]
48
- # Fix escaped quotes within JSON
49
- config_str = config_str.replace('\\"', '"')
50
- # Replace escaped backslashes
51
- config_str = config_str.replace('\\\\', '\\')
52
-
53
- logger.info(f"Parsing sanitized config: {config_str}")
54
- config = json.loads(config_str)
55
- logger.info(f"Successfully parsed config: {config}")
56
- except json.JSONDecodeError as e:
57
- logger.error(f"Failed to parse config JSON: {e}")
58
- logger.error(f"Raw config string: {args.config_json}")
59
- # Try one more approach - sometimes config is double-quoted JSON
60
- try:
61
- # Try to interpret as Python string literal
62
- import ast
63
- literal_str = ast.literal_eval(f"'''{args.config_json}'''")
64
- config = json.loads(literal_str)
65
- logger.info(f"Successfully parsed config using ast: {config}")
66
- except Exception as ast_error:
67
- logger.error(f"Failed alternate parsing method: {ast_error}")
68
-
69
- # Create MCP server
70
- app = FastMCP("Airtable Tools")
71
-
72
- # Add error handling wrapper for all MCP methods
73
- def handle_exceptions(func):
74
- """Decorator to properly handle and format exceptions in MCP functions"""
75
- async def wrapper(*args, **kwargs):
76
- try:
77
- return await func(*args, **kwargs)
78
- except Exception as e:
79
- error_trace = traceback.format_exc()
80
- logger.error(f"Error in MCP handler: {str(e)}\n{error_trace}")
81
- sys.stderr.write(f"Error in MCP handler: {str(e)}\n{error_trace}\n")
82
-
83
- # For tool functions that return strings, return a formatted error message
84
- if hasattr(func, "__annotations__") and func.__annotations__.get("return") == str:
85
- return f"Error: {str(e)}"
86
-
87
- # For RPC methods that return dicts, return a properly formatted JSON error
88
- return {"error": {"code": -32000, "message": str(e)}}
89
- return wrapper
90
-
91
- # Patch the tool method to automatically apply error handling
92
- original_tool = app.tool
93
- def patched_tool(*args, **kwargs):
94
- def decorator(func):
95
- wrapped_func = handle_exceptions(func)
96
- return original_tool(*args, **kwargs)(wrapped_func)
97
- return decorator
98
-
99
- # Replace app.tool with our patched version
100
- app.tool = patched_tool
101
-
102
- # Get token from arguments, config, or environment
103
- token = args.api_token or config.get("airtable_token", "") or os.environ.get("AIRTABLE_PERSONAL_ACCESS_TOKEN", "")
104
- # Clean up token if it has trailing quote
105
- if token and token.endswith('"'):
106
- token = token[:-1]
107
-
108
- base_id = args.base_id or config.get("base_id", "") or os.environ.get("AIRTABLE_BASE_ID", "")
109
-
110
- if not token:
111
- logger.warning("No Airtable API token provided. Use --token, --config, or set AIRTABLE_PERSONAL_ACCESS_TOKEN environment variable.")
112
- else:
113
- logger.info("Airtable authentication configured")
114
-
115
- if base_id:
116
- logger.info(f"Using base ID: {base_id}")
117
- else:
118
- logger.warning("No base ID provided. Use --base, --config, or set AIRTABLE_BASE_ID environment variable.")
119
-
120
- # Helper functions for Airtable API calls
121
- async def api_call(endpoint, method="GET", data=None, params=None):
122
- """Make an Airtable API call"""
123
- if not token:
124
- return {"error": "No Airtable API token provided. Use --token, --config, or set AIRTABLE_PERSONAL_ACCESS_TOKEN environment variable."}
125
-
126
- headers = {
127
- "Authorization": f"Bearer {token}",
128
- "Content-Type": "application/json"
129
- }
130
-
131
- url = f"https://api.airtable.com/v0/{endpoint}"
132
-
133
- try:
134
- if method == "GET":
135
- response = requests.get(url, headers=headers, params=params)
136
- elif method == "POST":
137
- response = requests.post(url, headers=headers, json=data)
138
- elif method == "PATCH":
139
- response = requests.patch(url, headers=headers, json=data)
140
- elif method == "DELETE":
141
- response = requests.delete(url, headers=headers, params=params)
142
- else:
143
- raise ValueError(f"Unsupported method: {method}")
144
-
145
- response.raise_for_status()
146
- return response.json()
147
- except Exception as e:
148
- logger.error(f"API call error: {str(e)}")
149
- return {"error": str(e)}
150
-
151
- # Define MCP tool functions
152
- @app.tool()
153
- async def list_bases() -> str:
154
- """List all accessible Airtable bases"""
155
- if not token:
156
- return "Please provide an Airtable API token to list your bases."
157
-
158
- result = await api_call("meta/bases")
159
-
160
- if "error" in result:
161
- return f"Error: {result['error']}"
162
-
163
- bases = result.get("bases", [])
164
- if not bases:
165
- return "No bases found accessible with your token."
166
-
167
- base_list = [f"{i+1}. {base['name']} (ID: {base['id']})" for i, base in enumerate(bases)]
168
- return "Available bases:\n" + "\n".join(base_list)
169
-
170
- @app.tool()
171
- async def list_tables(base_id_param: Optional[str] = None) -> str:
172
- """List all tables in the specified base or the default base"""
173
- global base_id
174
- current_base = base_id_param or base_id
175
-
176
- if not token:
177
- return "Please provide an Airtable API token to list tables."
178
-
179
- if not current_base:
180
- return "Error: No base ID provided. Please specify a base_id or set AIRTABLE_BASE_ID environment variable."
181
-
182
- result = await api_call(f"meta/bases/{current_base}/tables")
183
-
184
- if "error" in result:
185
- return f"Error: {result['error']}"
186
-
187
- tables = result.get("tables", [])
188
- if not tables:
189
- return "No tables found in this base."
190
-
191
- table_list = [f"{i+1}. {table['name']} (ID: {table['id']}, Fields: {len(table.get('fields', []))})"
192
- for i, table in enumerate(tables)]
193
- return "Tables in this base:\n" + "\n".join(table_list)
194
-
195
- @app.tool()
196
- async def list_records(table_name: str, max_records: Optional[int] = 100, filter_formula: Optional[str] = None) -> str:
197
- """List records from a table with optional filtering"""
198
- if not token:
199
- return "Please provide an Airtable API token to list records."
200
-
201
- if not base_id:
202
- return "Error: No base ID set. Please use --base or set AIRTABLE_BASE_ID environment variable."
203
-
204
- params = {"maxRecords": max_records}
205
-
206
- if filter_formula:
207
- params["filterByFormula"] = filter_formula
208
-
209
- result = await api_call(f"{base_id}/{table_name}", params=params)
210
-
211
- if "error" in result:
212
- return f"Error: {result['error']}"
213
-
214
- records = result.get("records", [])
215
- if not records:
216
- return "No records found in this table."
217
-
218
- # Format the records for display
219
- formatted_records = []
220
- for i, record in enumerate(records):
221
- record_id = record.get("id", "unknown")
222
- fields = record.get("fields", {})
223
- field_text = ", ".join([f"{k}: {v}" for k, v in fields.items()])
224
- formatted_records.append(f"{i+1}. ID: {record_id} - {field_text}")
225
-
226
- return "Records:\n" + "\n".join(formatted_records)
227
-
228
- @app.tool()
229
- async def get_record(table_name: str, record_id: str) -> str:
230
- """Get a specific record from a table"""
231
- if not token:
232
- return "Please provide an Airtable API token to get records."
233
-
234
- if not base_id:
235
- return "Error: No base ID set. Please set AIRTABLE_BASE_ID environment variable."
236
-
237
- result = await api_call(f"{base_id}/{table_name}/{record_id}")
238
-
239
- if "error" in result:
240
- return f"Error: {result['error']}"
241
-
242
- fields = result.get("fields", {})
243
- if not fields:
244
- return f"Record {record_id} found but contains no fields."
245
-
246
- # Format the fields for display
247
- formatted_fields = []
248
- for key, value in fields.items():
249
- formatted_fields.append(f"{key}: {value}")
250
-
251
- return f"Record ID: {record_id}\n" + "\n".join(formatted_fields)
252
-
253
- @app.tool()
254
- async def create_records(table_name: str, records_json: str) -> str:
255
- """Create records in a table from JSON string"""
256
- if not token:
257
- return "Please provide an Airtable API token to create records."
258
-
259
- if not base_id:
260
- return "Error: No base ID set. Please set AIRTABLE_BASE_ID environment variable."
261
-
262
- try:
263
- records_data = json.loads(records_json)
264
-
265
- # Format the records for Airtable API
266
- if not isinstance(records_data, list):
267
- records_data = [records_data]
268
-
269
- records = [{"fields": record} for record in records_data]
270
-
271
- data = {"records": records}
272
- result = await api_call(f"{base_id}/{table_name}", method="POST", data=data)
273
-
274
- if "error" in result:
275
- return f"Error: {result['error']}"
276
-
277
- created_records = result.get("records", [])
278
- return f"Successfully created {len(created_records)} records."
279
-
280
- except json.JSONDecodeError:
281
- return "Error: Invalid JSON format in records_json parameter."
282
- except Exception as e:
283
- return f"Error creating records: {str(e)}"
284
-
285
- @app.tool()
286
- async def update_records(table_name: str, records_json: str) -> str:
287
- """Update records in a table from JSON string"""
288
- if not token:
289
- return "Please provide an Airtable API token to update records."
290
-
291
- if not base_id:
292
- return "Error: No base ID set. Please set AIRTABLE_BASE_ID environment variable."
293
-
294
- try:
295
- records_data = json.loads(records_json)
296
-
297
- # Format the records for Airtable API
298
- if not isinstance(records_data, list):
299
- records_data = [records_data]
300
-
301
- records = []
302
- for record in records_data:
303
- if "id" not in record:
304
- return "Error: Each record must have an 'id' field."
305
-
306
- rec_id = record.pop("id")
307
- fields = record.get("fields", record) # Support both {id, fields} format and direct fields
308
- records.append({"id": rec_id, "fields": fields})
309
-
310
- data = {"records": records}
311
- result = await api_call(f"{base_id}/{table_name}", method="PATCH", data=data)
312
-
313
- if "error" in result:
314
- return f"Error: {result['error']}"
315
-
316
- updated_records = result.get("records", [])
317
- return f"Successfully updated {len(updated_records)} records."
318
-
319
- except json.JSONDecodeError:
320
- return "Error: Invalid JSON format in records_json parameter."
321
- except Exception as e:
322
- return f"Error updating records: {str(e)}"
323
-
324
- @app.tool()
325
- async def set_base_id(base_id_param: str) -> str:
326
- """Set the current Airtable base ID"""
327
- global base_id
328
- base_id = base_id_param
329
- return f"Base ID set to: {base_id}"
330
-
331
- # Note: rpc_method is not available in the current MCP version
332
- # These methods would be used for Claude-specific functionality
333
- # but are not needed for basic MCP operation
334
-
335
- # Start the server
336
- if __name__ == "__main__":
337
- app.start()