@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.
- package/README.md +1 -1
- package/airtable_simple_production.js +387 -5
- package/examples/claude_simple_config.json +0 -9
- 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
package/test_mock_server.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
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
|
-
});
|
package/test_v1.4.0_final.sh
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
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
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Comprehensive Test Suite for Airtable MCP Server v1.5.0
|
|
4
|
-
# Tests all 23 tools including new schema management features
|
|
5
|
-
|
|
6
|
-
set -e
|
|
7
|
-
SERVER_URL="http://localhost:8010/mcp"
|
|
8
|
-
PASSED=0
|
|
9
|
-
FAILED=0
|
|
10
|
-
|
|
11
|
-
echo "๐ Airtable MCP Server v1.5.0 Comprehensive Test Suite"
|
|
12
|
-
echo "======================================================"
|
|
13
|
-
|
|
14
|
-
# Function to make MCP calls
|
|
15
|
-
call_tool() {
|
|
16
|
-
local tool_name="$1"
|
|
17
|
-
local params="$2"
|
|
18
|
-
curl -s -X POST "$SERVER_URL" \
|
|
19
|
-
-H "Content-Type: application/json" \
|
|
20
|
-
-d "{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/call\", \"params\": {\"name\": \"$tool_name\", \"arguments\": $params}}"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
# Test function with result reporting
|
|
24
|
-
test_tool() {
|
|
25
|
-
local tool_name="$1"
|
|
26
|
-
local params="$2"
|
|
27
|
-
local description="$3"
|
|
28
|
-
|
|
29
|
-
echo -n "Testing $tool_name ($description)... "
|
|
30
|
-
|
|
31
|
-
if result=$(call_tool "$tool_name" "$params" 2>&1); then
|
|
32
|
-
if echo "$result" | jq -e '.result.content[0].text' > /dev/null 2>&1; then
|
|
33
|
-
echo "โ
PASS"
|
|
34
|
-
((PASSED++))
|
|
35
|
-
else
|
|
36
|
-
echo "โ FAIL (No content)"
|
|
37
|
-
echo "Response: $result"
|
|
38
|
-
((FAILED++))
|
|
39
|
-
fi
|
|
40
|
-
else
|
|
41
|
-
echo "โ FAIL (Request failed)"
|
|
42
|
-
echo "Error: $result"
|
|
43
|
-
((FAILED++))
|
|
44
|
-
fi
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
echo ""
|
|
48
|
-
echo "๐ Testing Original Data Operations (7 tools)..."
|
|
49
|
-
echo "------------------------------------------------"
|
|
50
|
-
|
|
51
|
-
test_tool "list_tables" "{}" "List all tables"
|
|
52
|
-
test_tool "list_records" "{\"table\": \"Test Table CRUD\", \"maxRecords\": 3}" "List records from test table"
|
|
53
|
-
test_tool "get_record" "{\"table\": \"Test Table CRUD\", \"recordId\": \"recXXX\"}" "Get specific record (may fail if record doesn't exist)"
|
|
54
|
-
test_tool "search_records" "{\"table\": \"Test Table CRUD\", \"searchTerm\": \"test\"}" "Search records"
|
|
55
|
-
|
|
56
|
-
echo ""
|
|
57
|
-
echo "๐ง Testing New Schema Management Tools (6 tools)..."
|
|
58
|
-
echo "---------------------------------------------------"
|
|
59
|
-
|
|
60
|
-
test_tool "list_bases" "{}" "List accessible bases"
|
|
61
|
-
test_tool "get_base_schema" "{}" "Get complete base schema"
|
|
62
|
-
test_tool "describe_table" "{\"table\": \"Test Table CRUD\"}" "Describe table with detailed field info"
|
|
63
|
-
test_tool "list_field_types" "{}" "List available field types reference"
|
|
64
|
-
test_tool "get_table_views" "{\"table\": \"Test Table CRUD\"}" "Get table views"
|
|
65
|
-
|
|
66
|
-
echo ""
|
|
67
|
-
echo "๐๏ธ Testing Webhook Management Tools (5 tools)..."
|
|
68
|
-
echo "-------------------------------------------------"
|
|
69
|
-
|
|
70
|
-
test_tool "list_webhooks" "{}" "List webhooks"
|
|
71
|
-
|
|
72
|
-
echo ""
|
|
73
|
-
echo "๐ Testing Results Summary"
|
|
74
|
-
echo "=========================="
|
|
75
|
-
echo "โ
Passed: $PASSED"
|
|
76
|
-
echo "โ Failed: $FAILED"
|
|
77
|
-
echo "Total Tests: $((PASSED + FAILED))"
|
|
78
|
-
|
|
79
|
-
if [ $FAILED -eq 0 ]; then
|
|
80
|
-
echo ""
|
|
81
|
-
echo "๐ ALL TESTS PASSED! v1.5.0 is ready for production!"
|
|
82
|
-
echo ""
|
|
83
|
-
echo "๐ฅ NEW FEATURES IN v1.5.0:"
|
|
84
|
-
echo "โข 23 total tools (up from 12 in v1.4.0)"
|
|
85
|
-
echo "โข Complete base discovery with list_bases"
|
|
86
|
-
echo "โข Advanced schema management"
|
|
87
|
-
echo "โข Table and field creation/modification"
|
|
88
|
-
echo "โข Comprehensive field type reference"
|
|
89
|
-
echo "โข Enhanced table inspection"
|
|
90
|
-
echo ""
|
|
91
|
-
exit 0
|
|
92
|
-
else
|
|
93
|
-
echo ""
|
|
94
|
-
echo "โ ๏ธ Some tests failed. Please review the errors above."
|
|
95
|
-
exit 1
|
|
96
|
-
fi
|
package/test_v1.5.0_final.sh
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# COMPREHENSIVE FINAL TEST SUITE - Airtable MCP Server v1.5.0
|
|
4
|
-
# Tests ALL 23 tools with no assumptions
|
|
5
|
-
|
|
6
|
-
set -e
|
|
7
|
-
SERVER_URL="http://localhost:8010/mcp"
|
|
8
|
-
PASSED=0
|
|
9
|
-
FAILED=0
|
|
10
|
-
TEST_RECORD_ID=""
|
|
11
|
-
TEST_WEBHOOK_ID=""
|
|
12
|
-
CREATED_FIELD_ID=""
|
|
13
|
-
|
|
14
|
-
echo "๐งช FINAL COMPREHENSIVE TEST SUITE - v1.5.0"
|
|
15
|
-
echo "==========================================="
|
|
16
|
-
echo "Testing ALL 23 tools with real API calls"
|
|
17
|
-
echo ""
|
|
18
|
-
|
|
19
|
-
# Function to make MCP calls
|
|
20
|
-
call_tool() {
|
|
21
|
-
local tool_name="$1"
|
|
22
|
-
local params="$2"
|
|
23
|
-
curl -s -X POST "$SERVER_URL" \
|
|
24
|
-
-H "Content-Type: application/json" \
|
|
25
|
-
-d "{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/call\", \"params\": {\"name\": \"$tool_name\", \"arguments\": $params}}"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
# Enhanced test function with better error reporting
|
|
29
|
-
test_tool() {
|
|
30
|
-
local tool_name="$1"
|
|
31
|
-
local params="$2"
|
|
32
|
-
local description="$3"
|
|
33
|
-
local expect_fail="$4"
|
|
34
|
-
|
|
35
|
-
echo -n "๐ง $tool_name: $description... "
|
|
36
|
-
|
|
37
|
-
if result=$(call_tool "$tool_name" "$params" 2>&1); then
|
|
38
|
-
if echo "$result" | jq -e '.result.content[0].text' > /dev/null 2>&1; then
|
|
39
|
-
response_text=$(echo "$result" | jq -r '.result.content[0].text')
|
|
40
|
-
if [[ "$expect_fail" == "true" ]]; then
|
|
41
|
-
if echo "$response_text" | grep -q "error\|Error\|not found\|requires"; then
|
|
42
|
-
echo "โ
PASS (Expected failure)"
|
|
43
|
-
((PASSED++))
|
|
44
|
-
else
|
|
45
|
-
echo "โ FAIL (Should have failed)"
|
|
46
|
-
echo " Response: ${response_text:0:100}..."
|
|
47
|
-
((FAILED++))
|
|
48
|
-
fi
|
|
49
|
-
else
|
|
50
|
-
echo "โ
PASS"
|
|
51
|
-
((PASSED++))
|
|
52
|
-
# Store important IDs for later tests
|
|
53
|
-
if [[ "$tool_name" == "create_record" ]]; then
|
|
54
|
-
TEST_RECORD_ID=$(echo "$result" | jq -r '.result.content[0].text' | grep -o 'rec[a-zA-Z0-9]\{10,20\}' | head -1)
|
|
55
|
-
echo " ๐ Stored record ID: $TEST_RECORD_ID"
|
|
56
|
-
elif [[ "$tool_name" == "create_webhook" ]]; then
|
|
57
|
-
TEST_WEBHOOK_ID=$(echo "$result" | jq -r '.result.content[0].text' | grep -o 'ach[a-zA-Z0-9]\{10,20\}' | head -1)
|
|
58
|
-
echo " ๐ช Stored webhook ID: $TEST_WEBHOOK_ID"
|
|
59
|
-
elif [[ "$tool_name" == "create_field" ]]; then
|
|
60
|
-
CREATED_FIELD_ID=$(echo "$result" | jq -r '.result.content[0].text' | grep -o 'fld[a-zA-Z0-9]\{10,20\}' | head -1)
|
|
61
|
-
echo " ๐๏ธ Stored field ID: $CREATED_FIELD_ID"
|
|
62
|
-
fi
|
|
63
|
-
fi
|
|
64
|
-
else
|
|
65
|
-
if echo "$result" | jq -e '.error' > /dev/null 2>&1; then
|
|
66
|
-
error_msg=$(echo "$result" | jq -r '.error.message')
|
|
67
|
-
if [[ "$expect_fail" == "true" ]]; then
|
|
68
|
-
echo "โ
PASS (Expected error: $error_msg)"
|
|
69
|
-
((PASSED++))
|
|
70
|
-
else
|
|
71
|
-
echo "โ FAIL (API Error: $error_msg)"
|
|
72
|
-
((FAILED++))
|
|
73
|
-
fi
|
|
74
|
-
else
|
|
75
|
-
echo "โ FAIL (Invalid response)"
|
|
76
|
-
echo " Response: $result"
|
|
77
|
-
((FAILED++))
|
|
78
|
-
fi
|
|
79
|
-
fi
|
|
80
|
-
else
|
|
81
|
-
echo "โ FAIL (Request failed)"
|
|
82
|
-
echo " Error: $result"
|
|
83
|
-
((FAILED++))
|
|
84
|
-
fi
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
echo "๐ PHASE 1: Core Data Operations (7 tools)"
|
|
88
|
-
echo "==========================================="
|
|
89
|
-
|
|
90
|
-
test_tool "list_tables" "{}" "List all tables in base"
|
|
91
|
-
test_tool "list_records" "{\"table\": \"Test Table CRUD\", \"maxRecords\": 3}" "List records with limit"
|
|
92
|
-
test_tool "create_record" "{\"table\": \"Test Table CRUD\", \"fields\": {\"Name\": \"v1.5.0 Test Record\", \"Description\": \"Created during final testing\", \"Status\": \"Testing\"}}" "Create test record"
|
|
93
|
-
|
|
94
|
-
# Use the created record ID for get_record test
|
|
95
|
-
if [[ -n "$TEST_RECORD_ID" ]]; then
|
|
96
|
-
test_tool "get_record" "{\"table\": \"Test Table CRUD\", \"recordId\": \"$TEST_RECORD_ID\"}" "Get the created record"
|
|
97
|
-
test_tool "update_record" "{\"table\": \"Test Table CRUD\", \"recordId\": \"$TEST_RECORD_ID\", \"fields\": {\"Status\": \"Updated\"}}" "Update the created record"
|
|
98
|
-
else
|
|
99
|
-
echo "โ ๏ธ Skipping get_record and update_record tests (no record ID)"
|
|
100
|
-
((FAILED += 2))
|
|
101
|
-
fi
|
|
102
|
-
|
|
103
|
-
test_tool "search_records" "{\"table\": \"Test Table CRUD\", \"searchTerm\": \"v1.5.0\"}" "Search for our test record"
|
|
104
|
-
|
|
105
|
-
echo ""
|
|
106
|
-
echo "๐ PHASE 2: Webhook Management (5 tools)"
|
|
107
|
-
echo "========================================"
|
|
108
|
-
|
|
109
|
-
test_tool "list_webhooks" "{}" "List existing webhooks"
|
|
110
|
-
test_tool "create_webhook" "{\"notificationUrl\": \"https://webhook.site/test-v1.5.0\", \"specification\": {\"options\": {\"filters\": {\"dataTypes\": [\"tableData\"]}}}}" "Create test webhook"
|
|
111
|
-
|
|
112
|
-
if [[ -n "$TEST_WEBHOOK_ID" ]]; then
|
|
113
|
-
test_tool "get_webhook_payloads" "{\"webhookId\": \"$TEST_WEBHOOK_ID\"}" "Get webhook payloads"
|
|
114
|
-
test_tool "refresh_webhook" "{\"webhookId\": \"$TEST_WEBHOOK_ID\"}" "Refresh webhook"
|
|
115
|
-
test_tool "delete_webhook" "{\"webhookId\": \"$TEST_WEBHOOK_ID\"}" "Delete test webhook"
|
|
116
|
-
else
|
|
117
|
-
echo "โ ๏ธ Skipping webhook payload/refresh/delete tests (no webhook ID)"
|
|
118
|
-
((FAILED += 3))
|
|
119
|
-
fi
|
|
120
|
-
|
|
121
|
-
echo ""
|
|
122
|
-
echo "๐๏ธ PHASE 3: NEW Schema Discovery (6 tools)"
|
|
123
|
-
echo "==========================================="
|
|
124
|
-
|
|
125
|
-
test_tool "list_bases" "{}" "Discover all accessible bases"
|
|
126
|
-
test_tool "get_base_schema" "{}" "Get complete base schema"
|
|
127
|
-
test_tool "describe_table" "{\"table\": \"Test Table CRUD\"}" "Describe table with field details"
|
|
128
|
-
test_tool "list_field_types" "{}" "List all available field types"
|
|
129
|
-
test_tool "get_table_views" "{\"table\": \"Test Table CRUD\"}" "Get table views"
|
|
130
|
-
|
|
131
|
-
# Test pagination for list_bases
|
|
132
|
-
test_tool "list_bases" "{\"offset\": \"invalid_offset\"}" "Test list_bases with invalid offset"
|
|
133
|
-
|
|
134
|
-
echo ""
|
|
135
|
-
echo "๐ง PHASE 4: NEW Field Management (4 tools)"
|
|
136
|
-
echo "=========================================="
|
|
137
|
-
|
|
138
|
-
test_tool "create_field" "{\"table\": \"Test Table CRUD\", \"name\": \"v1.5.0 Test Field\", \"type\": \"singleLineText\", \"description\": \"Field created during v1.5.0 testing\"}" "Create new field"
|
|
139
|
-
|
|
140
|
-
if [[ -n "$CREATED_FIELD_ID" ]]; then
|
|
141
|
-
test_tool "update_field" "{\"table\": \"Test Table CRUD\", \"fieldId\": \"$CREATED_FIELD_ID\", \"name\": \"v1.5.0 Updated Field\", \"description\": \"Updated during testing\"}" "Update the created field"
|
|
142
|
-
test_tool "delete_field" "{\"table\": \"Test Table CRUD\", \"fieldId\": \"$CREATED_FIELD_ID\", \"confirm\": true}" "Delete the test field"
|
|
143
|
-
else
|
|
144
|
-
echo "โ ๏ธ Skipping field update/delete tests (no field ID)"
|
|
145
|
-
((FAILED += 2))
|
|
146
|
-
fi
|
|
147
|
-
|
|
148
|
-
# Test safety checks
|
|
149
|
-
test_tool "delete_field" "{\"table\": \"Test Table CRUD\", \"fieldId\": \"fldDummyID\", \"confirm\": false}" "Test field deletion without confirmation" "true"
|
|
150
|
-
|
|
151
|
-
echo ""
|
|
152
|
-
echo "๐ข PHASE 5: NEW Table Management (3 tools)"
|
|
153
|
-
echo "========================================="
|
|
154
|
-
|
|
155
|
-
test_tool "create_table" "{\"name\": \"v1.5.0 Test Table\", \"description\": \"Table created during v1.5.0 testing\", \"fields\": [{\"name\": \"Name\", \"type\": \"singleLineText\"}, {\"name\": \"Notes\", \"type\": \"multilineText\"}]}" "Create new table"
|
|
156
|
-
test_tool "update_table" "{\"table\": \"v1.5.0 Test Table\", \"name\": \"v1.5.0 Updated Table\", \"description\": \"Updated description\"}" "Update table metadata"
|
|
157
|
-
|
|
158
|
-
# Test safety checks
|
|
159
|
-
test_tool "delete_table" "{\"table\": \"v1.5.0 Updated Table\", \"confirm\": false}" "Test table deletion without confirmation" "true"
|
|
160
|
-
test_tool "delete_table" "{\"table\": \"v1.5.0 Updated Table\", \"confirm\": true}" "Delete the test table"
|
|
161
|
-
|
|
162
|
-
echo ""
|
|
163
|
-
echo "โ ๏ธ PHASE 6: Error Handling & Edge Cases"
|
|
164
|
-
echo "======================================="
|
|
165
|
-
|
|
166
|
-
test_tool "get_record" "{\"table\": \"NonExistentTable\", \"recordId\": \"recFakeID123\"}" "Test with non-existent table" "true"
|
|
167
|
-
test_tool "describe_table" "{\"table\": \"NonExistentTable\"}" "Test describe non-existent table" "true"
|
|
168
|
-
test_tool "create_field" "{\"table\": \"NonExistentTable\", \"name\": \"Test\", \"type\": \"singleLineText\"}" "Test create field in non-existent table" "true"
|
|
169
|
-
test_tool "update_table" "{\"table\": \"NonExistentTable\", \"name\": \"New Name\"}" "Test update non-existent table" "true"
|
|
170
|
-
|
|
171
|
-
echo ""
|
|
172
|
-
echo "๐ PHASE 7: Security Verification"
|
|
173
|
-
echo "================================"
|
|
174
|
-
|
|
175
|
-
# Check that logs don't contain sensitive data
|
|
176
|
-
echo -n "๐ Security check: Log file doesn't contain tokens... "
|
|
177
|
-
if grep -q "pat" /tmp/v1.5.0_test.log; then
|
|
178
|
-
echo "โ FAIL (Token found in logs)"
|
|
179
|
-
((FAILED++))
|
|
180
|
-
else
|
|
181
|
-
echo "โ
PASS"
|
|
182
|
-
((PASSED++))
|
|
183
|
-
fi
|
|
184
|
-
|
|
185
|
-
# Clean up test record if it exists
|
|
186
|
-
if [[ -n "$TEST_RECORD_ID" ]]; then
|
|
187
|
-
echo -n "๐งน Cleanup: Deleting test record... "
|
|
188
|
-
cleanup_result=$(test_tool "delete_record" "{\"table\": \"Test Table CRUD\", \"recordId\": \"$TEST_RECORD_ID\"}" "Delete test record" 2>&1)
|
|
189
|
-
if echo "$cleanup_result" | grep -q "โ
PASS"; then
|
|
190
|
-
echo "โ
CLEANED"
|
|
191
|
-
else
|
|
192
|
-
echo "โ ๏ธ CLEANUP FAILED"
|
|
193
|
-
fi
|
|
194
|
-
fi
|
|
195
|
-
|
|
196
|
-
echo ""
|
|
197
|
-
echo "๐ FINAL TEST RESULTS"
|
|
198
|
-
echo "===================="
|
|
199
|
-
echo "โ
Passed: $PASSED"
|
|
200
|
-
echo "โ Failed: $FAILED"
|
|
201
|
-
echo "๐ Total Tests: $((PASSED + FAILED))"
|
|
202
|
-
echo "๐ Success Rate: $(echo "scale=1; $PASSED * 100 / ($PASSED + $FAILED)" | bc -l)%"
|
|
203
|
-
|
|
204
|
-
if [ $FAILED -eq 0 ]; then
|
|
205
|
-
echo ""
|
|
206
|
-
echo "๐ ๐ ๐ ALL TESTS PASSED! ๐ ๐ ๐"
|
|
207
|
-
echo ""
|
|
208
|
-
echo "โ
v1.5.0 is READY FOR PRODUCTION!"
|
|
209
|
-
echo ""
|
|
210
|
-
echo "๐ ACHIEVEMENTS:"
|
|
211
|
-
echo "โข 23 tools working perfectly"
|
|
212
|
-
echo "โข Complete schema management"
|
|
213
|
-
echo "โข Robust error handling"
|
|
214
|
-
echo "โข Security verified"
|
|
215
|
-
echo "โข All edge cases handled"
|
|
216
|
-
echo ""
|
|
217
|
-
echo "๐ฆ Ready for GitHub and NPM release!"
|
|
218
|
-
exit 0
|
|
219
|
-
else
|
|
220
|
-
echo ""
|
|
221
|
-
echo "โ SOME TESTS FAILED"
|
|
222
|
-
echo "Please review failures above before release."
|
|
223
|
-
exit 1
|
|
224
|
-
fi
|