@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.
- package/package.json +10 -1
- package/.github/ISSUE_TEMPLATE/bug-report.yml +0 -173
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
- package/.github/ISSUE_TEMPLATE/custom.md +0 -10
- package/.github/ISSUE_TEMPLATE/feature-request.yml +0 -209
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.github/ISSUE_TEMPLATE/security-report.yml +0 -216
- package/.github/pull_request_template.md +0 -245
- package/.github/workflows/ci-cd.yml +0 -408
- package/.github/workflows/security-audit.yml +0 -316
- package/API_DOCUMENTATION.md +0 -897
- package/CAPABILITY_REPORT.md +0 -118
- package/CLAUDE_INTEGRATION.md +0 -96
- package/CODE_OF_CONDUCT.md +0 -181
- package/CONTRIBUTING.md +0 -81
- package/DEVELOPMENT.md +0 -190
- package/Dockerfile +0 -39
- package/Dockerfile.node +0 -20
- package/Dockerfile.production +0 -127
- package/IMPROVEMENT_PROPOSAL.md +0 -371
- package/INSTALLATION.md +0 -183
- package/ISSUE_RESPONSES.md +0 -171
- package/MCP_REVIEW_SUMMARY.md +0 -142
- package/QUICK_START.md +0 -60
- package/RELEASE_NOTES_v1.2.0.md +0 -50
- package/RELEASE_NOTES_v1.2.1.md +0 -40
- package/RELEASE_NOTES_v1.2.2.md +0 -48
- package/RELEASE_NOTES_v1.2.3.md +0 -105
- package/RELEASE_NOTES_v1.2.4.md +0 -60
- package/RELEASE_NOTES_v1.4.0.md +0 -104
- package/RELEASE_NOTES_v1.5.0.md +0 -185
- package/RELEASE_NOTES_v1.6.0.md +0 -248
- package/SECURITY_NOTICE.md +0 -40
- package/airtable-clipper/CHANGELOG.md +0 -198
- package/airtable-clipper/CHROME_STORE_SUBMISSION.md +0 -343
- package/airtable-clipper/LAUNCH_STRATEGY.md +0 -495
- package/airtable-clipper/LICENSE +0 -21
- package/airtable-clipper/OAUTH_SETUP.md +0 -51
- package/airtable-clipper/PRIVACY_POLICY.md +0 -187
- package/airtable-clipper/README.md +0 -575
- package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +0 -273
- package/airtable-clipper/build.sh +0 -85
- package/airtable-clipper/docs/QUICK_START.md +0 -99
- package/airtable-clipper/docs/SETUP.md +0 -291
- package/airtable-clipper/extension/background.js +0 -337
- package/airtable-clipper/extension/base-setup.html +0 -324
- package/airtable-clipper/extension/base-setup.js +0 -471
- package/airtable-clipper/extension/content.js +0 -771
- package/airtable-clipper/extension/icons/README.md +0 -69
- package/airtable-clipper/extension/icons/icon-16.png +0 -3
- package/airtable-clipper/extension/manifest.json +0 -73
- package/airtable-clipper/extension/popup.html +0 -144
- package/airtable-clipper/extension/popup.js +0 -475
- package/airtable-clipper/extension/styles/content.css +0 -229
- package/airtable-clipper/extension/styles/popup.css +0 -477
- package/airtable-clipper/privacy-policy.md +0 -63
- package/airtable-clipper/releases/v1.0.0/background.js +0 -337
- package/airtable-clipper/releases/v1.0.0/base-setup.html +0 -324
- package/airtable-clipper/releases/v1.0.0/base-setup.js +0 -471
- package/airtable-clipper/releases/v1.0.0/content.js +0 -771
- package/airtable-clipper/releases/v1.0.0/icons/README.md +0 -69
- package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +0 -2
- package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +0 -3
- package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +0 -2
- package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +0 -2
- package/airtable-clipper/releases/v1.0.0/manifest.json +0 -73
- package/airtable-clipper/releases/v1.0.0/popup.html +0 -144
- package/airtable-clipper/releases/v1.0.0/popup.js +0 -475
- package/airtable-clipper/releases/v1.0.0/sidepanel.html +0 -25
- package/airtable-clipper/releases/v1.0.0/styles/content.css +0 -229
- package/airtable-clipper/releases/v1.0.0/styles/popup.css +0 -477
- package/airtable-clipper/releases/v1.0.1/background.js +0 -337
- package/airtable-clipper/releases/v1.0.1/base-setup.html +0 -324
- package/airtable-clipper/releases/v1.0.1/base-setup.js +0 -471
- package/airtable-clipper/releases/v1.0.1/content.js +0 -771
- package/airtable-clipper/releases/v1.0.1/icons/README.md +0 -69
- package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +0 -2
- package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +0 -3
- package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +0 -2
- package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +0 -2
- package/airtable-clipper/releases/v1.0.1/manifest.json +0 -70
- package/airtable-clipper/releases/v1.0.1/popup.html +0 -157
- package/airtable-clipper/releases/v1.0.1/popup.js +0 -562
- package/airtable-clipper/releases/v1.0.1/sidepanel.html +0 -25
- package/airtable-clipper/releases/v1.0.1/styles/content.css +0 -229
- package/airtable-clipper/releases/v1.0.1/styles/popup.css +0 -647
- package/airtable-clipper/releases/v1.0.2/background.js +0 -337
- package/airtable-clipper/releases/v1.0.2/base-setup.html +0 -324
- package/airtable-clipper/releases/v1.0.2/base-setup.js +0 -471
- package/airtable-clipper/releases/v1.0.2/content.js +0 -771
- package/airtable-clipper/releases/v1.0.2/icons/README.md +0 -69
- package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +0 -2
- package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +0 -3
- package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +0 -2
- package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +0 -2
- package/airtable-clipper/releases/v1.0.2/manifest.json +0 -62
- package/airtable-clipper/releases/v1.0.2/popup.html +0 -157
- package/airtable-clipper/releases/v1.0.2/popup.js +0 -567
- package/airtable-clipper/releases/v1.0.2/sidepanel.html +0 -25
- package/airtable-clipper/releases/v1.0.2/styles/content.css +0 -229
- package/airtable-clipper/releases/v1.0.2/styles/popup.css +0 -647
- package/airtable-clipper/terms-of-service.md +0 -124
- package/airtable-clipper/test-credentials.md +0 -61
- package/airtable-clipper/test-extension/background.js +0 -337
- package/airtable-clipper/test-extension/base-setup.html +0 -324
- package/airtable-clipper/test-extension/base-setup.js +0 -471
- package/airtable-clipper/test-extension/content.js +0 -873
- package/airtable-clipper/test-extension/icons/README.md +0 -69
- package/airtable-clipper/test-extension/icons/icon-128.png +0 -2
- package/airtable-clipper/test-extension/icons/icon-16.png +0 -3
- package/airtable-clipper/test-extension/icons/icon-32.png +0 -2
- package/airtable-clipper/test-extension/icons/icon-48.png +0 -2
- package/airtable-clipper/test-extension/manifest.json +0 -72
- package/airtable-clipper/test-extension/popup.html +0 -274
- package/airtable-clipper/test-extension/popup.js +0 -729
- package/airtable-clipper/test-extension/sidepanel.html +0 -25
- package/airtable-clipper/test-extension/styles/content.css +0 -229
- package/airtable-clipper/test-extension/styles/popup.css +0 -794
- package/airtable_mcp/__init__.py +0 -5
- package/airtable_mcp/src/server.py +0 -329
- package/airtable_mcp_v2.js +0 -1505
- package/airtable_mcp_v2_oauth.js +0 -1048
- package/airtable_mcp_v3_advanced.js +0 -1161
- package/cleanup.sh +0 -71
- package/docker-compose.production.yml +0 -366
- package/helm/airtable-mcp/Chart.yaml +0 -122
- package/helm/airtable-mcp/values.yaml +0 -538
- package/index.js +0 -179
- package/inspector.py +0 -148
- package/inspector_server.py +0 -337
- package/k8s/deployment.yaml +0 -402
- package/k8s/namespace.yaml +0 -108
- package/k8s/service.yaml +0 -194
- package/monitoring/alerts.yml +0 -289
- package/monitoring/prometheus.yml +0 -224
- package/publish-steps.txt +0 -27
- package/quick_test.sh +0 -30
- package/requirements.txt +0 -10
- package/setup.py +0 -29
- package/simple_airtable_server.py +0 -151
- package/smithery.yaml +0 -45
- package/test_all_features.sh +0 -146
- package/test_all_operations.sh +0 -120
- package/test_client.py +0 -70
- package/test_enhanced_features.js +0 -389
- package/test_mcp_comprehensive.js +0 -163
- package/test_mock_server.js +0 -180
- package/test_v1.4.0_final.sh +0 -131
- package/test_v1.5.0_comprehensive.sh +0 -96
- package/test_v1.5.0_final.sh +0 -224
- package/test_v1.6.0_comprehensive.sh +0 -187
- 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"
|
package/test_all_features.sh
DELETED
|
@@ -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
|
package/test_all_operations.sh
DELETED
|
@@ -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())
|