@rashidazarang/airtable-mcp 1.2.4 → 1.4.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.
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Mock Airtable API Server for Testing
5
+ * Simulates Airtable API responses for testing the MCP
6
+ */
7
+
8
+ const http = require('http');
9
+ const https = require('https');
10
+
11
+ // Mock data store
12
+ let mockTables = {
13
+ 'tblTest123': {
14
+ id: 'tblTest123',
15
+ name: 'Tasks',
16
+ fields: [
17
+ { id: 'fld1', name: 'Name', type: 'singleLineText' },
18
+ { id: 'fld2', name: 'Status', type: 'singleSelect' },
19
+ { id: 'fld3', name: 'Notes', type: 'multilineText' }
20
+ ]
21
+ },
22
+ 'tblTest456': {
23
+ id: 'tblTest456',
24
+ name: 'Projects',
25
+ fields: [
26
+ { id: 'fld4', name: 'Title', type: 'singleLineText' },
27
+ { id: 'fld5', name: 'Description', type: 'multilineText' }
28
+ ]
29
+ }
30
+ };
31
+
32
+ let mockRecords = {
33
+ 'tblTest123': [
34
+ {
35
+ id: 'recABC123',
36
+ fields: { Name: 'Test Task 1', Status: 'Todo', Notes: 'First test task' },
37
+ createdTime: '2025-08-14T10:00:00.000Z'
38
+ },
39
+ {
40
+ id: 'recDEF456',
41
+ fields: { Name: 'Test Task 2', Status: 'In Progress', Notes: 'Second test task' },
42
+ createdTime: '2025-08-14T11:00:00.000Z'
43
+ }
44
+ ],
45
+ 'tblTest456': []
46
+ };
47
+
48
+ let recordIdCounter = 1000;
49
+
50
+ // Create mock server
51
+ const server = http.createServer((req, res) => {
52
+ console.log(`Mock API: ${req.method} ${req.url}`);
53
+
54
+ // Parse URL
55
+ const url = req.url;
56
+
57
+ // Enable CORS
58
+ res.setHeader('Access-Control-Allow-Origin', '*');
59
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE, OPTIONS');
60
+ res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type');
61
+
62
+ // Check authorization
63
+ if (!req.headers.authorization || !req.headers.authorization.includes('Bearer')) {
64
+ res.writeHead(401, { 'Content-Type': 'application/json' });
65
+ res.end(JSON.stringify({ error: { type: 'UNAUTHORIZED', message: 'Missing authorization' } }));
66
+ return;
67
+ }
68
+
69
+ // Handle preflight
70
+ if (req.method === 'OPTIONS') {
71
+ res.writeHead(200);
72
+ res.end();
73
+ return;
74
+ }
75
+
76
+ // Route handlers
77
+ if (url.includes('/v0/meta/bases/') && url.includes('/tables')) {
78
+ // List tables
79
+ res.writeHead(200, { 'Content-Type': 'application/json' });
80
+ res.end(JSON.stringify({ tables: Object.values(mockTables) }));
81
+
82
+ } else if (req.method === 'GET' && url.match(/\/v0\/test_base\/(tbl\w+)$/)) {
83
+ // List records
84
+ const tableId = url.match(/\/(tbl\w+)$/)[1];
85
+ const records = mockRecords[tableId] || [];
86
+
87
+ res.writeHead(200, { 'Content-Type': 'application/json' });
88
+ res.end(JSON.stringify({ records }));
89
+
90
+ } else if (req.method === 'GET' && url.match(/\/v0\/test_base\/(tbl\w+)\/(rec\w+)$/)) {
91
+ // Get single record
92
+ const matches = url.match(/\/(tbl\w+)\/(rec\w+)$/);
93
+ const tableId = matches[1];
94
+ const recordId = matches[2];
95
+
96
+ const records = mockRecords[tableId] || [];
97
+ const record = records.find(r => r.id === recordId);
98
+
99
+ if (record) {
100
+ res.writeHead(200, { 'Content-Type': 'application/json' });
101
+ res.end(JSON.stringify(record));
102
+ } else {
103
+ res.writeHead(404, { 'Content-Type': 'application/json' });
104
+ res.end(JSON.stringify({ error: { type: 'NOT_FOUND', message: 'Record not found' } }));
105
+ }
106
+
107
+ } else if (req.method === 'POST' && url.match(/\/v0\/test_base\/(tbl\w+)$/)) {
108
+ // Create record
109
+ const tableId = url.match(/\/(tbl\w+)$/)[1];
110
+
111
+ let body = '';
112
+ req.on('data', chunk => body += chunk);
113
+ req.on('end', () => {
114
+ const data = JSON.parse(body);
115
+ const newRecord = {
116
+ id: `recNEW${recordIdCounter++}`,
117
+ fields: data.fields,
118
+ createdTime: new Date().toISOString()
119
+ };
120
+
121
+ if (!mockRecords[tableId]) mockRecords[tableId] = [];
122
+ mockRecords[tableId].push(newRecord);
123
+
124
+ res.writeHead(200, { 'Content-Type': 'application/json' });
125
+ res.end(JSON.stringify(newRecord));
126
+ });
127
+
128
+ } else if (req.method === 'PATCH' && url.match(/\/v0\/test_base\/(tbl\w+)\/(rec\w+)$/)) {
129
+ // Update record
130
+ const matches = url.match(/\/(tbl\w+)\/(rec\w+)$/);
131
+ const tableId = matches[1];
132
+ const recordId = matches[2];
133
+
134
+ let body = '';
135
+ req.on('data', chunk => body += chunk);
136
+ req.on('end', () => {
137
+ const data = JSON.parse(body);
138
+ const records = mockRecords[tableId] || [];
139
+ const record = records.find(r => r.id === recordId);
140
+
141
+ if (record) {
142
+ Object.assign(record.fields, data.fields);
143
+ res.writeHead(200, { 'Content-Type': 'application/json' });
144
+ res.end(JSON.stringify(record));
145
+ } else {
146
+ res.writeHead(404, { 'Content-Type': 'application/json' });
147
+ res.end(JSON.stringify({ error: { type: 'NOT_FOUND', message: 'Record not found' } }));
148
+ }
149
+ });
150
+
151
+ } else if (req.method === 'DELETE' && url.match(/\/v0\/test_base\/(tbl\w+)\/(rec\w+)$/)) {
152
+ // Delete record
153
+ const matches = url.match(/\/(tbl\w+)\/(rec\w+)$/);
154
+ const tableId = matches[1];
155
+ const recordId = matches[2];
156
+
157
+ const records = mockRecords[tableId] || [];
158
+ const index = records.findIndex(r => r.id === recordId);
159
+
160
+ if (index !== -1) {
161
+ records.splice(index, 1);
162
+ res.writeHead(200, { 'Content-Type': 'application/json' });
163
+ res.end(JSON.stringify({ id: recordId, deleted: true }));
164
+ } else {
165
+ res.writeHead(404, { 'Content-Type': 'application/json' });
166
+ res.end(JSON.stringify({ error: { type: 'NOT_FOUND', message: 'Record not found' } }));
167
+ }
168
+
169
+ } else {
170
+ res.writeHead(404, { 'Content-Type': 'application/json' });
171
+ res.end(JSON.stringify({ error: { type: 'NOT_FOUND', message: 'Endpoint not found' } }));
172
+ }
173
+ });
174
+
175
+ // Start mock server on port 8888
176
+ server.listen(8888, () => {
177
+ console.log('🎭 Mock Airtable API server running on http://localhost:8888');
178
+ console.log('📋 Mock tables: Tasks (tblTest123), Projects (tblTest456)');
179
+ console.log('📝 Mock records: 2 in Tasks, 0 in Projects');
180
+ });
@@ -0,0 +1,131 @@
1
+ #!/bin/bash
2
+
3
+ echo "🚀 FINAL TEST SUITE FOR AIRTABLE MCP v1.4.0"
4
+ echo "==========================================="
5
+ echo ""
6
+
7
+ PASSED=0
8
+ FAILED=0
9
+
10
+ # Helper function to test a tool
11
+ test_tool() {
12
+ local tool_name=$1
13
+ local args=$2
14
+ local expected=$3
15
+
16
+ result=$(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
+ if echo "$result" | grep -q "$expected"; then
21
+ echo "✅ $tool_name: PASSED"
22
+ ((PASSED++))
23
+ return 0
24
+ else
25
+ echo "❌ $tool_name: FAILED"
26
+ echo " Response: $(echo "$result" | python3 -c "import sys, json; r=json.load(sys.stdin); print(r.get('result', {}).get('content', [{}])[0].get('text', 'ERROR')[:100])")"
27
+ ((FAILED++))
28
+ return 1
29
+ fi
30
+ }
31
+
32
+ echo "📋 Testing 12 Tools"
33
+ echo "==================="
34
+ echo ""
35
+
36
+ # 1. List tools
37
+ echo -n "1. Tools available: "
38
+ tools_count=$(curl -s -X POST http://localhost:8010/mcp \
39
+ -H "Content-Type: application/json" \
40
+ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}' \
41
+ | python3 -c "import sys, json; print(len(json.load(sys.stdin)['result']['tools']))")
42
+ echo "$tools_count tools"
43
+
44
+ # 2. List tables
45
+ test_tool "list_tables" "{}" "table"
46
+
47
+ # 3. Create record
48
+ RECORD_JSON=$(curl -s -X POST http://localhost:8010/mcp \
49
+ -H "Content-Type: application/json" \
50
+ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "create_record", "arguments": {"table": "tblH7TnJxYpNqhQYK", "fields": {"Name": "Test v1.4.0", "Status": "Testing"}}}}')
51
+
52
+ if echo "$RECORD_JSON" | grep -q "Successfully created"; then
53
+ echo "✅ create_record: PASSED"
54
+ ((PASSED++))
55
+ RECORD_ID=$(echo "$RECORD_JSON" | grep -o 'rec[a-zA-Z0-9]\+' | head -1)
56
+ else
57
+ echo "❌ create_record: FAILED"
58
+ ((FAILED++))
59
+ RECORD_ID=""
60
+ fi
61
+
62
+ # 4. Get record
63
+ if [ ! -z "$RECORD_ID" ]; then
64
+ test_tool "get_record" "{\"table\": \"tblH7TnJxYpNqhQYK\", \"recordId\": \"$RECORD_ID\"}" "Record $RECORD_ID"
65
+ fi
66
+
67
+ # 5. Update record
68
+ if [ ! -z "$RECORD_ID" ]; then
69
+ test_tool "update_record" "{\"table\": \"tblH7TnJxYpNqhQYK\", \"recordId\": \"$RECORD_ID\", \"fields\": {\"Status\": \"Updated\"}}" "Successfully updated"
70
+ fi
71
+
72
+ # 6. List records
73
+ test_tool "list_records" "{\"table\": \"tblH7TnJxYpNqhQYK\", \"maxRecords\": 2}" "record"
74
+
75
+ # 7. Search records
76
+ test_tool "search_records" "{\"table\": \"tblH7TnJxYpNqhQYK\", \"maxRecords\": 2}" "record"
77
+
78
+ # 8. List webhooks
79
+ test_tool "list_webhooks" "{}" "webhook"
80
+
81
+ # 9. Create webhook
82
+ WEBHOOK_JSON=$(curl -s -X POST http://localhost:8010/mcp \
83
+ -H "Content-Type: application/json" \
84
+ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "create_webhook", "arguments": {"notificationUrl": "https://webhook.site/unique-test-id"}}}')
85
+
86
+ if echo "$WEBHOOK_JSON" | grep -q "Successfully created"; then
87
+ echo "✅ create_webhook: PASSED"
88
+ ((PASSED++))
89
+ WEBHOOK_ID=$(echo "$WEBHOOK_JSON" | grep -o 'ach[a-zA-Z0-9]\+' | head -1)
90
+ else
91
+ echo "❌ create_webhook: FAILED"
92
+ ((FAILED++))
93
+ WEBHOOK_ID=""
94
+ fi
95
+
96
+ # 10. Get webhook payloads
97
+ if [ ! -z "$WEBHOOK_ID" ]; then
98
+ test_tool "get_webhook_payloads" "{\"webhookId\": \"$WEBHOOK_ID\"}" "payload"
99
+ fi
100
+
101
+ # 11. Refresh webhook
102
+ if [ ! -z "$WEBHOOK_ID" ]; then
103
+ test_tool "refresh_webhook" "{\"webhookId\": \"$WEBHOOK_ID\"}" "refreshed"
104
+ fi
105
+
106
+ # 12. Delete webhook
107
+ if [ ! -z "$WEBHOOK_ID" ]; then
108
+ test_tool "delete_webhook" "{\"webhookId\": \"$WEBHOOK_ID\"}" "deleted"
109
+ fi
110
+
111
+ # 13. Delete record (cleanup)
112
+ if [ ! -z "$RECORD_ID" ]; then
113
+ test_tool "delete_record" "{\"table\": \"tblH7TnJxYpNqhQYK\", \"recordId\": \"$RECORD_ID\"}" "Successfully deleted"
114
+ fi
115
+
116
+ echo ""
117
+ echo "📊 TEST RESULTS"
118
+ echo "=============="
119
+ echo "✅ Passed: $PASSED"
120
+ echo "❌ Failed: $FAILED"
121
+ echo "📈 Success Rate: $(( PASSED * 100 / (PASSED + FAILED) ))%"
122
+
123
+ if [ $FAILED -eq 0 ]; then
124
+ echo ""
125
+ echo "🎉 ALL TESTS PASSED! Ready for production!"
126
+ exit 0
127
+ else
128
+ echo ""
129
+ echo "⚠️ Some tests failed. Please review before publishing."
130
+ exit 1
131
+ fi
@@ -0,0 +1,105 @@
1
+ #!/bin/bash
2
+
3
+ echo "🪝 TESTING WEBHOOK FUNCTIONALITY"
4
+ echo "================================"
5
+ echo ""
6
+
7
+ # Test 1: List webhooks
8
+ echo "1️⃣ LIST WEBHOOKS"
9
+ echo "-----------------"
10
+ result=$(curl -s -X POST http://localhost:8010/mcp \
11
+ -H "Content-Type: application/json" \
12
+ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "list_webhooks"}}')
13
+
14
+ if echo "$result" | grep -q "webhook\\|No webhooks"; then
15
+ echo "✅ PASSED: list_webhooks working"
16
+ echo "$result" | python3 -c "import sys, json; print(json.load(sys.stdin)['result']['content'][0]['text'])"
17
+ else
18
+ echo "❌ FAILED: list_webhooks error"
19
+ echo "$result" | python3 -m json.tool
20
+ fi
21
+ echo ""
22
+
23
+ # Test 2: Create webhook
24
+ echo "2️⃣ CREATE WEBHOOK"
25
+ echo "------------------"
26
+ result=$(curl -s -X POST http://localhost:8010/mcp \
27
+ -H "Content-Type: application/json" \
28
+ -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "create_webhook", "arguments": {"notificationUrl": "https://webhook.site/test-webhook"}}}')
29
+
30
+ if echo "$result" | grep -q "Successfully created webhook"; then
31
+ echo "✅ PASSED: create_webhook working"
32
+ # Extract webhook ID
33
+ WEBHOOK_ID=$(echo "$result" | grep -o 'ach[a-zA-Z0-9]*' | head -1)
34
+ echo "Created webhook ID: $WEBHOOK_ID"
35
+ echo "$result" | python3 -c "import sys, json; t=json.load(sys.stdin)['result']['content'][0]['text']; print(t[:300])"
36
+ else
37
+ echo "❌ FAILED: create_webhook error"
38
+ echo "$result" | python3 -m json.tool | head -20
39
+ fi
40
+ echo ""
41
+
42
+ # Test 3: List webhooks again
43
+ echo "3️⃣ VERIFY WEBHOOK CREATED"
44
+ echo "--------------------------"
45
+ result=$(curl -s -X POST http://localhost:8010/mcp \
46
+ -H "Content-Type: application/json" \
47
+ -d '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "list_webhooks"}}')
48
+
49
+ if echo "$result" | grep -q "Found.*webhook"; then
50
+ echo "✅ PASSED: Webhook appears in list"
51
+ count=$(echo "$result" | grep -o "ID: ach" | wc -l)
52
+ echo "Total webhooks: $count"
53
+ else
54
+ echo "⚠️ WARNING: No webhooks found"
55
+ fi
56
+ echo ""
57
+
58
+ # Test 4: Get webhook payloads (if webhook exists)
59
+ if [ ! -z "$WEBHOOK_ID" ]; then
60
+ echo "4️⃣ GET WEBHOOK PAYLOADS"
61
+ echo "------------------------"
62
+ result=$(curl -s -X POST http://localhost:8010/mcp \
63
+ -H "Content-Type: application/json" \
64
+ -d "{\"jsonrpc\": \"2.0\", \"id\": 4, \"method\": \"tools/call\", \"params\": {\"name\": \"get_webhook_payloads\", \"arguments\": {\"webhookId\": \"$WEBHOOK_ID\"}}}")
65
+
66
+ if echo "$result" | grep -q "payload\\|No payloads"; then
67
+ echo "✅ PASSED: get_webhook_payloads working"
68
+ else
69
+ echo "❌ FAILED: get_webhook_payloads error"
70
+ fi
71
+
72
+ # Test 5: Refresh webhook
73
+ echo ""
74
+ echo "5️⃣ REFRESH WEBHOOK"
75
+ echo "-------------------"
76
+ result=$(curl -s -X POST http://localhost:8010/mcp \
77
+ -H "Content-Type: application/json" \
78
+ -d "{\"jsonrpc\": \"2.0\", \"id\": 5, \"method\": \"tools/call\", \"params\": {\"name\": \"refresh_webhook\", \"arguments\": {\"webhookId\": \"$WEBHOOK_ID\"}}}")
79
+
80
+ if echo "$result" | grep -q "Successfully refreshed"; then
81
+ echo "✅ PASSED: refresh_webhook working"
82
+ else
83
+ echo "❌ FAILED: refresh_webhook error"
84
+ fi
85
+
86
+ # Test 6: Delete webhook
87
+ echo ""
88
+ echo "6️⃣ DELETE WEBHOOK"
89
+ echo "------------------"
90
+ result=$(curl -s -X POST http://localhost:8010/mcp \
91
+ -H "Content-Type: application/json" \
92
+ -d "{\"jsonrpc\": \"2.0\", \"id\": 6, \"method\": \"tools/call\", \"params\": {\"name\": \"delete_webhook\", \"arguments\": {\"webhookId\": \"$WEBHOOK_ID\"}}}")
93
+
94
+ if echo "$result" | grep -q "Successfully deleted"; then
95
+ echo "✅ PASSED: delete_webhook working"
96
+ else
97
+ echo "❌ FAILED: delete_webhook error"
98
+ echo "$result" | python3 -m json.tool | head -20
99
+ fi
100
+ fi
101
+
102
+ echo ""
103
+ echo "📊 WEBHOOK TEST SUMMARY"
104
+ echo "======================"
105
+ echo "Webhook operations tested with Airtable API"