@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,151 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Simple Airtable MCP Server for Claude
4
- -------------------------------------
5
- A minimal MCP server that implements Airtable tools and Claude's special methods
6
- """
7
- import os
8
- import sys
9
- import json
10
- import logging
11
- import requests
12
- import traceback
13
- from typing import Dict, Any, List, Optional
14
-
15
- # Check if MCP SDK is installed
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
- if len(sys.argv) < 5:
24
- print("Usage: python3 simple_airtable_server.py --token YOUR_TOKEN --base YOUR_BASE_ID")
25
- sys.exit(1)
26
-
27
- # Get the token and base ID from command line arguments
28
- token = None
29
- base_id = None
30
- for i in range(1, len(sys.argv)):
31
- if sys.argv[i] == "--token" and i+1 < len(sys.argv):
32
- token = sys.argv[i+1]
33
- elif sys.argv[i] == "--base" and i+1 < len(sys.argv):
34
- base_id = sys.argv[i+1]
35
-
36
- if not token:
37
- print("Error: No Airtable token provided. Use --token parameter.")
38
- sys.exit(1)
39
-
40
- if not base_id:
41
- print("Error: No base ID provided. Use --base parameter.")
42
- sys.exit(1)
43
-
44
- # Set up logging
45
- logging.basicConfig(level=logging.INFO)
46
- logger = logging.getLogger("airtable-mcp")
47
-
48
- # Create MCP server
49
- app = FastMCP("Airtable Tools")
50
-
51
- # Helper function for Airtable API calls
52
- async def airtable_api_call(endpoint, method="GET", data=None, params=None):
53
- """Make an Airtable API call with error handling"""
54
- headers = {
55
- "Authorization": f"Bearer {token}",
56
- "Content-Type": "application/json"
57
- }
58
-
59
- url = f"https://api.airtable.com/v0/{endpoint}"
60
-
61
- try:
62
- if method == "GET":
63
- response = requests.get(url, headers=headers, params=params)
64
- elif method == "POST":
65
- response = requests.post(url, headers=headers, json=data)
66
- else:
67
- raise ValueError(f"Unsupported method: {method}")
68
-
69
- response.raise_for_status()
70
- return response.json()
71
- except Exception as e:
72
- logger.error(f"API call error: {str(e)}")
73
- return {"error": str(e)}
74
-
75
- # Claude-specific methods
76
- @app.rpc_method("resources/list")
77
- async def resources_list(params: Dict = None) -> Dict:
78
- """List available Airtable resources for Claude"""
79
- try:
80
- # Return a simple list of resources
81
- resources = [
82
- {"id": "airtable_tables", "name": "Airtable Tables", "description": "Tables in your Airtable base"}
83
- ]
84
- return {"resources": resources}
85
- except Exception as e:
86
- logger.error(f"Error in resources/list: {str(e)}")
87
- return {"error": {"code": -32000, "message": str(e)}}
88
-
89
- @app.rpc_method("prompts/list")
90
- async def prompts_list(params: Dict = None) -> Dict:
91
- """List available prompts for Claude"""
92
- try:
93
- # Return a simple list of prompts
94
- prompts = [
95
- {"id": "tables_prompt", "name": "List Tables", "description": "List all tables"}
96
- ]
97
- return {"prompts": prompts}
98
- except Exception as e:
99
- logger.error(f"Error in prompts/list: {str(e)}")
100
- return {"error": {"code": -32000, "message": str(e)}}
101
-
102
- # Airtable tool functions
103
- @app.tool()
104
- async def list_tables() -> str:
105
- """List all tables in the specified base"""
106
- try:
107
- result = await airtable_api_call(f"meta/bases/{base_id}/tables")
108
-
109
- if "error" in result:
110
- return f"Error: {result['error']}"
111
-
112
- tables = result.get("tables", [])
113
- if not tables:
114
- return "No tables found in this base."
115
-
116
- table_list = [f"{i+1}. {table['name']} (ID: {table['id']})"
117
- for i, table in enumerate(tables)]
118
- return "Tables in this base:\n" + "\n".join(table_list)
119
- except Exception as e:
120
- return f"Error listing tables: {str(e)}"
121
-
122
- @app.tool()
123
- async def list_records(table_name: str, max_records: int = 100) -> str:
124
- """List records from a table"""
125
- try:
126
- params = {"maxRecords": max_records}
127
- result = await airtable_api_call(f"{base_id}/{table_name}", params=params)
128
-
129
- if "error" in result:
130
- return f"Error: {result['error']}"
131
-
132
- records = result.get("records", [])
133
- if not records:
134
- return "No records found in this table."
135
-
136
- # Format the records for display
137
- formatted_records = []
138
- for i, record in enumerate(records):
139
- record_id = record.get("id", "unknown")
140
- fields = record.get("fields", {})
141
- field_text = ", ".join([f"{k}: {v}" for k, v in fields.items()])
142
- formatted_records.append(f"{i+1}. ID: {record_id} - {field_text}")
143
-
144
- return "Records:\n" + "\n".join(formatted_records)
145
- except Exception as e:
146
- return f"Error listing records: {str(e)}"
147
-
148
- # Start the server
149
- if __name__ == "__main__":
150
- print(f"Starting Airtable MCP Server with token {token[:5]}...{token[-5:]} and base {base_id}")
151
- app.start()
package/smithery.yaml DELETED
@@ -1,45 +0,0 @@
1
- # Smithery.ai configuration
2
- name: "@rashidazarang/airtable-mcp"
3
- version: "1.2.4"
4
- description: "Connect your AI tools directly to Airtable. Query, create, update, and delete records using natural language. Features include base management, table operations, schema manipulation, record filtering, and data migration—all through a standardized MCP interface compatible with Claude Desktop and other Claude-powered editors."
5
-
6
- startCommand:
7
- type: stdio
8
- configSchema:
9
- type: object
10
- properties:
11
- airtable_token:
12
- type: string
13
- description: "Your Airtable Personal Access Token"
14
- required: true
15
- base_id:
16
- type: string
17
- description: "Your default Airtable base ID"
18
- required: true
19
- required: ["airtable_token", "base_id"]
20
- commandFunction: |
21
- (config) => {
22
- // Use the working JavaScript implementation
23
- return {
24
- command: "node",
25
- args: ["airtable_simple.js", "--token", config.airtable_token, "--base", config.base_id],
26
- env: {
27
- AIRTABLE_TOKEN: config.airtable_token,
28
- AIRTABLE_BASE_ID: config.base_id
29
- }
30
- };
31
- }
32
-
33
- listTools:
34
- command: "node"
35
- args: ["airtable_simple.js", "--list-tools"]
36
- env: {}
37
-
38
- build:
39
- dockerfile: "Dockerfile.node"
40
-
41
- metadata:
42
- author: "Rashid Azarang"
43
- license: "MIT"
44
- repository: "https://github.com/rashidazarang/airtable-mcp"
45
- homepage: "https://github.com/rashidazarang/airtable-mcp#readme"
@@ -1,146 +0,0 @@
1
- #!/bin/bash
2
-
3
- echo "🎯 COMPREHENSIVE TEST - AIRTABLE MCP v1.4.0"
4
- echo "==========================================="
5
- echo ""
6
-
7
- PASSED=0
8
- FAILED=0
9
- TOTAL=0
10
-
11
- # Test function
12
- test_feature() {
13
- local name=$1
14
- local result=$2
15
- ((TOTAL++))
16
-
17
- if [ "$result" = "PASS" ]; then
18
- echo "✅ $name"
19
- ((PASSED++))
20
- else
21
- echo "❌ $name"
22
- ((FAILED++))
23
- fi
24
- }
25
-
26
- echo "📊 TESTING ALL 12 TOOLS"
27
- echo "======================="
28
- echo ""
29
-
30
- # 1. List tables
31
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
32
- -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "list_tables"}}')
33
- if [[ "$result" == *"table"* ]]; then
34
- test_feature "list_tables" "PASS"
35
- else
36
- test_feature "list_tables" "FAIL"
37
- fi
38
-
39
- # 2. Create record
40
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
41
- -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "create_record", "arguments": {"table": "tblH7TnJxYpNqhQYK", "fields": {"Name": "Final Test", "Status": "Active"}}}}')
42
- if [[ "$result" == *"Successfully created"* ]]; then
43
- test_feature "create_record" "PASS"
44
- RECORD_ID=$(echo "$result" | grep -o 'rec[a-zA-Z0-9]\{10,20\}' | head -1)
45
- else
46
- test_feature "create_record" "FAIL"
47
- RECORD_ID=""
48
- fi
49
-
50
- # 3. Get record
51
- if [ ! -z "$RECORD_ID" ]; then
52
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
53
- -d "{\"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"tools/call\", \"params\": {\"name\": \"get_record\", \"arguments\": {\"table\": \"tblH7TnJxYpNqhQYK\", \"recordId\": \"$RECORD_ID\"}}}")
54
- [[ "$result" == *"Record $RECORD_ID"* ]] && test_feature "get_record" "PASS" || test_feature "get_record" "FAIL"
55
- else
56
- test_feature "get_record" "SKIP"
57
- fi
58
-
59
- # 4. Update record
60
- if [ ! -z "$RECORD_ID" ]; then
61
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
62
- -d "{\"jsonrpc\": \"2.0\", \"id\": 4, \"method\": \"tools/call\", \"params\": {\"name\": \"update_record\", \"arguments\": {\"table\": \"tblH7TnJxYpNqhQYK\", \"recordId\": \"$RECORD_ID\", \"fields\": {\"Status\": \"Completed\"}}}}")
63
- [[ "$result" == *"Successfully updated"* ]] && test_feature "update_record" "PASS" || test_feature "update_record" "FAIL"
64
- else
65
- test_feature "update_record" "SKIP"
66
- fi
67
-
68
- # 5. List records
69
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
70
- -d '{"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "list_records", "arguments": {"table": "tblH7TnJxYpNqhQYK", "maxRecords": 3}}}')
71
- [[ "$result" == *"record"* ]] && test_feature "list_records" "PASS" || test_feature "list_records" "FAIL"
72
-
73
- # 6. Search records
74
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
75
- -d '{"jsonrpc": "2.0", "id": 6, "method": "tools/call", "params": {"name": "search_records", "arguments": {"table": "tblH7TnJxYpNqhQYK", "maxRecords": 3}}}')
76
- [[ "$result" == *"record"* ]] && test_feature "search_records" "PASS" || test_feature "search_records" "FAIL"
77
-
78
- # 7. Delete record
79
- if [ ! -z "$RECORD_ID" ]; then
80
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
81
- -d "{\"jsonrpc\": \"2.0\", \"id\": 7, \"method\": \"tools/call\", \"params\": {\"name\": \"delete_record\", \"arguments\": {\"table\": \"tblH7TnJxYpNqhQYK\", \"recordId\": \"$RECORD_ID\"}}}")
82
- [[ "$result" == *"Successfully deleted"* ]] && test_feature "delete_record" "PASS" || test_feature "delete_record" "FAIL"
83
- else
84
- test_feature "delete_record" "SKIP"
85
- fi
86
-
87
- # 8. List webhooks
88
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
89
- -d '{"jsonrpc": "2.0", "id": 8, "method": "tools/call", "params": {"name": "list_webhooks"}}')
90
- [[ "$result" == *"webhook"* ]] && test_feature "list_webhooks" "PASS" || test_feature "list_webhooks" "FAIL"
91
-
92
- # 9. Create webhook
93
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
94
- -d '{"jsonrpc": "2.0", "id": 9, "method": "tools/call", "params": {"name": "create_webhook", "arguments": {"notificationUrl": "https://webhook.site/test-final"}}}')
95
- if [[ "$result" == *"Successfully created"* ]]; then
96
- test_feature "create_webhook" "PASS"
97
- WEBHOOK_ID=$(echo "$result" | grep -o 'ach[a-zA-Z0-9]*' | head -1)
98
- else
99
- test_feature "create_webhook" "FAIL"
100
- WEBHOOK_ID=""
101
- fi
102
-
103
- # 10. Get webhook payloads
104
- if [ ! -z "$WEBHOOK_ID" ]; then
105
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
106
- -d "{\"jsonrpc\": \"2.0\", \"id\": 10, \"method\": \"tools/call\", \"params\": {\"name\": \"get_webhook_payloads\", \"arguments\": {\"webhookId\": \"$WEBHOOK_ID\"}}}")
107
- [[ "$result" == *"payload"* ]] && test_feature "get_webhook_payloads" "PASS" || test_feature "get_webhook_payloads" "FAIL"
108
- else
109
- test_feature "get_webhook_payloads" "SKIP"
110
- fi
111
-
112
- # 11. Refresh webhook
113
- if [ ! -z "$WEBHOOK_ID" ]; then
114
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
115
- -d "{\"jsonrpc\": \"2.0\", \"id\": 11, \"method\": \"tools/call\", \"params\": {\"name\": \"refresh_webhook\", \"arguments\": {\"webhookId\": \"$WEBHOOK_ID\"}}}")
116
- [[ "$result" == *"refreshed"* ]] && test_feature "refresh_webhook" "PASS" || test_feature "refresh_webhook" "FAIL"
117
- else
118
- test_feature "refresh_webhook" "SKIP"
119
- fi
120
-
121
- # 12. Delete webhook
122
- if [ ! -z "$WEBHOOK_ID" ]; then
123
- result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
124
- -d "{\"jsonrpc\": \"2.0\", \"id\": 12, \"method\": \"tools/call\", \"params\": {\"name\": \"delete_webhook\", \"arguments\": {\"webhookId\": \"$WEBHOOK_ID\"}}}")
125
- [[ "$result" == *"deleted"* ]] && test_feature "delete_webhook" "PASS" || test_feature "delete_webhook" "FAIL"
126
- else
127
- test_feature "delete_webhook" "SKIP"
128
- fi
129
-
130
- echo ""
131
- echo "📈 FINAL RESULTS"
132
- echo "==============="
133
- echo "Total Tests: $TOTAL"
134
- echo "✅ Passed: $PASSED"
135
- echo "❌ Failed: $FAILED"
136
- echo "Success Rate: $(( PASSED * 100 / TOTAL ))%"
137
-
138
- if [ $FAILED -eq 0 ]; then
139
- echo ""
140
- echo "🎉 ALL TESTS PASSED! v1.4.0 is ready for production!"
141
- exit 0
142
- else
143
- echo ""
144
- echo "⚠️ $FAILED test(s) failed. Please review."
145
- exit 1
146
- fi
@@ -1,120 +0,0 @@
1
- #!/bin/bash
2
-
3
- echo "🧪 COMPREHENSIVE TEST OF AIRTABLE MCP v1.3.0"
4
- echo "============================================"
5
- echo ""
6
-
7
- # Configuration
8
- BASE_ID="appTV04Fyu1Gvbunq"
9
- TABLE_ID="tblH7TnJxYpNqhQYK"
10
- CREATED_RECORD_ID=""
11
-
12
- # Helper function for API calls
13
- call_mcp() {
14
- local tool_name=$1
15
- local args=$2
16
- curl -s -X POST http://localhost:8010/mcp \
17
- -H "Content-Type: application/json" \
18
- -d "{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/call\", \"params\": {\"name\": \"$tool_name\", \"arguments\": $args}}"
19
- }
20
-
21
- # Test 1: List tables
22
- echo "1️⃣ TEST: list_tables"
23
- echo "---------------------"
24
- result=$(call_mcp "list_tables" "{}")
25
- if echo "$result" | grep -q "Found.*table"; then
26
- echo "✅ PASSED: Tables listed successfully"
27
- echo "$result" | python3 -c "import sys, json; print(json.load(sys.stdin)['result']['content'][0]['text'][:200])"
28
- else
29
- echo "❌ FAILED: Could not list tables"
30
- fi
31
- echo ""
32
-
33
- # Test 2: Create record
34
- echo "2️⃣ TEST: create_record"
35
- echo "----------------------"
36
- result=$(call_mcp "create_record" "{\"table\": \"$TABLE_ID\", \"fields\": {\"Name\": \"Test Record $(date +%s)\", \"Notes\": \"Created by test suite\", \"Status\": \"Testing\"}}")
37
- if echo "$result" | grep -q "Successfully created"; then
38
- echo "✅ PASSED: Record created successfully"
39
- CREATED_RECORD_ID=$(echo "$result" | grep -o 'rec[a-zA-Z0-9]*' | head -1)
40
- echo " Created Record ID: $CREATED_RECORD_ID"
41
- else
42
- echo "❌ FAILED: Could not create record"
43
- echo "$result" | python3 -m json.tool
44
- fi
45
- echo ""
46
-
47
- # Test 3: Get record
48
- echo "3️⃣ TEST: get_record"
49
- echo "-------------------"
50
- if [ ! -z "$CREATED_RECORD_ID" ]; then
51
- result=$(call_mcp "get_record" "{\"table\": \"$TABLE_ID\", \"recordId\": \"$CREATED_RECORD_ID\"}")
52
- if echo "$result" | grep -q "Record $CREATED_RECORD_ID"; then
53
- echo "✅ PASSED: Record retrieved successfully"
54
- echo "$result" | python3 -c "import sys, json; print(json.load(sys.stdin)['result']['content'][0]['text'][:200])"
55
- else
56
- echo "❌ FAILED: Could not retrieve record"
57
- fi
58
- else
59
- echo "⚠️ SKIPPED: No record ID available"
60
- fi
61
- echo ""
62
-
63
- # Test 4: Update record
64
- echo "4️⃣ TEST: update_record"
65
- echo "----------------------"
66
- if [ ! -z "$CREATED_RECORD_ID" ]; then
67
- result=$(call_mcp "update_record" "{\"table\": \"$TABLE_ID\", \"recordId\": \"$CREATED_RECORD_ID\", \"fields\": {\"Status\": \"Updated\", \"Notes\": \"Updated at $(date)\"}}")
68
- if echo "$result" | grep -q "Successfully updated"; then
69
- echo "✅ PASSED: Record updated successfully"
70
- else
71
- echo "❌ FAILED: Could not update record"
72
- fi
73
- else
74
- echo "⚠️ SKIPPED: No record ID available"
75
- fi
76
- echo ""
77
-
78
- # Test 5: Search records
79
- echo "5️⃣ TEST: search_records"
80
- echo "-----------------------"
81
- result=$(call_mcp "search_records" "{\"table\": \"$TABLE_ID\", \"maxRecords\": 5}")
82
- if echo "$result" | grep -q "Found.*record\\|No records"; then
83
- echo "✅ PASSED: Search executed successfully"
84
- echo "$result" | python3 -c "import sys, json; t=json.load(sys.stdin)['result']['content'][0]['text']; print(t[:200] if len(t)>200 else t)"
85
- else
86
- echo "❌ FAILED: Search failed"
87
- fi
88
- echo ""
89
-
90
- # Test 6: List records
91
- echo "6️⃣ TEST: list_records"
92
- echo "---------------------"
93
- result=$(call_mcp "list_records" "{\"table\": \"$TABLE_ID\", \"maxRecords\": 3}")
94
- if echo "$result" | grep -q "record\\|No records"; then
95
- echo "✅ PASSED: Records listed successfully"
96
- count=$(echo "$result" | grep -o "ID: rec" | wc -l)
97
- echo " Found $count records"
98
- else
99
- echo "❌ FAILED: Could not list records"
100
- fi
101
- echo ""
102
-
103
- # Test 7: Delete record
104
- echo "7️⃣ TEST: delete_record"
105
- echo "----------------------"
106
- if [ ! -z "$CREATED_RECORD_ID" ]; then
107
- result=$(call_mcp "delete_record" "{\"table\": \"$TABLE_ID\", \"recordId\": \"$CREATED_RECORD_ID\"}")
108
- if echo "$result" | grep -q "Successfully deleted"; then
109
- echo "✅ PASSED: Record deleted successfully"
110
- else
111
- echo "❌ FAILED: Could not delete record"
112
- fi
113
- else
114
- echo "⚠️ SKIPPED: No record ID available"
115
- fi
116
- echo ""
117
-
118
- echo "📊 TEST SUMMARY"
119
- echo "=============="
120
- echo "All 7 tools tested with real Airtable API"
package/test_client.py DELETED
@@ -1,70 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Simple test client for Airtable MCP
4
- """
5
- import asyncio
6
- import json
7
- import os
8
- import sys
9
- import subprocess
10
- import time
11
- from typing import Dict, Any
12
-
13
- # Load credentials from environment variables
14
- TOKEN = os.environ.get('AIRTABLE_TOKEN', 'YOUR_AIRTABLE_TOKEN_HERE')
15
- BASE_ID = os.environ.get('AIRTABLE_BASE_ID', 'YOUR_BASE_ID_HERE')
16
-
17
- if TOKEN == 'YOUR_AIRTABLE_TOKEN_HERE' or BASE_ID == 'YOUR_BASE_ID_HERE':
18
- print("Error: Please set AIRTABLE_TOKEN and AIRTABLE_BASE_ID environment variables")
19
- print("Example: export AIRTABLE_TOKEN=your_token_here")
20
- print(" export AIRTABLE_BASE_ID=your_base_id_here")
21
- sys.exit(1)
22
-
23
- # Helper function to directly make Airtable API calls
24
- def api_call(endpoint, token=TOKEN):
25
- """Make a direct Airtable API call to test API access"""
26
- import requests
27
- headers = {
28
- "Authorization": f"Bearer {token}",
29
- "Content-Type": "application/json"
30
- }
31
- url = f"https://api.airtable.com/v0/{endpoint}"
32
-
33
- try:
34
- response = requests.get(url, headers=headers)
35
- response.raise_for_status()
36
- return response.json()
37
- except Exception as e:
38
- print(f"API error: {str(e)}")
39
- return {"error": str(e)}
40
-
41
- async def main():
42
- # Instead of using the MCP, let's directly test the Airtable API
43
- print("Testing direct API access...")
44
-
45
- # List bases
46
- print("\nListing bases:")
47
- result = api_call("meta/bases")
48
- if "error" in result:
49
- print(f"Error: {result['error']}")
50
- else:
51
- bases = result.get("bases", [])
52
- for i, base in enumerate(bases):
53
- print(f"{i+1}. {base['name']} (ID: {base['id']})")
54
-
55
- # List tables in the specified base
56
- print(f"\nListing tables in base {BASE_ID}:")
57
- result = api_call(f"meta/bases/{BASE_ID}/tables")
58
- if "error" in result:
59
- print(f"Error: {result['error']}")
60
- else:
61
- tables = result.get("tables", [])
62
- for i, table in enumerate(tables):
63
- print(f"{i+1}. {table['name']} (ID: {table['id']}, Fields: {len(table.get('fields', []))})")
64
- # Print fields
65
- print(" Fields:")
66
- for field in table.get('fields', []):
67
- print(f" - {field['name']} ({field['type']})")
68
-
69
- if __name__ == "__main__":
70
- asyncio.run(main())