@striderlabs/mcp-airtable 1.0.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 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListToolsRequestSchema
9
+ } from "@modelcontextprotocol/sdk/types.js";
10
+ import { z } from "zod";
11
+ var API_KEY = process.env.AIRTABLE_API_KEY;
12
+ var BASE_URL = "https://api.airtable.com/v0";
13
+ var META_URL = "https://api.airtable.com/v0/meta";
14
+ async function airtableRequest(method, url, body) {
15
+ const headers = {
16
+ "Content-Type": "application/json"
17
+ };
18
+ if (API_KEY) {
19
+ headers["Authorization"] = `Bearer ${API_KEY}`;
20
+ }
21
+ const res = await fetch(url, {
22
+ method,
23
+ headers,
24
+ body: body ? JSON.stringify(body) : void 0
25
+ });
26
+ const data = await res.json();
27
+ if (!res.ok) {
28
+ throw new Error(`Airtable API error ${res.status}: ${JSON.stringify(data)}`);
29
+ }
30
+ return data;
31
+ }
32
+ var server = new Server(
33
+ { name: "mcp-airtable", version: "1.0.0" },
34
+ { capabilities: { tools: {} } }
35
+ );
36
+ server.setRequestHandler(ListToolsRequestSchema, async (_request, _extra) => ({
37
+ tools: [
38
+ {
39
+ name: "list_bases",
40
+ description: "List all accessible Airtable bases",
41
+ inputSchema: {
42
+ type: "object",
43
+ properties: {
44
+ offset: { type: "string", description: "Pagination offset" }
45
+ }
46
+ }
47
+ },
48
+ {
49
+ name: "list_records",
50
+ description: "List records from an Airtable table",
51
+ inputSchema: {
52
+ type: "object",
53
+ properties: {
54
+ base_id: { type: "string", description: "The base ID" },
55
+ table_name: { type: "string", description: "Table name or ID" },
56
+ max_records: { type: "number", description: "Max records to return" },
57
+ view: { type: "string", description: "View name or ID" },
58
+ filter_formula: { type: "string", description: "Airtable formula for filtering" },
59
+ sort: { type: "array", description: "Sort array [{field, direction}]" },
60
+ fields: { type: "array", items: { type: "string" }, description: "Fields to include" },
61
+ offset: { type: "string", description: "Pagination offset" }
62
+ },
63
+ required: ["base_id", "table_name"]
64
+ }
65
+ },
66
+ {
67
+ name: "get_record",
68
+ description: "Get a specific record from Airtable",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ base_id: { type: "string", description: "The base ID" },
73
+ table_name: { type: "string", description: "Table name or ID" },
74
+ record_id: { type: "string", description: "The record ID" }
75
+ },
76
+ required: ["base_id", "table_name", "record_id"]
77
+ }
78
+ },
79
+ {
80
+ name: "create_record",
81
+ description: "Create a new record in Airtable",
82
+ inputSchema: {
83
+ type: "object",
84
+ properties: {
85
+ base_id: { type: "string", description: "The base ID" },
86
+ table_name: { type: "string", description: "Table name or ID" },
87
+ fields: { type: "object", description: "Record field values" }
88
+ },
89
+ required: ["base_id", "table_name", "fields"]
90
+ }
91
+ },
92
+ {
93
+ name: "update_record",
94
+ description: "Update a record in Airtable",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ base_id: { type: "string", description: "The base ID" },
99
+ table_name: { type: "string", description: "Table name or ID" },
100
+ record_id: { type: "string", description: "The record ID" },
101
+ fields: { type: "object", description: "Fields to update" },
102
+ replace: { type: "boolean", description: "If true, replace all fields (PUT); otherwise patch (PATCH)" }
103
+ },
104
+ required: ["base_id", "table_name", "record_id", "fields"]
105
+ }
106
+ },
107
+ {
108
+ name: "delete_record",
109
+ description: "Delete a record from Airtable",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ base_id: { type: "string", description: "The base ID" },
114
+ table_name: { type: "string", description: "Table name or ID" },
115
+ record_id: { type: "string", description: "The record ID" }
116
+ },
117
+ required: ["base_id", "table_name", "record_id"]
118
+ }
119
+ }
120
+ ]
121
+ }));
122
+ var ListBasesSchema = z.object({
123
+ offset: z.string().optional()
124
+ });
125
+ var ListRecordsSchema = z.object({
126
+ base_id: z.string(),
127
+ table_name: z.string(),
128
+ max_records: z.number().optional(),
129
+ view: z.string().optional(),
130
+ filter_formula: z.string().optional(),
131
+ sort: z.array(z.unknown()).optional(),
132
+ fields: z.array(z.string()).optional(),
133
+ offset: z.string().optional()
134
+ });
135
+ var GetRecordSchema = z.object({
136
+ base_id: z.string(),
137
+ table_name: z.string(),
138
+ record_id: z.string()
139
+ });
140
+ var CreateRecordSchema = z.object({
141
+ base_id: z.string(),
142
+ table_name: z.string(),
143
+ fields: z.record(z.string(), z.unknown())
144
+ });
145
+ var UpdateRecordSchema = z.object({
146
+ base_id: z.string(),
147
+ table_name: z.string(),
148
+ record_id: z.string(),
149
+ fields: z.record(z.string(), z.unknown()),
150
+ replace: z.boolean().optional()
151
+ });
152
+ var DeleteRecordSchema = z.object({
153
+ base_id: z.string(),
154
+ table_name: z.string(),
155
+ record_id: z.string()
156
+ });
157
+ server.setRequestHandler(CallToolRequestSchema, async (request, _extra) => {
158
+ const { name, arguments: args } = request.params;
159
+ try {
160
+ switch (name) {
161
+ case "list_bases": {
162
+ const { offset } = ListBasesSchema.parse(args);
163
+ const qs = offset ? `?offset=${offset}` : "";
164
+ const result = await airtableRequest("GET", `${META_URL}/bases${qs}`);
165
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
166
+ }
167
+ case "list_records": {
168
+ const params = ListRecordsSchema.parse(args);
169
+ const qs = new URLSearchParams();
170
+ if (params.max_records) qs.set("maxRecords", String(params.max_records));
171
+ if (params.view) qs.set("view", params.view);
172
+ if (params.filter_formula) qs.set("filterByFormula", params.filter_formula);
173
+ if (params.offset) qs.set("offset", params.offset);
174
+ if (params.fields) {
175
+ params.fields.forEach((f) => qs.append("fields[]", f));
176
+ }
177
+ if (params.sort) {
178
+ params.sort.forEach((s, i) => {
179
+ qs.set(`sort[${i}][field]`, s.field);
180
+ if (s.direction) qs.set(`sort[${i}][direction]`, s.direction);
181
+ });
182
+ }
183
+ const url = `${BASE_URL}/${params.base_id}/${encodeURIComponent(params.table_name)}?${qs}`;
184
+ const result = await airtableRequest("GET", url);
185
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
186
+ }
187
+ case "get_record": {
188
+ const { base_id, table_name, record_id } = GetRecordSchema.parse(args);
189
+ const url = `${BASE_URL}/${base_id}/${encodeURIComponent(table_name)}/${record_id}`;
190
+ const result = await airtableRequest("GET", url);
191
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
192
+ }
193
+ case "create_record": {
194
+ const { base_id, table_name, fields } = CreateRecordSchema.parse(args);
195
+ const url = `${BASE_URL}/${base_id}/${encodeURIComponent(table_name)}`;
196
+ const result = await airtableRequest("POST", url, { fields });
197
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
198
+ }
199
+ case "update_record": {
200
+ const { base_id, table_name, record_id, fields, replace } = UpdateRecordSchema.parse(args);
201
+ const url = `${BASE_URL}/${base_id}/${encodeURIComponent(table_name)}/${record_id}`;
202
+ const method = replace ? "PUT" : "PATCH";
203
+ const result = await airtableRequest(method, url, { fields });
204
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
205
+ }
206
+ case "delete_record": {
207
+ const { base_id, table_name, record_id } = DeleteRecordSchema.parse(args);
208
+ const url = `${BASE_URL}/${base_id}/${encodeURIComponent(table_name)}/${record_id}`;
209
+ const result = await airtableRequest("DELETE", url);
210
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
211
+ }
212
+ default:
213
+ throw new Error(`Unknown tool: ${name}`);
214
+ }
215
+ } catch (error) {
216
+ return {
217
+ content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
218
+ isError: true
219
+ };
220
+ }
221
+ });
222
+ var transport = new StdioServerTransport();
223
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@striderlabs/mcp-airtable",
3
+ "version": "1.0.0",
4
+ "description": "MCP connector for Airtable API",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "mcp-airtable": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsup src/index.ts --format esm --dts",
12
+ "dev": "tsup src/index.ts --format esm --watch"
13
+ },
14
+ "files": ["dist"],
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "latest",
17
+ "zod": "latest"
18
+ },
19
+ "devDependencies": {
20
+ "tsup": "latest",
21
+ "typescript": "latest",
22
+ "@types/node": "latest"
23
+ }
24
+ }