@rashidazarang/airtable-mcp 2.1.0 → 2.1.1

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 (152) hide show
  1. package/package.json +10 -1
  2. package/.github/ISSUE_TEMPLATE/bug-report.yml +0 -173
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  4. package/.github/ISSUE_TEMPLATE/custom.md +0 -10
  5. package/.github/ISSUE_TEMPLATE/feature-request.yml +0 -209
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  7. package/.github/ISSUE_TEMPLATE/security-report.yml +0 -216
  8. package/.github/pull_request_template.md +0 -245
  9. package/.github/workflows/ci-cd.yml +0 -408
  10. package/.github/workflows/security-audit.yml +0 -316
  11. package/API_DOCUMENTATION.md +0 -897
  12. package/CAPABILITY_REPORT.md +0 -118
  13. package/CLAUDE_INTEGRATION.md +0 -96
  14. package/CODE_OF_CONDUCT.md +0 -181
  15. package/CONTRIBUTING.md +0 -81
  16. package/DEVELOPMENT.md +0 -190
  17. package/Dockerfile +0 -39
  18. package/Dockerfile.node +0 -20
  19. package/Dockerfile.production +0 -127
  20. package/IMPROVEMENT_PROPOSAL.md +0 -371
  21. package/INSTALLATION.md +0 -183
  22. package/ISSUE_RESPONSES.md +0 -171
  23. package/MCP_REVIEW_SUMMARY.md +0 -142
  24. package/QUICK_START.md +0 -60
  25. package/RELEASE_NOTES_v1.2.0.md +0 -50
  26. package/RELEASE_NOTES_v1.2.1.md +0 -40
  27. package/RELEASE_NOTES_v1.2.2.md +0 -48
  28. package/RELEASE_NOTES_v1.2.3.md +0 -105
  29. package/RELEASE_NOTES_v1.2.4.md +0 -60
  30. package/RELEASE_NOTES_v1.4.0.md +0 -104
  31. package/RELEASE_NOTES_v1.5.0.md +0 -185
  32. package/RELEASE_NOTES_v1.6.0.md +0 -248
  33. package/SECURITY_NOTICE.md +0 -40
  34. package/airtable-clipper/CHANGELOG.md +0 -198
  35. package/airtable-clipper/CHROME_STORE_SUBMISSION.md +0 -343
  36. package/airtable-clipper/LAUNCH_STRATEGY.md +0 -495
  37. package/airtable-clipper/LICENSE +0 -21
  38. package/airtable-clipper/OAUTH_SETUP.md +0 -51
  39. package/airtable-clipper/PRIVACY_POLICY.md +0 -187
  40. package/airtable-clipper/README.md +0 -575
  41. package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +0 -273
  42. package/airtable-clipper/build.sh +0 -85
  43. package/airtable-clipper/docs/QUICK_START.md +0 -99
  44. package/airtable-clipper/docs/SETUP.md +0 -291
  45. package/airtable-clipper/extension/background.js +0 -337
  46. package/airtable-clipper/extension/base-setup.html +0 -324
  47. package/airtable-clipper/extension/base-setup.js +0 -471
  48. package/airtable-clipper/extension/content.js +0 -771
  49. package/airtable-clipper/extension/icons/README.md +0 -69
  50. package/airtable-clipper/extension/icons/icon-16.png +0 -3
  51. package/airtable-clipper/extension/manifest.json +0 -73
  52. package/airtable-clipper/extension/popup.html +0 -144
  53. package/airtable-clipper/extension/popup.js +0 -475
  54. package/airtable-clipper/extension/styles/content.css +0 -229
  55. package/airtable-clipper/extension/styles/popup.css +0 -477
  56. package/airtable-clipper/privacy-policy.md +0 -63
  57. package/airtable-clipper/releases/v1.0.0/background.js +0 -337
  58. package/airtable-clipper/releases/v1.0.0/base-setup.html +0 -324
  59. package/airtable-clipper/releases/v1.0.0/base-setup.js +0 -471
  60. package/airtable-clipper/releases/v1.0.0/content.js +0 -771
  61. package/airtable-clipper/releases/v1.0.0/icons/README.md +0 -69
  62. package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +0 -2
  63. package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +0 -3
  64. package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +0 -2
  65. package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +0 -2
  66. package/airtable-clipper/releases/v1.0.0/manifest.json +0 -73
  67. package/airtable-clipper/releases/v1.0.0/popup.html +0 -144
  68. package/airtable-clipper/releases/v1.0.0/popup.js +0 -475
  69. package/airtable-clipper/releases/v1.0.0/sidepanel.html +0 -25
  70. package/airtable-clipper/releases/v1.0.0/styles/content.css +0 -229
  71. package/airtable-clipper/releases/v1.0.0/styles/popup.css +0 -477
  72. package/airtable-clipper/releases/v1.0.1/background.js +0 -337
  73. package/airtable-clipper/releases/v1.0.1/base-setup.html +0 -324
  74. package/airtable-clipper/releases/v1.0.1/base-setup.js +0 -471
  75. package/airtable-clipper/releases/v1.0.1/content.js +0 -771
  76. package/airtable-clipper/releases/v1.0.1/icons/README.md +0 -69
  77. package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +0 -2
  78. package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +0 -3
  79. package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +0 -2
  80. package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +0 -2
  81. package/airtable-clipper/releases/v1.0.1/manifest.json +0 -70
  82. package/airtable-clipper/releases/v1.0.1/popup.html +0 -157
  83. package/airtable-clipper/releases/v1.0.1/popup.js +0 -562
  84. package/airtable-clipper/releases/v1.0.1/sidepanel.html +0 -25
  85. package/airtable-clipper/releases/v1.0.1/styles/content.css +0 -229
  86. package/airtable-clipper/releases/v1.0.1/styles/popup.css +0 -647
  87. package/airtable-clipper/releases/v1.0.2/background.js +0 -337
  88. package/airtable-clipper/releases/v1.0.2/base-setup.html +0 -324
  89. package/airtable-clipper/releases/v1.0.2/base-setup.js +0 -471
  90. package/airtable-clipper/releases/v1.0.2/content.js +0 -771
  91. package/airtable-clipper/releases/v1.0.2/icons/README.md +0 -69
  92. package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +0 -2
  93. package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +0 -3
  94. package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +0 -2
  95. package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +0 -2
  96. package/airtable-clipper/releases/v1.0.2/manifest.json +0 -62
  97. package/airtable-clipper/releases/v1.0.2/popup.html +0 -157
  98. package/airtable-clipper/releases/v1.0.2/popup.js +0 -567
  99. package/airtable-clipper/releases/v1.0.2/sidepanel.html +0 -25
  100. package/airtable-clipper/releases/v1.0.2/styles/content.css +0 -229
  101. package/airtable-clipper/releases/v1.0.2/styles/popup.css +0 -647
  102. package/airtable-clipper/terms-of-service.md +0 -124
  103. package/airtable-clipper/test-credentials.md +0 -61
  104. package/airtable-clipper/test-extension/background.js +0 -337
  105. package/airtable-clipper/test-extension/base-setup.html +0 -324
  106. package/airtable-clipper/test-extension/base-setup.js +0 -471
  107. package/airtable-clipper/test-extension/content.js +0 -873
  108. package/airtable-clipper/test-extension/icons/README.md +0 -69
  109. package/airtable-clipper/test-extension/icons/icon-128.png +0 -2
  110. package/airtable-clipper/test-extension/icons/icon-16.png +0 -3
  111. package/airtable-clipper/test-extension/icons/icon-32.png +0 -2
  112. package/airtable-clipper/test-extension/icons/icon-48.png +0 -2
  113. package/airtable-clipper/test-extension/manifest.json +0 -72
  114. package/airtable-clipper/test-extension/popup.html +0 -274
  115. package/airtable-clipper/test-extension/popup.js +0 -729
  116. package/airtable-clipper/test-extension/sidepanel.html +0 -25
  117. package/airtable-clipper/test-extension/styles/content.css +0 -229
  118. package/airtable-clipper/test-extension/styles/popup.css +0 -794
  119. package/airtable_mcp/__init__.py +0 -5
  120. package/airtable_mcp/src/server.py +0 -329
  121. package/airtable_mcp_v2.js +0 -1505
  122. package/airtable_mcp_v2_oauth.js +0 -1048
  123. package/airtable_mcp_v3_advanced.js +0 -1161
  124. package/cleanup.sh +0 -71
  125. package/docker-compose.production.yml +0 -366
  126. package/helm/airtable-mcp/Chart.yaml +0 -122
  127. package/helm/airtable-mcp/values.yaml +0 -538
  128. package/index.js +0 -179
  129. package/inspector.py +0 -148
  130. package/inspector_server.py +0 -337
  131. package/k8s/deployment.yaml +0 -402
  132. package/k8s/namespace.yaml +0 -108
  133. package/k8s/service.yaml +0 -194
  134. package/monitoring/alerts.yml +0 -289
  135. package/monitoring/prometheus.yml +0 -224
  136. package/publish-steps.txt +0 -27
  137. package/quick_test.sh +0 -30
  138. package/requirements.txt +0 -10
  139. package/setup.py +0 -29
  140. package/simple_airtable_server.py +0 -151
  141. package/smithery.yaml +0 -45
  142. package/test_all_features.sh +0 -146
  143. package/test_all_operations.sh +0 -120
  144. package/test_client.py +0 -70
  145. package/test_enhanced_features.js +0 -389
  146. package/test_mcp_comprehensive.js +0 -163
  147. package/test_mock_server.js +0 -180
  148. package/test_v1.4.0_final.sh +0 -131
  149. package/test_v1.5.0_comprehensive.sh +0 -96
  150. package/test_v1.5.0_final.sh +0 -224
  151. package/test_v1.6.0_comprehensive.sh +0 -187
  152. 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())