@rashidazarang/airtable-mcp 3.1.0 → 3.2.8

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.
Files changed (106) hide show
  1. package/README.md +209 -334
  2. package/bin/airtable-mcp.js +12 -32
  3. package/dist/typescript/airtable-mcp-server.js +77 -0
  4. package/dist/typescript/airtable-mcp-server.js.map +1 -0
  5. package/dist/typescript/app/airtable-client.js +327 -0
  6. package/dist/typescript/app/airtable-client.js.map +1 -0
  7. package/dist/typescript/app/config.js +151 -0
  8. package/dist/typescript/app/config.js.map +1 -0
  9. package/dist/typescript/app/context.js +3 -0
  10. package/dist/typescript/app/context.js.map +1 -0
  11. package/dist/typescript/app/exceptions.js +85 -0
  12. package/dist/typescript/app/exceptions.js.map +1 -0
  13. package/dist/typescript/app/governance.js +58 -0
  14. package/dist/typescript/app/governance.js.map +1 -0
  15. package/dist/typescript/app/logger.js +47 -0
  16. package/dist/typescript/app/logger.js.map +1 -0
  17. package/dist/typescript/app/rateLimiter.js +37 -0
  18. package/dist/typescript/app/rateLimiter.js.map +1 -0
  19. package/dist/typescript/app/sanitize.js +95 -0
  20. package/dist/typescript/app/sanitize.js.map +1 -0
  21. package/dist/typescript/app/tools/create.js +55 -0
  22. package/dist/typescript/app/tools/create.js.map +1 -0
  23. package/dist/typescript/app/tools/describe.js +190 -0
  24. package/dist/typescript/app/tools/describe.js.map +1 -0
  25. package/dist/typescript/app/tools/handleError.js +205 -0
  26. package/dist/typescript/app/tools/handleError.js.map +1 -0
  27. package/dist/typescript/app/tools/index.js +24 -0
  28. package/dist/typescript/app/tools/index.js.map +1 -0
  29. package/dist/typescript/app/tools/listBases.js +47 -0
  30. package/dist/typescript/app/tools/listBases.js.map +1 -0
  31. package/dist/typescript/app/tools/listExceptions.js +16 -0
  32. package/dist/typescript/app/tools/listExceptions.js.map +1 -0
  33. package/dist/typescript/app/tools/listGovernance.js +15 -0
  34. package/dist/typescript/app/tools/listGovernance.js.map +1 -0
  35. package/dist/typescript/app/tools/query.js +133 -0
  36. package/dist/typescript/app/tools/query.js.map +1 -0
  37. package/dist/typescript/app/tools/response.js +21 -0
  38. package/dist/typescript/app/tools/response.js.map +1 -0
  39. package/dist/typescript/app/tools/update.js +57 -0
  40. package/dist/typescript/app/tools/update.js.map +1 -0
  41. package/dist/typescript/app/tools/upsert.js +66 -0
  42. package/dist/typescript/app/tools/upsert.js.map +1 -0
  43. package/dist/typescript/app/tools/webhooks.js +45 -0
  44. package/dist/typescript/app/tools/webhooks.js.map +1 -0
  45. package/dist/typescript/app/types.js +291 -0
  46. package/dist/typescript/app/types.js.map +1 -0
  47. package/dist/typescript/app/validateApiKey.js +75 -0
  48. package/dist/typescript/app/validateApiKey.js.map +1 -0
  49. package/dist/typescript/errors.js +75 -0
  50. package/dist/typescript/errors.js.map +1 -0
  51. package/dist/typescript/index.js +27 -0
  52. package/dist/typescript/index.js.map +1 -0
  53. package/package.json +49 -31
  54. package/tsconfig.json +10 -4
  55. package/types/typescript/airtable-mcp-server.d.ts +2 -0
  56. package/types/typescript/app/airtable-client.d.ts +50 -0
  57. package/types/typescript/app/config.d.ts +17 -0
  58. package/types/typescript/app/context.d.ts +12 -0
  59. package/types/typescript/app/exceptions.d.ts +12 -0
  60. package/types/typescript/app/governance.d.ts +18 -0
  61. package/types/typescript/app/logger.d.ts +13 -0
  62. package/types/typescript/app/rateLimiter.d.ts +13 -0
  63. package/types/typescript/app/sanitize.d.ts +50 -0
  64. package/types/typescript/app/tools/create.d.ts +3 -0
  65. package/types/typescript/app/tools/describe.d.ts +3 -0
  66. package/types/typescript/app/tools/handleError.d.ts +8 -0
  67. package/types/typescript/app/tools/index.d.ts +3 -0
  68. package/types/typescript/app/tools/listBases.d.ts +13 -0
  69. package/types/typescript/app/tools/listExceptions.d.ts +3 -0
  70. package/types/typescript/app/tools/listGovernance.d.ts +3 -0
  71. package/types/typescript/app/tools/query.d.ts +3 -0
  72. package/types/typescript/app/tools/response.d.ts +20 -0
  73. package/types/typescript/app/tools/update.d.ts +3 -0
  74. package/types/typescript/app/tools/upsert.d.ts +3 -0
  75. package/types/typescript/app/tools/webhooks.d.ts +3 -0
  76. package/types/typescript/app/types.d.ts +318 -0
  77. package/types/typescript/app/validateApiKey.d.ts +25 -0
  78. package/types/typescript/errors.d.ts +57 -0
  79. package/types/typescript/index.d.ts +10 -0
  80. package/types/typescript/prompt-templates.d.ts +5 -0
  81. package/types/typescript/tools-schemas.d.ts +5 -0
  82. package/airtable_simple.js +0 -1561
  83. package/airtable_simple_production.js +0 -1564
  84. package/dist/airtable-mcp-server.js +0 -660
  85. package/dist/airtable-mcp-server.js.map +0 -1
  86. package/dist/test-suite.js +0 -421
  87. package/dist/test-suite.js.map +0 -1
  88. package/examples/airtable-crud-example.js +0 -203
  89. package/examples/building-mcp.md +0 -6666
  90. package/examples/claude_config.json +0 -4
  91. package/examples/claude_simple_config.json +0 -7
  92. package/examples/env-demo.js +0 -172
  93. package/examples/example-tasks-update.json +0 -23
  94. package/examples/example-tasks.json +0 -26
  95. package/examples/example_usage.md +0 -124
  96. package/examples/python_debug_patch.txt +0 -27
  97. package/examples/sample-transform.js +0 -76
  98. package/examples/typescript/advanced-ai-prompts.ts +0 -447
  99. package/examples/typescript/basic-usage.ts +0 -174
  100. package/examples/typescript/claude-desktop-config.json +0 -29
  101. package/examples/windsurf_mcp_config.json +0 -17
  102. package/types/ai-prompts.d.ts +0 -321
  103. package/types/airtable-mcp-server.d.ts +0 -52
  104. package/types/index.d.ts +0 -357
  105. package/types/tools.d.ts +0 -514
  106. /package/types/{test-suite.d.ts → typescript/test-suite.d.ts} +0 -0
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExceptionStore = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ function mapCategory(code) {
6
+ switch (code) {
7
+ case 'RateLimited':
8
+ return 'rate_limit';
9
+ case 'ValidationError':
10
+ return 'validation';
11
+ case 'AuthError':
12
+ return 'auth';
13
+ case 'ConflictError':
14
+ return 'conflict';
15
+ case 'GovernanceError':
16
+ return 'schema_drift';
17
+ default:
18
+ return 'other';
19
+ }
20
+ }
21
+ function mapSeverity(code) {
22
+ switch (code) {
23
+ case 'RateLimited':
24
+ case 'AuthError':
25
+ case 'ConflictError':
26
+ case 'GovernanceError':
27
+ return 'error';
28
+ case 'ValidationError':
29
+ return 'warning';
30
+ default:
31
+ return 'error';
32
+ }
33
+ }
34
+ class ExceptionStore {
35
+ constructor(capacity, logger) {
36
+ this.items = [];
37
+ this.capacity = capacity;
38
+ this.logger = logger.child({ component: 'exception_store' });
39
+ }
40
+ record(error, summary, details, proposedFix) {
41
+ const item = {
42
+ id: (0, node_crypto_1.randomUUID)(),
43
+ timestamp: new Date().toISOString(),
44
+ severity: mapSeverity(error.code),
45
+ category: mapCategory(error.code),
46
+ summary,
47
+ details,
48
+ proposedFix
49
+ };
50
+ this.items.unshift(item);
51
+ if (this.items.length > this.capacity) {
52
+ this.items.pop();
53
+ }
54
+ this.logger.debug('Recorded exception', { code: error.code });
55
+ }
56
+ list(params) {
57
+ const limit = params.limit ?? 100;
58
+ const cursorIndex = this.parseCursor(params.cursor);
59
+ let filtered = this.items;
60
+ if (params.since) {
61
+ filtered = filtered.filter((item) => item.timestamp > params.since);
62
+ }
63
+ if (params.severity) {
64
+ filtered = filtered.filter((item) => item.severity === params.severity);
65
+ }
66
+ const slice = filtered.slice(cursorIndex, cursorIndex + limit);
67
+ const nextCursor = cursorIndex + limit < filtered.length ? String(cursorIndex + limit) : undefined;
68
+ return {
69
+ items: slice,
70
+ cursor: nextCursor
71
+ };
72
+ }
73
+ parseCursor(cursor) {
74
+ if (!cursor) {
75
+ return 0;
76
+ }
77
+ const parsed = Number.parseInt(cursor, 10);
78
+ if (Number.isNaN(parsed) || parsed < 0) {
79
+ return 0;
80
+ }
81
+ return parsed;
82
+ }
83
+ }
84
+ exports.ExceptionStore = ExceptionStore;
85
+ //# sourceMappingURL=exceptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exceptions.js","sourceRoot":"","sources":["../../../src/typescript/app/exceptions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GovernanceService = void 0;
4
+ const errors_1 = require("../errors");
5
+ class GovernanceService {
6
+ constructor(snapshot) {
7
+ this.snapshot = snapshot;
8
+ this.tablesByBase = this.buildTableIndex(snapshot);
9
+ }
10
+ ensureBaseAllowed(baseId) {
11
+ // If allowedBases is empty, allow all bases (user will use list_bases to discover)
12
+ if (this.snapshot.allowedBases.length > 0 && !this.snapshot.allowedBases.includes(baseId)) {
13
+ throw new errors_1.GovernanceError(`Base ${baseId} is not in the allow-list`, {
14
+ context: { baseId, governanceRule: 'allowedBases' }
15
+ });
16
+ }
17
+ }
18
+ ensureOperationAllowed(operation) {
19
+ if (!this.snapshot.allowedOperations.includes(operation)) {
20
+ throw new errors_1.GovernanceError(`Operation ${operation} is not permitted`, {
21
+ context: { governanceRule: 'allowedOperations' }
22
+ });
23
+ }
24
+ }
25
+ ensureTableAllowed(baseId, table) {
26
+ if (!this.isTableAllowed(baseId, table)) {
27
+ throw new errors_1.GovernanceError(`Table ${table} is not allowed in base ${baseId}`, {
28
+ context: { baseId, table, governanceRule: 'allowedTables' }
29
+ });
30
+ }
31
+ }
32
+ listPiiPolicies(baseId, table) {
33
+ return this.snapshot.piiFields
34
+ ?.filter((field) => field.baseId === baseId && field.table === table)
35
+ .map((field) => ({ field: field.field, policy: field.policy })) ?? [];
36
+ }
37
+ getSnapshot() {
38
+ return this.snapshot;
39
+ }
40
+ isTableAllowed(baseId, table) {
41
+ const allowedTables = this.tablesByBase.get(baseId);
42
+ if (!allowedTables || allowedTables.size === 0) {
43
+ return true;
44
+ }
45
+ return allowedTables.has(table);
46
+ }
47
+ buildTableIndex(snapshot) {
48
+ const map = new Map();
49
+ for (const item of snapshot.allowedTables ?? []) {
50
+ const baseTables = map.get(item.baseId) ?? new Set();
51
+ baseTables.add(item.table);
52
+ map.set(item.baseId, baseTables);
53
+ }
54
+ return map;
55
+ }
56
+ }
57
+ exports.GovernanceService = GovernanceService;
58
+ //# sourceMappingURL=governance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"governance.js","sourceRoot":"","sources":["../../../src/typescript/app/governance.ts"],"names":[],"mappings":";;;AACA,sCAA4C;AAI5C,MAAa,iBAAiB;IAI5B,YAAY,QAA4B;QACtC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED,iBAAiB,CAAC,MAAc;QAC9B,mFAAmF;QACnF,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1F,MAAM,IAAI,wBAAe,CAAC,QAAQ,MAAM,2BAA2B,EAAE;gBACnE,OAAO,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE;aACpD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,sBAAsB,CAAC,SAAoB;QACzC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,wBAAe,CAAC,aAAa,SAAS,mBAAmB,EAAE;gBACnE,OAAO,EAAE,EAAE,cAAc,EAAE,mBAAmB,EAAE;aACjD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,kBAAkB,CAAC,MAAc,EAAE,KAAa;QAC9C,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,wBAAe,CAAC,SAAS,KAAK,2BAA2B,MAAM,EAAE,EAAE;gBAC3E,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE;aAC5D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,eAAe,CAAC,MAAc,EAAE,KAAa;QAC3C,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS;YAC5B,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC;aACpE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1E,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,cAAc,CAAC,MAAc,EAAE,KAAa;QAC1C,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpD,IAAI,CAAC,aAAa,IAAI,aAAa,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAC/C,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAEO,eAAe,CAAC,QAA4B;QAClD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAuB,CAAC;QAC3C,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;YAChD,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,EAAU,CAAC;YAC7D,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3B,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF;AA7DD,8CA6DC"}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = void 0;
4
+ const LEVEL_ORDER = {
5
+ error: 0,
6
+ warn: 1,
7
+ info: 2,
8
+ debug: 3
9
+ };
10
+ class Logger {
11
+ constructor(level, context = {}) {
12
+ this.level = level;
13
+ this.context = context;
14
+ }
15
+ child(context) {
16
+ return new Logger(this.level, { ...this.context, ...context });
17
+ }
18
+ error(message, metadata = {}) {
19
+ this.log('error', message, metadata);
20
+ }
21
+ warn(message, metadata = {}) {
22
+ this.log('warn', message, metadata);
23
+ }
24
+ info(message, metadata = {}) {
25
+ this.log('info', message, metadata);
26
+ }
27
+ debug(message, metadata = {}) {
28
+ this.log('debug', message, metadata);
29
+ }
30
+ log(level, message, metadata) {
31
+ if (LEVEL_ORDER[level] > LEVEL_ORDER[this.level]) {
32
+ return;
33
+ }
34
+ const timestamp = new Date().toISOString();
35
+ const output = {
36
+ timestamp,
37
+ level,
38
+ message,
39
+ ...this.context,
40
+ ...(Object.keys(metadata).length > 0 ? { metadata } : {})
41
+ };
42
+ // Write logs to stderr so we don't corrupt the MCP stdio protocol stream.
43
+ process.stderr.write(`${JSON.stringify(output)}\n`);
44
+ }
45
+ }
46
+ exports.Logger = Logger;
47
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/typescript/app/logger.ts"],"names":[],"mappings":";;;AAEA,MAAM,WAAW,GAA6B;IAC5C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAIF,MAAa,MAAM;IAIjB,YAAY,KAAe,EAAE,UAAuB,EAAE;QACpD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,OAAoB;QACxB,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,WAAwB,EAAE;QAC/C,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,WAAwB,EAAE;QAC9C,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,WAAwB,EAAE;QAC9C,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,WAAwB,EAAE;QAC/C,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,QAAqB;QACjE,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACjD,OAAO;QACT,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,MAAM,GAAG;YACb,SAAS;YACT,KAAK;YACL,OAAO;YACP,GAAG,IAAI,CAAC,OAAO;YACf,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1D,CAAC;QAEF,0EAA0E;QAC1E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC;CACF;AA9CD,wBA8CC"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RateLimiter = void 0;
4
+ const promises_1 = require("node:timers/promises");
5
+ /**
6
+ * Lightweight token-based rate limiter to enforce Airtable quotas.
7
+ * Maintains per-key queues to preserve ordering and fairness.
8
+ */
9
+ class RateLimiter {
10
+ constructor({ maxRequestsPerSecond }) {
11
+ this.lockByKey = new Map();
12
+ this.nextAvailableByKey = new Map();
13
+ if (maxRequestsPerSecond <= 0) {
14
+ throw new Error('maxRequestsPerSecond must be greater than zero');
15
+ }
16
+ this.minIntervalMs = Math.ceil(1000 / maxRequestsPerSecond);
17
+ }
18
+ async schedule(key) {
19
+ const previous = this.lockByKey.get(key) ?? Promise.resolve();
20
+ let release = () => undefined;
21
+ const current = new Promise((resolve) => {
22
+ release = resolve;
23
+ });
24
+ this.lockByKey.set(key, previous.then(() => current));
25
+ await previous;
26
+ const now = Date.now();
27
+ const availableAt = this.nextAvailableByKey.get(key) ?? now;
28
+ const waitMs = Math.max(availableAt - now, 0);
29
+ if (waitMs > 0) {
30
+ await (0, promises_1.setTimeout)(waitMs);
31
+ }
32
+ this.nextAvailableByKey.set(key, Date.now() + this.minIntervalMs);
33
+ release();
34
+ }
35
+ }
36
+ exports.RateLimiter = RateLimiter;
37
+ //# sourceMappingURL=rateLimiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimiter.js","sourceRoot":"","sources":["../../../src/typescript/app/rateLimiter.ts"],"names":[],"mappings":";;;AAAA,mDAA2D;AAE3D;;;GAGG;AACH,MAAa,WAAW;IAKtB,YAAY,EAAE,oBAAoB,EAAoC;QAHrD,cAAS,GAAG,IAAI,GAAG,EAAyB,CAAC;QAC7C,uBAAkB,GAAG,IAAI,GAAG,EAAkB,CAAC;QAG9D,IAAI,oBAAoB,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,GAAW;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAC9D,IAAI,OAAO,GAAe,GAAG,EAAE,CAAC,SAAS,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAC5C,OAAO,GAAG,OAAO,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,SAAS,CAAC,GAAG,CAChB,GAAG,EACH,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAC7B,CAAC;QAEF,MAAM,QAAQ,CAAC;QAEf,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;QAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;QAC9C,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,MAAM,IAAA,qBAAK,EAAC,MAAM,CAAC,CAAC;QACtB,CAAC;QAED,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;QAClE,OAAO,EAAE,CAAC;IACZ,CAAC;CACF;AApCD,kCAoCC"}
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Sanitization utilities for Airtable formula strings.
4
+ *
5
+ * Prevents formula injection attacks by escaping user-provided
6
+ * strings before they're interpolated into Airtable formulas.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.escapeFormulaString = escapeFormulaString;
10
+ exports.validateFormula = validateFormula;
11
+ exports.buildSafeFindFormula = buildSafeFindFormula;
12
+ exports.buildSafeEqualityFormula = buildSafeEqualityFormula;
13
+ /**
14
+ * Escapes a string for safe use within Airtable formula string literals.
15
+ *
16
+ * Airtable formulas use double quotes for strings. This function escapes:
17
+ * - Double quotes (") -> \"
18
+ * - Backslashes (\) -> \\
19
+ *
20
+ * @example
21
+ * // Safe interpolation in formula:
22
+ * const formula = `Name = "${escapeFormulaString(userInput)}"`;
23
+ *
24
+ * @param input - The user-provided string to escape
25
+ * @returns The escaped string safe for use in formulas
26
+ */
27
+ function escapeFormulaString(input) {
28
+ if (!input)
29
+ return '';
30
+ // Escape backslashes first, then quotes
31
+ return input
32
+ .replace(/\\/g, '\\\\')
33
+ .replace(/"/g, '\\"');
34
+ }
35
+ /**
36
+ * Validates that a formula string doesn't contain obvious injection attempts.
37
+ *
38
+ * This is a heuristic check - not foolproof, but catches common patterns.
39
+ * Should be used in addition to escapeFormulaString, not as a replacement.
40
+ *
41
+ * @param formula - The complete formula string to validate
42
+ * @returns Object with isValid flag and optional warning message
43
+ */
44
+ function validateFormula(formula) {
45
+ if (!formula) {
46
+ return { isValid: true };
47
+ }
48
+ // Check for unbalanced quotes (simple heuristic)
49
+ const quoteCount = (formula.match(/(?<!\\)"/g) || []).length;
50
+ if (quoteCount % 2 !== 0) {
51
+ return {
52
+ isValid: false,
53
+ warning: 'Formula contains unbalanced quotes'
54
+ };
55
+ }
56
+ // Check for suspicious patterns that might indicate injection
57
+ const suspiciousPatterns = [
58
+ /"\s*\)\s*,/, // Closing quote followed by ) and comma - potential function breakout
59
+ /"\s*&\s*"/, // String concatenation that might be injection
60
+ ];
61
+ for (const pattern of suspiciousPatterns) {
62
+ if (pattern.test(formula)) {
63
+ return {
64
+ isValid: false,
65
+ warning: 'Formula contains suspicious patterns'
66
+ };
67
+ }
68
+ }
69
+ return { isValid: true };
70
+ }
71
+ /**
72
+ * Builds a safe FIND formula for searching text in a field.
73
+ *
74
+ * @param searchTerm - The user-provided search term
75
+ * @param fieldName - The field name to search in
76
+ * @returns A safe FIND formula string
77
+ */
78
+ function buildSafeFindFormula(searchTerm, fieldName) {
79
+ const escapedTerm = escapeFormulaString(searchTerm);
80
+ const escapedField = escapeFormulaString(fieldName);
81
+ return `FIND("${escapedTerm}", {${escapedField}})`;
82
+ }
83
+ /**
84
+ * Builds a safe equality comparison formula.
85
+ *
86
+ * @param fieldName - The field name to compare
87
+ * @param value - The user-provided value to compare against
88
+ * @returns A safe equality formula string
89
+ */
90
+ function buildSafeEqualityFormula(fieldName, value) {
91
+ const escapedField = escapeFormulaString(fieldName);
92
+ const escapedValue = escapeFormulaString(value);
93
+ return `{${escapedField}} = "${escapedValue}"`;
94
+ }
95
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../../src/typescript/app/sanitize.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAgBH,kDAOC;AAWD,0CA8BC;AASD,oDAIC;AASD,4DAIC;AAxFD;;;;;;;;;;;;;GAaG;AACH,SAAgB,mBAAmB,CAAC,KAAa;IAC/C,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IAEtB,wCAAwC;IACxC,OAAO,KAAK;SACT,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,eAAe,CAAC,OAAe;IAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED,iDAAiD;IACjD,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IAC7D,IAAI,UAAU,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,oCAAoC;SAC9C,CAAC;IACJ,CAAC;IAED,8DAA8D;IAC9D,MAAM,kBAAkB,GAAG;QACzB,YAAY,EAAG,sEAAsE;QACrF,WAAW,EAAI,+CAA+C;KAC/D,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,kBAAkB,EAAE,CAAC;QACzC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,sCAAsC;aAChD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAAC,UAAkB,EAAE,SAAiB;IACxE,MAAM,WAAW,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACpD,OAAO,SAAS,WAAW,OAAO,YAAY,IAAI,CAAC;AACrD,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,wBAAwB,CAAC,SAAiB,EAAE,KAAa;IACvE,MAAM,YAAY,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAChD,OAAO,IAAI,YAAY,QAAQ,YAAY,GAAG,CAAC;AACjD,CAAC"}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerCreateTool = registerCreateTool;
4
+ const types_1 = require("../types");
5
+ const handleError_1 = require("./handleError");
6
+ const response_1 = require("./response");
7
+ function chunk(arr, size) {
8
+ const out = [];
9
+ for (let i = 0; i < arr.length; i += size)
10
+ out.push(arr.slice(i, i + size));
11
+ return out;
12
+ }
13
+ function registerCreateTool(server, ctx) {
14
+ server.registerTool('create', {
15
+ description: 'Create Airtable records (requires diff-before-write via dryRun first).',
16
+ inputSchema: types_1.createInputSchema.shape,
17
+ outputSchema: types_1.createOutputSchema.shape
18
+ }, async (raw) => {
19
+ try {
20
+ const args = types_1.createInputSchema.parse(raw);
21
+ ctx.governance.ensureOperationAllowed('create');
22
+ ctx.governance.ensureBaseAllowed(args.baseId);
23
+ ctx.governance.ensureTableAllowed(args.baseId, args.table);
24
+ const logger = ctx.logger.child({ tool: 'create', baseId: args.baseId, table: args.table });
25
+ if (args.dryRun) {
26
+ const structuredContent = {
27
+ diff: { added: args.records.length, updated: 0, unchanged: 0 },
28
+ dryRun: true,
29
+ records: args.records.map((r) => ({ id: 'pending', fields: r.fields }))
30
+ };
31
+ return (0, response_1.createToolResponse)(structuredContent);
32
+ }
33
+ const chunks = chunk(args.records, 10);
34
+ const aggregated = [];
35
+ for (let i = 0; i < chunks.length; i++) {
36
+ const body = { records: chunks[i], typecast: args.typecast ?? false };
37
+ const headerKey = args.idempotencyKey ? `${args.idempotencyKey}:${i}` : undefined;
38
+ const response = await ctx.airtable.createRecords(args.baseId, args.table, body, headerKey);
39
+ if (Array.isArray(response?.records))
40
+ aggregated.push(...response.records);
41
+ }
42
+ const structuredContent = {
43
+ diff: { added: aggregated.length, updated: 0, unchanged: 0 },
44
+ records: aggregated.map((r) => ({ id: String(r.id), fields: r.fields || {} })),
45
+ dryRun: false
46
+ };
47
+ logger.info('Create completed', { added: aggregated.length });
48
+ return (0, response_1.createToolResponse)(structuredContent);
49
+ }
50
+ catch (error) {
51
+ return (0, handleError_1.handleToolError)('create', error, ctx);
52
+ }
53
+ });
54
+ }
55
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../../../../src/typescript/app/tools/create.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerDescribeTool = registerDescribeTool;
4
+ const types_1 = require("../types");
5
+ const errors_1 = require("../../errors");
6
+ const handleError_1 = require("./handleError");
7
+ const response_1 = require("./response");
8
+ /**
9
+ * Normalize field based on detail level:
10
+ * - identifiersOnly: only id and name
11
+ * - full: all details including type, description, options
12
+ */
13
+ function normalizeField(raw, detailLevel) {
14
+ const source = raw;
15
+ const field = {
16
+ id: String(source?.id ?? ''),
17
+ name: String(source?.name ?? ''),
18
+ type: detailLevel === 'full' ? String(source?.type ?? '') : ''
19
+ };
20
+ // Only include full details in 'full' mode
21
+ if (detailLevel === 'full') {
22
+ if (source?.description && typeof source.description === 'string') {
23
+ field.description = source.description;
24
+ }
25
+ if (source?.options && typeof source.options === 'object') {
26
+ field.options = source.options;
27
+ }
28
+ }
29
+ // Remove empty type for identifiersOnly
30
+ if (detailLevel === 'identifiersOnly') {
31
+ delete field.type;
32
+ }
33
+ return field;
34
+ }
35
+ /**
36
+ * Normalize view based on detail level:
37
+ * - identifiersOnly: only id and name
38
+ * - full: includes type
39
+ */
40
+ function normalizeView(raw, detailLevel) {
41
+ const source = raw;
42
+ const view = {
43
+ id: String(source?.id ?? ''),
44
+ name: String(source?.name ?? '')
45
+ };
46
+ if (detailLevel === 'full' && source?.type && typeof source.type === 'string') {
47
+ view.type = source.type;
48
+ }
49
+ return view;
50
+ }
51
+ /**
52
+ * Normalize table based on detail level:
53
+ * - tableIdentifiersOnly: only id and name
54
+ * - identifiersOnly: id, name, and field/view identifiers
55
+ * - full: complete details
56
+ */
57
+ function normalizeTable(raw, options) {
58
+ const { detailLevel, includeFields, includeViews } = options;
59
+ const source = raw;
60
+ const table = {
61
+ id: String(source?.id ?? ''),
62
+ name: String(source?.name ?? '')
63
+ };
64
+ // tableIdentifiersOnly: stop here
65
+ if (detailLevel === 'tableIdentifiersOnly') {
66
+ return table;
67
+ }
68
+ // identifiersOnly and full: include primaryFieldId
69
+ if (source?.primaryFieldId && typeof source.primaryFieldId === 'string') {
70
+ table.primaryFieldId = source.primaryFieldId;
71
+ }
72
+ // Include fields based on settings
73
+ if (includeFields && Array.isArray(source?.fields)) {
74
+ table.fields = source.fields.map((field) => normalizeField(field, detailLevel));
75
+ }
76
+ // Include views based on settings
77
+ if (includeViews && Array.isArray(source?.views)) {
78
+ table.views = source.views.map((view) => normalizeView(view, detailLevel));
79
+ }
80
+ return table;
81
+ }
82
+ function registerDescribeTool(server, ctx) {
83
+ server.registerTool('describe', {
84
+ description: `Describe Airtable base or table schema.
85
+
86
+ Use detailLevel to optimize context usage:
87
+ - tableIdentifiersOnly: Only table IDs and names (minimal)
88
+ - identifiersOnly: Table, field, and view IDs and names
89
+ - full: Complete details including field types and options (default)`,
90
+ inputSchema: types_1.describeInputShape,
91
+ outputSchema: types_1.describeOutputSchema.shape
92
+ }, async (args, _extra) => {
93
+ try {
94
+ const input = types_1.describeInputSchema.parse(args);
95
+ ctx.governance.ensureOperationAllowed('describe');
96
+ ctx.governance.ensureBaseAllowed(input.baseId);
97
+ // Determine detail level and field/view inclusion
98
+ const detailLevel = input.detailLevel ?? 'full';
99
+ // For backward compatibility, respect includeFields/includeViews
100
+ // but detailLevel takes precedence for tableIdentifiersOnly
101
+ const includeFields = detailLevel !== 'tableIdentifiersOnly' && (input.includeFields ?? true);
102
+ const includeViews = detailLevel !== 'tableIdentifiersOnly' && (input.includeViews ?? false);
103
+ const normalizeOptions = {
104
+ detailLevel,
105
+ includeFields,
106
+ includeViews
107
+ };
108
+ const logger = ctx.logger.child({
109
+ tool: 'describe',
110
+ baseId: input.baseId,
111
+ scope: input.scope,
112
+ detailLevel
113
+ });
114
+ const [baseInfo, tableInfo] = await Promise.all([
115
+ ctx.airtable.getBase(input.baseId),
116
+ ctx.airtable.listTables(input.baseId)
117
+ ]);
118
+ const baseName = typeof baseInfo?.name === 'string'
119
+ ? String(baseInfo.name)
120
+ : input.baseId;
121
+ const rawTables = Array.isArray(tableInfo?.tables)
122
+ ? tableInfo.tables
123
+ : [];
124
+ const tables = rawTables
125
+ .filter((rawTable) => {
126
+ const record = rawTable;
127
+ const tableId = typeof record.id === 'string' ? record.id : '';
128
+ const tableName = typeof record.name === 'string' ? record.name : '';
129
+ const idAllowed = tableId
130
+ ? ctx.governance.isTableAllowed(input.baseId, tableId)
131
+ : false;
132
+ const nameAllowed = tableName
133
+ ? ctx.governance.isTableAllowed(input.baseId, tableName)
134
+ : false;
135
+ return idAllowed || nameAllowed;
136
+ })
137
+ .map((table) => normalizeTable(table, normalizeOptions));
138
+ let selectedTables = tables;
139
+ if (input.scope === 'table') {
140
+ const target = tables.find((tableRecord) => String(tableRecord.id) === input.table ||
141
+ String(tableRecord.name).toLowerCase() === input.table?.toLowerCase());
142
+ if (!target) {
143
+ const context = { baseId: input.baseId };
144
+ if (input.table) {
145
+ context.table = input.table;
146
+ }
147
+ throw new errors_1.NotFoundError(`Table ${input.table} not found in base ${input.baseId}`, {
148
+ context
149
+ });
150
+ }
151
+ const targetId = String(target.id);
152
+ const targetName = String(target.name);
153
+ if (!ctx.governance.isTableAllowed(input.baseId, targetId) &&
154
+ !ctx.governance.isTableAllowed(input.baseId, targetName)) {
155
+ const context = { baseId: input.baseId };
156
+ if (input.table) {
157
+ context.table = input.table;
158
+ }
159
+ throw new errors_1.GovernanceError(`Table ${input.table} is not allowed in base ${input.baseId}`, {
160
+ context
161
+ });
162
+ }
163
+ selectedTables = [target];
164
+ }
165
+ const structuredContent = {
166
+ base: {
167
+ id: input.baseId,
168
+ name: baseName
169
+ },
170
+ tables: selectedTables
171
+ };
172
+ if (input.scope === 'base' && includeViews) {
173
+ structuredContent.views = rawTables
174
+ .flatMap((table) => {
175
+ const record = table;
176
+ return Array.isArray(record.views) ? record.views : [];
177
+ })
178
+ .map((view) => normalizeView(view, detailLevel));
179
+ }
180
+ logger.debug('Describe completed', {
181
+ tableCount: selectedTables.length
182
+ });
183
+ return (0, response_1.createToolResponse)(structuredContent);
184
+ }
185
+ catch (error) {
186
+ return (0, handleError_1.handleToolError)('describe', error, ctx);
187
+ }
188
+ });
189
+ }
190
+ //# sourceMappingURL=describe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe.js","sourceRoot":"","sources":["../../../../src/typescript/app/tools/describe.ts"],"names":[],"mappings":"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"}