@rashidazarang/airtable-mcp 1.2.1 → 1.2.4

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,60 @@
1
+ # Release Notes - v1.2.4
2
+
3
+ ## šŸ”’ Security Fix Release
4
+
5
+ ### Critical Security Fix
6
+ - **REMOVED hardcoded API tokens from test files** (Addresses Issue #7)
7
+ - `test_client.py` and `test_mcp_comprehensive.js` now require environment variables
8
+ - Added security notice documentation
9
+ - No exposed credentials in the codebase
10
+
11
+ ### šŸ› Bug Fixes
12
+
13
+ #### Smithery Cloud Deployment Issues (Issues #5 and #6)
14
+ - **Fixed HTTP 400 errors** when using Smithery
15
+ - **Switched to JavaScript implementation** for Smithery deployment
16
+ - Updated `smithery.yaml` to use `airtable_simple.js` instead of problematic Python server
17
+ - Created dedicated `Dockerfile.node` for Node.js deployment
18
+ - Fixed authentication flow for Smithery connections
19
+
20
+ ### šŸ“š Documentation Updates
21
+ - Added `SECURITY_NOTICE.md` with token rotation instructions
22
+ - Created `.env.example` file for secure configuration
23
+ - Updated Dockerfile references for Glama listing (Issue #4)
24
+
25
+ ### šŸ”§ Improvements
26
+ - Added environment variable support with dotenv
27
+ - Improved logging system with configurable levels (ERROR, WARN, INFO, DEBUG)
28
+ - Better error messages for missing credentials
29
+
30
+ ### āš ļø Breaking Changes
31
+ - Test files now require environment variables:
32
+ ```bash
33
+ export AIRTABLE_TOKEN="your_token"
34
+ export AIRTABLE_BASE_ID="your_base_id"
35
+ ```
36
+
37
+ ### šŸš€ Migration Guide
38
+
39
+ 1. **Update your environment variables:**
40
+ ```bash
41
+ cp .env.example .env
42
+ # Edit .env with your credentials
43
+ ```
44
+
45
+ 2. **For Smithery users:**
46
+ - Reinstall the MCP to get the latest configuration
47
+ - The server now properly accepts credentials through Smithery's config
48
+
49
+ 3. **For direct users:**
50
+ - Continue using command line arguments or switch to environment variables
51
+ - Both methods are supported
52
+
53
+ ### šŸ“ Notes
54
+ - All previously exposed tokens have been revoked
55
+ - Please use your own Airtable credentials
56
+ - Never commit API tokens to version control
57
+
58
+ ---
59
+
60
+ **Full Changelog**: [v1.2.3...v1.2.4](https://github.com/rashidazarang/airtable-mcp/compare/v1.2.3...v1.2.4)
@@ -0,0 +1,40 @@
1
+ # Security Notice
2
+
3
+ ## Important: API Token Rotation Required
4
+
5
+ If you have been using or testing this repository before January 2025, please note that hardcoded API tokens were previously included in test files. These have been removed and replaced with environment variable requirements.
6
+
7
+ ### Actions Required:
8
+
9
+ 1. **If you used the exposed tokens**:
10
+ - These tokens have been revoked and are no longer valid
11
+ - You must use your own Airtable API credentials
12
+
13
+ 2. **For all users**:
14
+ - Never commit API tokens to version control
15
+ - Always use environment variables or secure configuration files
16
+ - Add `.env` to your `.gitignore` file
17
+
18
+ ### Secure Configuration
19
+
20
+ Set your credentials using environment variables:
21
+
22
+ ```bash
23
+ export AIRTABLE_TOKEN="your_personal_token_here"
24
+ export AIRTABLE_BASE_ID="your_base_id_here"
25
+ ```
26
+
27
+ Or create a `.env` file (never commit this):
28
+
29
+ ```env
30
+ AIRTABLE_TOKEN=your_personal_token_here
31
+ AIRTABLE_BASE_ID=your_base_id_here
32
+ ```
33
+
34
+ ### Reporting Security Issues
35
+
36
+ If you discover any security vulnerabilities, please report them to:
37
+ - Open an issue on GitHub (without including sensitive details)
38
+ - Contact the maintainer directly for sensitive information
39
+
40
+ Thank you for helping keep this project secure.
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+
3
+ const http = require('http');
4
+ const https = require('https');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ // Load environment variables from .env file if it exists
9
+ const envPath = path.join(__dirname, '.env');
10
+ if (fs.existsSync(envPath)) {
11
+ require('dotenv').config({ path: envPath });
12
+ }
13
+
14
+ // Parse command line arguments with environment variable fallback
15
+ const args = process.argv.slice(2);
16
+ let tokenIndex = args.indexOf('--token');
17
+ let baseIndex = args.indexOf('--base');
18
+
19
+ // Use environment variables as fallback
20
+ const token = tokenIndex !== -1 ? args[tokenIndex + 1] : process.env.AIRTABLE_TOKEN || process.env.AIRTABLE_API_TOKEN;
21
+ const baseId = baseIndex !== -1 ? args[baseIndex + 1] : process.env.AIRTABLE_BASE_ID || process.env.AIRTABLE_BASE;
22
+
23
+ if (!token || !baseId) {
24
+ console.error('Error: Missing Airtable credentials');
25
+ console.error('\nUsage options:');
26
+ console.error(' 1. Command line: node airtable_simple.js --token YOUR_TOKEN --base YOUR_BASE_ID');
27
+ console.error(' 2. Environment variables: AIRTABLE_TOKEN and AIRTABLE_BASE_ID');
28
+ console.error(' 3. .env file with AIRTABLE_TOKEN and AIRTABLE_BASE_ID');
29
+ process.exit(1);
30
+ }
31
+
32
+ // Configure logging levels
33
+ const LOG_LEVELS = {
34
+ ERROR: 0,
35
+ WARN: 1,
36
+ INFO: 2,
37
+ DEBUG: 3
38
+ };
39
+
40
+ const currentLogLevel = process.env.LOG_LEVEL ? LOG_LEVELS[process.env.LOG_LEVEL.toUpperCase()] || LOG_LEVELS.INFO : LOG_LEVELS.INFO;
41
+
42
+ function log(level, message, ...args) {
43
+ const levelName = Object.keys(LOG_LEVELS).find(key => LOG_LEVELS[key] === level);
44
+ const timestamp = new Date().toISOString();
45
+
46
+ if (level <= currentLogLevel) {
47
+ const prefix = `[${timestamp}] [${levelName}]`;
48
+ if (level === LOG_LEVELS.ERROR) {
49
+ console.error(prefix, message, ...args);
50
+ } else if (level === LOG_LEVELS.WARN) {
51
+ console.warn(prefix, message, ...args);
52
+ } else {
53
+ console.log(prefix, message, ...args);
54
+ }
55
+ }
56
+ }
57
+
58
+ log(LOG_LEVELS.INFO, `Starting Airtable MCP server with token ${token.slice(0, 5)}...${token.slice(-5)} and base ${baseId}`);
59
+
60
+ // Create HTTP server
61
+ const server = http.createServer(async (req, res) => {
62
+ // Enable CORS
63
+ res.setHeader('Access-Control-Allow-Origin', '*');
64
+ res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
65
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
66
+
67
+ // Handle preflight request
68
+ if (req.method === 'OPTIONS') {
69
+ res.writeHead(200);
70
+ res.end();
71
+ return;
72
+ }
73
+
74
+ // Only handle POST requests to /mcp
75
+ if (req.method !== 'POST' || !req.url.endsWith('/mcp')) {
76
+ res.writeHead(404);
77
+ res.end();
78
+ return;
79
+ }
80
+
81
+ let body = '';
82
+ req.on('data', chunk => {
83
+ body += chunk.toString();
84
+ });
85
+
86
+ req.on('end', async () => {
87
+ try {
88
+ const request = JSON.parse(body);
89
+
90
+ // Handle JSON-RPC methods
91
+ if (request.method === 'resources/list') {
92
+ const response = {
93
+ jsonrpc: '2.0',
94
+ id: request.id,
95
+ result: {
96
+ resources: [
97
+ {
98
+ id: 'airtable_tables',
99
+ name: 'Airtable Tables',
100
+ description: 'Tables in your Airtable base'
101
+ }
102
+ ]
103
+ }
104
+ };
105
+ res.writeHead(200, { 'Content-Type': 'application/json' });
106
+ res.end(JSON.stringify(response));
107
+ return;
108
+ }
109
+
110
+ if (request.method === 'prompts/list') {
111
+ const response = {
112
+ jsonrpc: '2.0',
113
+ id: request.id,
114
+ result: {
115
+ prompts: [
116
+ {
117
+ id: 'tables_prompt',
118
+ name: 'List Tables',
119
+ description: 'List all tables'
120
+ }
121
+ ]
122
+ }
123
+ };
124
+ res.writeHead(200, { 'Content-Type': 'application/json' });
125
+ res.end(JSON.stringify(response));
126
+ return;
127
+ }
128
+
129
+ // Handle tool calls
130
+ if (request.method === 'tools/call') {
131
+ const toolName = request.params.name;
132
+
133
+ if (toolName === 'list_tables') {
134
+ // Call Airtable API to list tables
135
+ const result = await callAirtableAPI(`meta/bases/${baseId}/tables`);
136
+ const tables = result.tables || [];
137
+
138
+ const tableList = tables.map((table, i) =>
139
+ `${i+1}. ${table.name} (ID: ${table.id})`
140
+ ).join('\n');
141
+
142
+ const response = {
143
+ jsonrpc: '2.0',
144
+ id: request.id,
145
+ result: {
146
+ content: [
147
+ {
148
+ type: 'text',
149
+ text: tables.length > 0
150
+ ? `Tables in this base:\n${tableList}`
151
+ : 'No tables found in this base.'
152
+ }
153
+ ]
154
+ }
155
+ };
156
+
157
+ res.writeHead(200, { 'Content-Type': 'application/json' });
158
+ res.end(JSON.stringify(response));
159
+ return;
160
+ }
161
+
162
+ if (toolName === 'list_records') {
163
+ const tableName = request.params.arguments.table_name;
164
+ const maxRecords = request.params.arguments.max_records || 100;
165
+
166
+ // Call Airtable API to list records
167
+ const result = await callAirtableAPI(`${baseId}/${tableName}`, { maxRecords });
168
+ const records = result.records || [];
169
+
170
+ const recordList = records.map((record, i) => {
171
+ const fields = Object.entries(record.fields || {})
172
+ .map(([k, v]) => `${k}: ${v}`)
173
+ .join(', ');
174
+ return `${i+1}. ID: ${record.id} - ${fields}`;
175
+ }).join('\n');
176
+
177
+ const response = {
178
+ jsonrpc: '2.0',
179
+ id: request.id,
180
+ result: {
181
+ content: [
182
+ {
183
+ type: 'text',
184
+ text: records.length > 0
185
+ ? `Records:\n${recordList}`
186
+ : 'No records found in this table.'
187
+ }
188
+ ]
189
+ }
190
+ };
191
+
192
+ res.writeHead(200, { 'Content-Type': 'application/json' });
193
+ res.end(JSON.stringify(response));
194
+ return;
195
+ }
196
+
197
+ // Tool not found
198
+ const response = {
199
+ jsonrpc: '2.0',
200
+ id: request.id,
201
+ error: {
202
+ code: -32601,
203
+ message: `Tool ${toolName} not found`
204
+ }
205
+ };
206
+ res.writeHead(200, { 'Content-Type': 'application/json' });
207
+ res.end(JSON.stringify(response));
208
+ return;
209
+ }
210
+
211
+ // Method not found
212
+ const response = {
213
+ jsonrpc: '2.0',
214
+ id: request.id,
215
+ error: {
216
+ code: -32601,
217
+ message: `Method ${request.method} not found`
218
+ }
219
+ };
220
+ res.writeHead(200, { 'Content-Type': 'application/json' });
221
+ res.end(JSON.stringify(response));
222
+
223
+ } catch (error) {
224
+ console.error('Error processing request:', error);
225
+ const response = {
226
+ jsonrpc: '2.0',
227
+ id: request.id || null,
228
+ error: {
229
+ code: -32000,
230
+ message: error.message || 'Unknown error'
231
+ }
232
+ };
233
+ res.writeHead(200, { 'Content-Type': 'application/json' });
234
+ res.end(JSON.stringify(response));
235
+ }
236
+ });
237
+ });
238
+
239
+ // Helper function to call Airtable API
240
+ function callAirtableAPI(endpoint, params = {}) {
241
+ return new Promise((resolve, reject) => {
242
+ const queryParams = new URLSearchParams(params).toString();
243
+ const url = `https://api.airtable.com/v0/${endpoint}${queryParams ? '?' + queryParams : ''}`;
244
+
245
+ const options = {
246
+ headers: {
247
+ 'Authorization': `Bearer ${token}`,
248
+ 'Content-Type': 'application/json'
249
+ }
250
+ };
251
+
252
+ https.get(url, options, (response) => {
253
+ let data = '';
254
+
255
+ response.on('data', (chunk) => {
256
+ data += chunk;
257
+ });
258
+
259
+ response.on('end', () => {
260
+ try {
261
+ resolve(JSON.parse(data));
262
+ } catch (e) {
263
+ reject(new Error(`Failed to parse Airtable response: ${e.message}`));
264
+ }
265
+ });
266
+ }).on('error', (error) => {
267
+ reject(new Error(`Airtable API request failed: ${error.message}`));
268
+ });
269
+ });
270
+ }
271
+
272
+ // Start the server on port 8010
273
+ const PORT = 8010;
274
+ server.listen(PORT, () => {
275
+ console.log(`Airtable MCP server running at http://localhost:${PORT}/mcp`);
276
+ console.log(`For Claude, use this URL: http://localhost:${PORT}/mcp`);
277
+ });
package/cleanup.sh ADDED
@@ -0,0 +1,69 @@
1
+ #!/bin/bash
2
+
3
+ echo "🧹 Airtable MCP Cleanup Script"
4
+ echo "=============================="
5
+
6
+ # Function to check if port is in use
7
+ check_port() {
8
+ if lsof -Pi :8010 -sTCP:LISTEN -t >/dev/null ; then
9
+ echo "āš ļø Port 8010 is in use"
10
+ return 0
11
+ else
12
+ echo "āœ… Port 8010 is free"
13
+ return 1
14
+ fi
15
+ }
16
+
17
+ # Function to kill processes on port 8010
18
+ kill_port() {
19
+ echo "šŸ”„ Killing processes on port 8010..."
20
+ lsof -ti:8010 | xargs kill -9 2>/dev/null
21
+ echo "āœ… Port 8010 cleared"
22
+ }
23
+
24
+ # Function to clean up temporary files
25
+ cleanup_files() {
26
+ echo "🧹 Cleaning up temporary files..."
27
+
28
+ # Remove test files if they exist
29
+ if [ -f "test_mcp_comprehensive.js" ]; then
30
+ rm test_mcp_comprehensive.js
31
+ echo "āœ… Removed test_mcp_comprehensive.js"
32
+ fi
33
+
34
+ if [ -f "quick_test.sh" ]; then
35
+ rm quick_test.sh
36
+ echo "āœ… Removed quick_test.sh"
37
+ fi
38
+
39
+ if [ -f "MCP_REVIEW_SUMMARY.md" ]; then
40
+ rm MCP_REVIEW_SUMMARY.md
41
+ echo "āœ… Removed MCP_REVIEW_SUMMARY.md"
42
+ fi
43
+
44
+ if [ -f "cleanup.sh" ]; then
45
+ rm cleanup.sh
46
+ echo "āœ… Removed cleanup.sh"
47
+ fi
48
+ }
49
+
50
+ # Main cleanup process
51
+ echo "1. Checking port status..."
52
+ if check_port; then
53
+ echo "2. Clearing port 8010..."
54
+ kill_port
55
+ else
56
+ echo "2. Port is already free"
57
+ fi
58
+
59
+ echo "3. Cleaning up temporary files..."
60
+ cleanup_files
61
+
62
+ echo ""
63
+ echo "šŸŽ‰ Cleanup completed!"
64
+ echo ""
65
+ echo "To start the MCP server again:"
66
+ echo " node airtable_simple.js --token YOUR_TOKEN --base YOUR_BASE_ID"
67
+ echo ""
68
+ echo "To test the MCP:"
69
+ echo " npm run test"
@@ -0,0 +1,16 @@
1
+ {
2
+ "mcpServers": {
3
+ "airtable": {
4
+ "command": "curl",
5
+ "args": [
6
+ "-s",
7
+ "-X",
8
+ "POST",
9
+ "-H",
10
+ "Content-Type: application/json",
11
+ "http://localhost:8010/mcp"
12
+ ],
13
+ "url": "http://localhost:8010/mcp"
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,27 @@
1
+ # Add proper error handling
2
+ import traceback
3
+ import sys
4
+
5
+ # Override the default error handlers to format errors as proper JSON
6
+ def handle_exceptions(func):
7
+ async def wrapper(*args, **kwargs):
8
+ try:
9
+ return await func(*args, **kwargs)
10
+ except Exception as e:
11
+ error_trace = traceback.format_exc()
12
+ sys.stderr.write(f"Error in MCP handler: {str(e)}\n{error_trace}\n")
13
+ # Return a properly formatted JSON error
14
+ return {"error": {"code": -32000, "message": str(e)}}
15
+ return wrapper
16
+
17
+ # Apply the decorator to all RPC methods
18
+ original_rpc_method = app.rpc_method
19
+ def patched_rpc_method(*args, **kwargs):
20
+ def decorator(func):
21
+ wrapped_func = handle_exceptions(func)
22
+ return original_rpc_method(*args, **kwargs)(wrapped_func)
23
+ return decorator
24
+
25
+ # Then add this line right before creating the FastMCP instance:
26
+ # Replace app.rpc_method with our patched version
27
+ app.rpc_method = patched_rpc_method
@@ -10,6 +10,7 @@ import json
10
10
  import logging
11
11
  import requests
12
12
  import argparse
13
+ import traceback
13
14
  from typing import Optional, Dict, Any, List
14
15
 
15
16
  try:
@@ -68,6 +69,36 @@ if args.config_json:
68
69
  # Create MCP server
69
70
  app = FastMCP("Airtable Tools")
70
71
 
72
+ # Add error handling wrapper for all MCP methods
73
+ def handle_exceptions(func):
74
+ """Decorator to properly handle and format exceptions in MCP functions"""
75
+ async def wrapper(*args, **kwargs):
76
+ try:
77
+ return await func(*args, **kwargs)
78
+ except Exception as e:
79
+ error_trace = traceback.format_exc()
80
+ logger.error(f"Error in MCP handler: {str(e)}\n{error_trace}")
81
+ sys.stderr.write(f"Error in MCP handler: {str(e)}\n{error_trace}\n")
82
+
83
+ # For tool functions that return strings, return a formatted error message
84
+ if hasattr(func, "__annotations__") and func.__annotations__.get("return") == str:
85
+ return f"Error: {str(e)}"
86
+
87
+ # For RPC methods that return dicts, return a properly formatted JSON error
88
+ return {"error": {"code": -32000, "message": str(e)}}
89
+ return wrapper
90
+
91
+ # Patch the tool method to automatically apply error handling
92
+ original_tool = app.tool
93
+ def patched_tool(*args, **kwargs):
94
+ def decorator(func):
95
+ wrapped_func = handle_exceptions(func)
96
+ return original_tool(*args, **kwargs)(wrapped_func)
97
+ return decorator
98
+
99
+ # Replace app.tool with our patched version
100
+ app.tool = patched_tool
101
+
71
102
  # Get token from arguments, config, or environment
72
103
  token = args.api_token or config.get("airtable_token", "") or os.environ.get("AIRTABLE_PERSONAL_ACCESS_TOKEN", "")
73
104
  # Clean up token if it has trailing quote
@@ -297,50 +328,9 @@ async def set_base_id(base_id_param: str) -> str:
297
328
  base_id = base_id_param
298
329
  return f"Base ID set to: {base_id}"
299
330
 
300
- # Add Claude-specific methods
301
- @app.rpc_method("resources/list")
302
- async def resources_list(params: Dict = None) -> Dict:
303
- """List available Airtable resources for Claude"""
304
- resources = [
305
- {
306
- "id": "airtable_bases",
307
- "name": "Airtable Bases",
308
- "description": "The Airtable bases accessible with your API token"
309
- },
310
- {
311
- "id": "airtable_tables",
312
- "name": "Airtable Tables",
313
- "description": "Tables in your current Airtable base"
314
- },
315
- {
316
- "id": "airtable_records",
317
- "name": "Airtable Records",
318
- "description": "Records in your Airtable tables"
319
- }
320
- ]
321
- return {"resources": resources}
322
-
323
- @app.rpc_method("prompts/list")
324
- async def prompts_list(params: Dict = None) -> Dict:
325
- """List available prompts for Claude"""
326
- prompts = [
327
- {
328
- "id": "list_tables_prompt",
329
- "name": "List Tables",
330
- "description": "List all tables in your Airtable base"
331
- },
332
- {
333
- "id": "list_records_prompt",
334
- "name": "List Records",
335
- "description": "List records from a specific Airtable table"
336
- },
337
- {
338
- "id": "create_record_prompt",
339
- "name": "Create Record",
340
- "description": "Create a new record in an Airtable table"
341
- }
342
- ]
343
- return {"prompts": prompts}
331
+ # Note: rpc_method is not available in the current MCP version
332
+ # These methods would be used for Claude-specific functionality
333
+ # but are not needed for basic MCP operation
344
334
 
345
335
  # Start the server
346
336
  if __name__ == "__main__":
package/package.json CHANGED
@@ -1,40 +1,43 @@
1
1
  {
2
2
  "name": "@rashidazarang/airtable-mcp",
3
- "version": "1.2.1",
4
- "description": "Airtable MCP for AI tools - compatible with MCP SDK 1.4.1+, includes enhanced Claude integration",
5
- "main": "index.js",
3
+ "version": "1.2.4",
4
+ "description": "Airtable MCP for Claude Desktop - Connect directly to Airtable using natural language",
5
+ "main": "airtable_simple.js",
6
6
  "bin": {
7
- "airtable-mcp": "index.js"
7
+ "airtable-mcp": "./airtable_simple.js"
8
8
  },
9
9
  "scripts": {
10
- "start": "node index.js"
10
+ "start": "node airtable_simple.js",
11
+ "start:python": "python3.10 inspector_server.py",
12
+ "test": "node test_mcp_comprehensive.js",
13
+ "test:quick": "./quick_test.sh",
14
+ "dev": "node airtable_simple.js --token YOUR_TOKEN --base YOUR_BASE_ID"
11
15
  },
12
16
  "keywords": [
13
17
  "airtable",
14
18
  "mcp",
15
- "ai",
16
19
  "claude",
20
+ "claude-desktop",
17
21
  "anthropic",
18
- "cursor",
19
- "claude-code",
20
- "smithery",
21
- "model-context-protocol",
22
- "windsurf"
22
+ "ai",
23
+ "database"
23
24
  ],
24
25
  "author": "Rashid Azarang",
25
26
  "license": "MIT",
27
+ "dependencies": {
28
+ "@smithery/cli": "^1.0.0",
29
+ "airtable": "^0.12.2",
30
+ "dotenv": "^16.0.0"
31
+ },
32
+ "engines": {
33
+ "node": ">=14.0.0"
34
+ },
26
35
  "repository": {
27
36
  "type": "git",
28
- "url": "git+https://github.com/rashidazarang/airtable-mcp.git"
37
+ "url": "https://github.com/rashidazarang/airtable-mcp"
29
38
  },
30
39
  "bugs": {
31
40
  "url": "https://github.com/rashidazarang/airtable-mcp/issues"
32
41
  },
33
- "homepage": "https://github.com/rashidazarang/airtable-mcp#readme",
34
- "engines": {
35
- "node": ">=14.0.0"
36
- },
37
- "dependencies": {
38
- "dotenv": "^16.0.3"
39
- }
42
+ "homepage": "https://github.com/rashidazarang/airtable-mcp#readme"
40
43
  }
package/quick_test.sh ADDED
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+
3
+ echo "šŸ”Œ Airtable MCP Quick Tests"
4
+ echo "==========================="
5
+
6
+ # Test 1: List Tables
7
+ echo "šŸ“Š Testing: List Tables"
8
+ curl -s -X POST http://localhost:8010/mcp \
9
+ -H "Content-Type: application/json" \
10
+ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "list_tables"}}' | jq '.result.content[0].text'
11
+
12
+ echo -e "\n"
13
+
14
+ # Test 2: List Records from Requests
15
+ echo "šŸ“„ Testing: List Records (Requests)"
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": "list_records", "arguments": {"table_name": "requests", "max_records": 2}}}' | jq '.result.content[0].text'
19
+
20
+ echo -e "\n"
21
+
22
+ # Test 3: List Records from Providers
23
+ echo "šŸ‘„ Testing: List Records (Providers)"
24
+ curl -s -X POST http://localhost:8010/mcp \
25
+ -H "Content-Type: application/json" \
26
+ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "list_records", "arguments": {"table_name": "providers", "max_records": 2}}}' | jq '.result.content[0].text'
27
+
28
+ echo -e "\nāœ… All quick tests completed!"