@rashidazarang/airtable-mcp 2.1.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # Airtable MCP Server
2
2
 
3
+ [![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/rashidazarang/airtable-mcp)](https://archestra.ai/mcp-catalog/rashidazarang__airtable-mcp)
3
4
  [![smithery badge](https://smithery.ai/badge/@rashidazarang/airtable-mcp)](https://smithery.ai/server/@rashidazarang/airtable-mcp)
4
- [![Trust Score](https://archestra.ai/badge/@rashidazarang/airtable-mcp)](https://archestra.ai/mcp-catalog/rashidazarang__airtable-mcp)
5
5
  ![Airtable](https://img.shields.io/badge/Airtable-18BFFF?style=for-the-badge&logo=Airtable&logoColor=white)
6
6
  [![MCP](https://img.shields.io/badge/MCP-1.6.0-green)](https://github.com/rashidazarang/airtable-mcp)
7
7
 
@@ -56,7 +56,7 @@ const CONFIG = {
56
56
 
57
57
  // Logging
58
58
  const LOG_LEVELS = { ERROR: 0, WARN: 1, INFO: 2, DEBUG: 3, TRACE: 4 };
59
- const currentLogLevel = LOG_LEVELS[CONFIG.LOG_LEVEL] || LOG_LEVELS.INFO;
59
+ let currentLogLevel = LOG_LEVELS[CONFIG.LOG_LEVEL] || LOG_LEVELS.INFO;
60
60
 
61
61
  function log(level, message, metadata = {}) {
62
62
  if (level <= currentLogLevel) {
@@ -236,6 +236,98 @@ const TOOLS_SCHEMA = [
236
236
  }
237
237
  ];
238
238
 
239
+ // Prompts schema - AI-powered templates for common Airtable operations
240
+ const PROMPTS_SCHEMA = [
241
+ {
242
+ name: 'analyze_data',
243
+ description: 'Analyze data patterns and provide insights from Airtable records',
244
+ arguments: [
245
+ {
246
+ name: 'table',
247
+ description: 'Table name or ID to analyze',
248
+ required: true
249
+ },
250
+ {
251
+ name: 'analysis_type',
252
+ description: 'Type of analysis (trends, summary, patterns, insights)',
253
+ required: false
254
+ },
255
+ {
256
+ name: 'field_focus',
257
+ description: 'Specific fields to focus the analysis on',
258
+ required: false
259
+ }
260
+ ]
261
+ },
262
+ {
263
+ name: 'create_report',
264
+ description: 'Generate a comprehensive report based on Airtable data',
265
+ arguments: [
266
+ {
267
+ name: 'table',
268
+ description: 'Table name or ID for the report',
269
+ required: true
270
+ },
271
+ {
272
+ name: 'report_type',
273
+ description: 'Type of report (summary, detailed, dashboard, metrics)',
274
+ required: false
275
+ },
276
+ {
277
+ name: 'time_period',
278
+ description: 'Time period for the report (if applicable)',
279
+ required: false
280
+ }
281
+ ]
282
+ },
283
+ {
284
+ name: 'data_insights',
285
+ description: 'Discover hidden insights and correlations in your Airtable data',
286
+ arguments: [
287
+ {
288
+ name: 'tables',
289
+ description: 'Comma-separated list of table names to analyze',
290
+ required: true
291
+ },
292
+ {
293
+ name: 'insight_type',
294
+ description: 'Type of insights to find (correlations, outliers, trends, predictions)',
295
+ required: false
296
+ }
297
+ ]
298
+ },
299
+ {
300
+ name: 'optimize_workflow',
301
+ description: 'Suggest workflow optimizations based on your Airtable usage patterns',
302
+ arguments: [
303
+ {
304
+ name: 'base_overview',
305
+ description: 'Overview of the base structure and usage',
306
+ required: false
307
+ },
308
+ {
309
+ name: 'optimization_focus',
310
+ description: 'Focus area (automation, fields, views, collaboration)',
311
+ required: false
312
+ }
313
+ ]
314
+ }
315
+ ];
316
+
317
+ // Roots configuration for filesystem access
318
+ const ROOTS_CONFIG = [
319
+ {
320
+ uri: 'file:///airtable-exports',
321
+ name: 'Airtable Exports'
322
+ },
323
+ {
324
+ uri: 'file:///airtable-attachments',
325
+ name: 'Airtable Attachments'
326
+ }
327
+ ];
328
+
329
+ // Logging configuration (currentLogLevel is already declared above)
330
+
239
331
  // HTTP server
240
332
  const server = http.createServer(async (req, res) => {
241
333
  // Security headers
@@ -261,13 +353,106 @@ const server = http.createServer(async (req, res) => {
261
353
  res.writeHead(200, { 'Content-Type': 'application/json' });
262
354
  res.end(JSON.stringify({
263
355
  status: 'healthy',
264
- version: '2.1.0',
356
+ version: '2.2.0',
265
357
  timestamp: new Date().toISOString(),
266
358
  uptime: process.uptime()
267
359
  }));
268
360
  return;
269
361
  }
270
362
 
363
+ // OAuth2 authorization endpoint
364
+ if (pathname === '/oauth/authorize' && req.method === 'GET') {
365
+ const params = parsedUrl.query;
366
+ const clientId = params.client_id;
367
+ const redirectUri = params.redirect_uri;
368
+ const state = params.state;
369
+ const codeChallenge = params.code_challenge;
370
+ const codeChallengeMethod = params.code_challenge_method;
371
+
372
+ // Generate authorization code
373
+ const authCode = crypto.randomBytes(32).toString('hex');
374
+
375
+ // In a real implementation, store the auth code with expiration
376
+ // and associate it with the client and PKCE challenge
377
+
378
+ res.writeHead(200, { 'Content-Type': 'text/html' });
379
+ res.end(`
380
+ <!DOCTYPE html>
381
+ <html>
382
+ <head><title>OAuth2 Authorization</title></head>
383
+ <body>
384
+ <h2>Airtable MCP Server - OAuth2 Authorization</h2>
385
+ <p>Client ID: ${clientId}</p>
386
+ <p>Redirect URI: ${redirectUri}</p>
387
+ <div style="margin: 20px 0;">
388
+ <button onclick="authorize()" style="background: #18BFFF; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;">
389
+ Authorize Application
390
+ </button>
391
+ <button onclick="deny()" style="background: #ff4444; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-left: 10px;">
392
+ Deny Access
393
+ </button>
394
+ </div>
395
+ <script>
396
+ function authorize() {
397
+ const url = '${redirectUri}?code=${authCode}&state=${state || ''}';
398
+ window.location.href = url;
399
+ }
400
+ function deny() {
401
+ const url = '${redirectUri}?error=access_denied&state=${state || ''}';
402
+ window.location.href = url;
403
+ }
404
+ </script>
405
+ </body>
406
+ </html>
407
+ `);
408
+ return;
409
+ }
410
+
411
+ // OAuth2 token endpoint
412
+ if (pathname === '/oauth/token' && req.method === 'POST') {
413
+ let body = '';
414
+ req.on('data', chunk => body += chunk.toString());
415
+
416
+ req.on('end', () => {
417
+ try {
418
+ const params = querystring.parse(body);
419
+ const grantType = params.grant_type;
420
+ const code = params.code;
421
+ const codeVerifier = params.code_verifier;
422
+ const clientId = params.client_id;
423
+
424
+ // In a real implementation, verify the authorization code and PKCE
425
+ if (grantType === 'authorization_code' && code) {
426
+ // Generate access token
427
+ const accessToken = crypto.randomBytes(32).toString('hex');
428
+ const refreshToken = crypto.randomBytes(32).toString('hex');
429
+
430
+ res.writeHead(200, { 'Content-Type': 'application/json' });
431
+ res.end(JSON.stringify({
432
+ access_token: accessToken,
433
+ token_type: 'Bearer',
434
+ expires_in: 3600,
435
+ refresh_token: refreshToken,
436
+ scope: 'data.records:read data.records:write schema.bases:read'
437
+ }));
438
+ } else {
439
+ res.writeHead(400, { 'Content-Type': 'application/json' });
440
+ res.end(JSON.stringify({
441
+ error: 'invalid_request',
442
+ error_description: 'Invalid grant type or authorization code'
443
+ }));
444
+ }
445
+ } catch (error) {
446
+ res.writeHead(400, { 'Content-Type': 'application/json' });
447
+ res.end(JSON.stringify({
448
+ error: 'invalid_request',
449
+ error_description: 'Malformed request body'
450
+ }));
451
+ }
452
+ });
453
+ return;
454
+ }
455
+
271
456
  // MCP endpoint
272
457
  if (pathname === '/mcp' && req.method === 'POST') {
273
458
  // Rate limiting
@@ -321,9 +506,9 @@ const server = http.createServer(async (req, res) => {
321
506
  logging: {}
322
507
  },
323
508
  serverInfo: {
324
- name: 'Airtable MCP Server',
325
- version: '2.1.0',
326
- description: 'Model Context Protocol server for Airtable integration'
509
+ name: 'Airtable MCP Server Enhanced',
510
+ version: '2.2.0',
511
+ description: 'Complete MCP 2024-11-05 server with Prompts, Sampling, Roots, Logging, and OAuth2'
327
512
  }
328
513
  }
329
514
  };
@@ -344,6 +529,47 @@ const server = http.createServer(async (req, res) => {
344
529
  response = await handleToolCall(request);
345
530
  break;
346
531
 
532
+ case 'prompts/list':
533
+ response = {
534
+ jsonrpc: '2.0',
535
+ id: request.id,
536
+ result: {
537
+ prompts: PROMPTS_SCHEMA
538
+ }
539
+ };
540
+ break;
541
+
542
+ case 'prompts/get':
543
+ response = await handlePromptGet(request);
544
+ break;
545
+
546
+ case 'roots/list':
547
+ response = {
548
+ jsonrpc: '2.0',
549
+ id: request.id,
550
+ result: {
551
+ roots: ROOTS_CONFIG
552
+ }
553
+ };
554
+ break;
555
+
556
+ case 'logging/setLevel':
557
+ const level = request.params?.level;
558
+ if (level && LOG_LEVELS[level.toUpperCase()] !== undefined) {
559
+ currentLogLevel = LOG_LEVELS[level.toUpperCase()];
560
+ log(LOG_LEVELS.INFO, 'Log level updated', { newLevel: level });
561
+ }
562
+ response = {
563
+ jsonrpc: '2.0',
564
+ id: request.id,
565
+ result: {}
566
+ };
567
+ break;
568
+
569
+ case 'sampling/createMessage':
570
+ response = await handleSampling(request);
571
+ break;
572
+
347
573
  default:
348
574
  log(LOG_LEVELS.WARN, 'Unknown method', { method: request.method });
349
575
  throw new Error(`Method "${request.method}" not found`);
@@ -476,6 +702,162 @@ async function handleToolCall(request) {
476
702
  }
477
703
  }
478
704
 
705
+ // Prompt handlers
706
+ async function handlePromptGet(request) {
707
+ const promptName = request.params.name;
708
+ const promptArgs = request.params.arguments || {};
709
+
710
+ try {
711
+ const prompt = PROMPTS_SCHEMA.find(p => p.name === promptName);
712
+ if (!prompt) {
713
+ throw new Error(`Prompt "${promptName}" not found`);
714
+ }
715
+
716
+ let messages = [];
717
+
718
+ switch (promptName) {
719
+ case 'analyze_data':
720
+ const { table, analysis_type = 'summary', field_focus } = promptArgs;
721
+ messages = [
722
+ {
723
+ role: 'user',
724
+ content: {
725
+ type: 'text',
726
+ text: `Please analyze the data in table "${table}".
727
+ Analysis type: ${analysis_type}
728
+ ${field_focus ? `Focus on fields: ${field_focus}` : ''}
729
+
730
+ First, list the tables and their schemas, then retrieve sample records from "${table}"
731
+ and provide insights based on the ${analysis_type} analysis type.`
732
+ }
733
+ }
734
+ ];
735
+ break;
736
+
737
+ case 'create_report':
738
+ const { table: reportTable, report_type = 'summary', time_period } = promptArgs;
739
+ messages = [
740
+ {
741
+ role: 'user',
742
+ content: {
743
+ type: 'text',
744
+ text: `Create a ${report_type} report for table "${reportTable}".
745
+ ${time_period ? `Time period: ${time_period}` : ''}
746
+
747
+ Please gather the table schema and recent records, then generate a comprehensive
748
+ ${report_type} report with key metrics, trends, and actionable insights.`
749
+ }
750
+ }
751
+ ];
752
+ break;
753
+
754
+ case 'data_insights':
755
+ const { tables, insight_type = 'correlations' } = promptArgs;
756
+ messages = [
757
+ {
758
+ role: 'user',
759
+ content: {
760
+ type: 'text',
761
+ text: `Discover ${insight_type} insights across these tables: ${tables}
762
+
763
+ Please examine the data structures and content to identify:
764
+ - ${insight_type} patterns
765
+ - Unexpected relationships
766
+ - Optimization opportunities
767
+ - Data quality insights`
768
+ }
769
+ }
770
+ ];
771
+ break;
772
+
773
+ case 'optimize_workflow':
774
+ const { base_overview, optimization_focus = 'automation' } = promptArgs;
775
+ messages = [
776
+ {
777
+ role: 'user',
778
+ content: {
779
+ type: 'text',
780
+ text: `Analyze the current Airtable setup and suggest ${optimization_focus} optimizations.
781
+ ${base_overview ? `Base overview: ${base_overview}` : ''}
782
+
783
+ Please review the table structures, field types, and relationships to recommend:
784
+ - ${optimization_focus} improvements
785
+ - Best practice implementations
786
+ - Performance enhancements
787
+ - Workflow streamlining opportunities`
788
+ }
789
+ }
790
+ ];
791
+ break;
792
+
793
+ default:
794
+ throw new Error(`Unsupported prompt: ${promptName}`);
795
+ }
796
+
797
+ return {
798
+ jsonrpc: '2.0',
799
+ id: request.id,
800
+ result: {
801
+ description: prompt.description,
802
+ messages: messages
803
+ }
804
+ };
805
+
806
+ } catch (error) {
807
+ log(LOG_LEVELS.ERROR, `Prompt ${promptName} failed`, { error: error.message });
808
+
809
+ return {
810
+ jsonrpc: '2.0',
811
+ id: request.id,
812
+ error: {
813
+ code: -32000,
814
+ message: `Error getting prompt ${promptName}: ${error.message}`
815
+ }
816
+ };
817
+ }
818
+ }
819
+
820
+ // Sampling handler
821
+ async function handleSampling(request) {
822
+ const { messages, modelPreferences } = request.params;
823
+
824
+ try {
825
+ // Note: In a real implementation, this would integrate with an LLM API
826
+ // For now, we'll return a structured response indicating sampling capability
827
+
828
+ log(LOG_LEVELS.INFO, 'Sampling request received', {
829
+ messageCount: messages?.length,
830
+ model: modelPreferences?.model
831
+ });
832
+
833
+ return {
834
+ jsonrpc: '2.0',
835
+ id: request.id,
836
+ result: {
837
+ model: modelPreferences?.model || 'claude-3-sonnet',
838
+ role: 'assistant',
839
+ content: {
840
+ type: 'text',
841
+ text: 'Sampling capability is available. This MCP server can request AI assistance for complex data analysis and insights generation. In a full implementation, this would connect to your preferred LLM for intelligent Airtable operations.'
842
+ },
843
+ stopReason: 'end_turn'
844
+ }
845
+ };
846
+
847
+ } catch (error) {
848
+ log(LOG_LEVELS.ERROR, 'Sampling failed', { error: error.message });
849
+
850
+ return {
851
+ jsonrpc: '2.0',
852
+ id: request.id,
853
+ error: {
854
+ code: -32000,
855
+ message: `Sampling error: ${error.message}`
856
+ }
857
+ };
858
+ }
859
+ }
860
+
479
861
  // Server startup
480
862
  const PORT = CONFIG.PORT;
481
863
  const HOST = CONFIG.HOST;
@@ -1,15 +1,6 @@
1
1
  {
2
2
  "mcpServers": {
3
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
4
  "url": "http://localhost:8010/mcp"
14
5
  }
15
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rashidazarang/airtable-mcp",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Airtable MCP server for Claude Desktop - Connect directly to Airtable using natural language",
5
5
  "main": "airtable_simple_production.js",
6
6
  "bin": {