adoptai-mcp 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.
Files changed (174) hide show
  1. package/README.md +70 -0
  2. package/bin/adoptai-mcp.js +2 -0
  3. package/dist/apps/canva.js +1 -0
  4. package/dist/apps/figma.js +1 -0
  5. package/dist/apps/github.js +2 -0
  6. package/dist/apps/notion.js +1 -0
  7. package/dist/apps/registry.js +20 -0
  8. package/dist/apps/salesforce.js +1 -0
  9. package/dist/cli/add.js +532 -0
  10. package/dist/cli/index.js +39 -0
  11. package/dist/cli/list.js +19 -0
  12. package/dist/cli/remove.js +37 -0
  13. package/dist/cli/serve.js +27 -0
  14. package/dist/cli/status.js +24 -0
  15. package/dist/config/clients.js +118 -0
  16. package/dist/config/credentials.js +34 -0
  17. package/dist/core/auth-manager.js +237 -0
  18. package/dist/core/config-writer.js +161 -0
  19. package/dist/core/doctor.js +199 -0
  20. package/dist/core/package.json +3 -0
  21. package/dist/core/server-base.js +81 -0
  22. package/dist/integrations/canva/.env +3 -0
  23. package/dist/integrations/canva/auth.js +287 -0
  24. package/dist/integrations/canva/env.js +9 -0
  25. package/dist/integrations/canva/index.js +12 -0
  26. package/dist/integrations/canva/package.json +31 -0
  27. package/dist/integrations/canva/publish-to-adoptai.js +365 -0
  28. package/dist/integrations/canva/setup.js +90 -0
  29. package/dist/integrations/canva/tools.js +1315 -0
  30. package/dist/integrations/canva/tools.original.js +1315 -0
  31. package/dist/integrations/figma/auth.js +48 -0
  32. package/dist/integrations/figma/index.js +11 -0
  33. package/dist/integrations/figma/package.json +27 -0
  34. package/dist/integrations/figma/publish-to-adoptai.js +384 -0
  35. package/dist/integrations/figma/setup.js +90 -0
  36. package/dist/integrations/figma/tools.js +1137 -0
  37. package/dist/integrations/github/auth.js +53 -0
  38. package/dist/integrations/github/index.js +11 -0
  39. package/dist/integrations/github/package.json +28 -0
  40. package/dist/integrations/github/publish-to-adoptai.js +240 -0
  41. package/dist/integrations/github/setup.js +103 -0
  42. package/dist/integrations/github/tools.js +78 -0
  43. package/dist/integrations/github-actions/auth.js +53 -0
  44. package/dist/integrations/github-actions/index.js +11 -0
  45. package/dist/integrations/github-actions/package.json +27 -0
  46. package/dist/integrations/github-actions/setup.js +103 -0
  47. package/dist/integrations/github-actions/tools.js +5642 -0
  48. package/dist/integrations/github-activity/auth.js +53 -0
  49. package/dist/integrations/github-activity/index.js +11 -0
  50. package/dist/integrations/github-activity/package.json +27 -0
  51. package/dist/integrations/github-activity/setup.js +103 -0
  52. package/dist/integrations/github-activity/tools.js +925 -0
  53. package/dist/integrations/github-apps/auth.js +53 -0
  54. package/dist/integrations/github-apps/index.js +11 -0
  55. package/dist/integrations/github-apps/package.json +27 -0
  56. package/dist/integrations/github-apps/setup.js +103 -0
  57. package/dist/integrations/github-apps/tools.js +791 -0
  58. package/dist/integrations/github-billing/auth.js +53 -0
  59. package/dist/integrations/github-billing/index.js +11 -0
  60. package/dist/integrations/github-billing/package.json +27 -0
  61. package/dist/integrations/github-billing/setup.js +103 -0
  62. package/dist/integrations/github-billing/tools.js +438 -0
  63. package/dist/integrations/github-checks/auth.js +53 -0
  64. package/dist/integrations/github-checks/index.js +11 -0
  65. package/dist/integrations/github-checks/package.json +27 -0
  66. package/dist/integrations/github-checks/setup.js +103 -0
  67. package/dist/integrations/github-checks/tools.js +607 -0
  68. package/dist/integrations/github-code-scanning/auth.js +53 -0
  69. package/dist/integrations/github-code-scanning/index.js +11 -0
  70. package/dist/integrations/github-code-scanning/package.json +27 -0
  71. package/dist/integrations/github-code-scanning/setup.js +103 -0
  72. package/dist/integrations/github-code-scanning/tools.js +987 -0
  73. package/dist/integrations/github-dependabot/auth.js +53 -0
  74. package/dist/integrations/github-dependabot/index.js +11 -0
  75. package/dist/integrations/github-dependabot/package.json +27 -0
  76. package/dist/integrations/github-dependabot/setup.js +103 -0
  77. package/dist/integrations/github-dependabot/tools.js +915 -0
  78. package/dist/integrations/github-gists/auth.js +53 -0
  79. package/dist/integrations/github-gists/index.js +11 -0
  80. package/dist/integrations/github-gists/package.json +27 -0
  81. package/dist/integrations/github-gists/setup.js +103 -0
  82. package/dist/integrations/github-gists/tools.js +545 -0
  83. package/dist/integrations/github-git/auth.js +53 -0
  84. package/dist/integrations/github-git/index.js +11 -0
  85. package/dist/integrations/github-git/package.json +27 -0
  86. package/dist/integrations/github-git/setup.js +103 -0
  87. package/dist/integrations/github-git/tools.js +513 -0
  88. package/dist/integrations/github-issues/auth.js +53 -0
  89. package/dist/integrations/github-issues/index.js +11 -0
  90. package/dist/integrations/github-issues/package.json +27 -0
  91. package/dist/integrations/github-issues/setup.js +103 -0
  92. package/dist/integrations/github-issues/tools.js +2232 -0
  93. package/dist/integrations/github-orgs/auth.js +53 -0
  94. package/dist/integrations/github-orgs/index.js +11 -0
  95. package/dist/integrations/github-orgs/package.json +27 -0
  96. package/dist/integrations/github-orgs/setup.js +103 -0
  97. package/dist/integrations/github-orgs/tools.js +3512 -0
  98. package/dist/integrations/github-packages/auth.js +53 -0
  99. package/dist/integrations/github-packages/index.js +11 -0
  100. package/dist/integrations/github-packages/package.json +27 -0
  101. package/dist/integrations/github-packages/setup.js +103 -0
  102. package/dist/integrations/github-packages/tools.js +1088 -0
  103. package/dist/integrations/github-pulls/auth.js +53 -0
  104. package/dist/integrations/github-pulls/index.js +11 -0
  105. package/dist/integrations/github-pulls/package.json +27 -0
  106. package/dist/integrations/github-pulls/setup.js +103 -0
  107. package/dist/integrations/github-pulls/tools.js +1252 -0
  108. package/dist/integrations/github-reactions/auth.js +53 -0
  109. package/dist/integrations/github-reactions/index.js +11 -0
  110. package/dist/integrations/github-reactions/package.json +27 -0
  111. package/dist/integrations/github-reactions/setup.js +103 -0
  112. package/dist/integrations/github-reactions/tools.js +706 -0
  113. package/dist/integrations/github-repos/auth.js +53 -0
  114. package/dist/integrations/github-repos/index.js +11 -0
  115. package/dist/integrations/github-repos/package.json +27 -0
  116. package/dist/integrations/github-repos/setup.js +103 -0
  117. package/dist/integrations/github-repos/tools.js +7286 -0
  118. package/dist/integrations/github-search/auth.js +53 -0
  119. package/dist/integrations/github-search/index.js +11 -0
  120. package/dist/integrations/github-search/package.json +27 -0
  121. package/dist/integrations/github-search/setup.js +103 -0
  122. package/dist/integrations/github-search/tools.js +370 -0
  123. package/dist/integrations/github-teams/auth.js +53 -0
  124. package/dist/integrations/github-teams/index.js +11 -0
  125. package/dist/integrations/github-teams/package.json +27 -0
  126. package/dist/integrations/github-teams/setup.js +103 -0
  127. package/dist/integrations/github-teams/tools.js +633 -0
  128. package/dist/integrations/github-users/auth.js +53 -0
  129. package/dist/integrations/github-users/index.js +11 -0
  130. package/dist/integrations/github-users/package.json +27 -0
  131. package/dist/integrations/github-users/setup.js +103 -0
  132. package/dist/integrations/github-users/tools.js +1118 -0
  133. package/dist/integrations/notion/api.js +108 -0
  134. package/dist/integrations/notion/auth.js +59 -0
  135. package/dist/integrations/notion/endpoints.json +630 -0
  136. package/dist/integrations/notion/index.js +11 -0
  137. package/dist/integrations/notion/package.json +33 -0
  138. package/dist/integrations/notion/publish-to-adoptai.js +271 -0
  139. package/dist/integrations/notion/scripts/generate-endpoints.mjs +306 -0
  140. package/dist/integrations/notion/setup.js +89 -0
  141. package/dist/integrations/notion/tools.js +586 -0
  142. package/dist/integrations/notion/tools.original.js +568 -0
  143. package/dist/integrations/salesforce/.env +8 -0
  144. package/dist/integrations/salesforce/.env.example +15 -0
  145. package/dist/integrations/salesforce/auth.js +311 -0
  146. package/dist/integrations/salesforce/endpoints.json +1359 -0
  147. package/dist/integrations/salesforce/env.js +9 -0
  148. package/dist/integrations/salesforce/index.js +12 -0
  149. package/dist/integrations/salesforce/package.json +42 -0
  150. package/dist/integrations/salesforce/publish-smart-specs.js +890 -0
  151. package/dist/integrations/salesforce/publish-to-adoptai.js +386 -0
  152. package/dist/integrations/salesforce/scripts/extract-postman.mjs +222 -0
  153. package/dist/integrations/salesforce/setup.js +112 -0
  154. package/dist/integrations/salesforce/tools.js +4544 -0
  155. package/dist/integrations/salesforce/tools.original.js +4487 -0
  156. package/dist/server/mcp-server.js +50 -0
  157. package/dist/server/tool-loader.js +47 -0
  158. package/dist/specs/figma-api.json +13621 -0
  159. package/dist/specs/split/salesforce-auth.json +3931 -0
  160. package/dist/specs/split/salesforce-bulk-v1.json +1489 -0
  161. package/dist/specs/split/salesforce-bulk-v2.json +1951 -0
  162. package/dist/specs/split/salesforce-composite.json +1246 -0
  163. package/dist/specs/split/salesforce-connect.json +11639 -0
  164. package/dist/specs/split/salesforce-einstein-prediction-service.json +576 -0
  165. package/dist/specs/split/salesforce-event-platform.json +2682 -0
  166. package/dist/specs/split/salesforce-graphql.json +1754 -0
  167. package/dist/specs/split/salesforce-industries.json +4115 -0
  168. package/dist/specs/split/salesforce-metadata.json +555 -0
  169. package/dist/specs/split/salesforce-rest.json +4798 -0
  170. package/dist/specs/split/salesforce-soap.json +210 -0
  171. package/dist/specs/split/salesforce-subscription-management.json +1299 -0
  172. package/dist/specs/split/salesforce-tooling.json +2026 -0
  173. package/dist/specs/split/salesforce-ui.json +7426 -0
  174. package/package.json +47 -0
@@ -0,0 +1,48 @@
1
+ import { saveCredentials, getCredentials } from '../../core/auth-manager.js';
2
+ import readline from 'readline';
3
+
4
+ const APP_ID = 'figma';
5
+
6
+ export async function runAuth() {
7
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
8
+
9
+ console.log(`
10
+ ┌─────────────────────────────────────────────┐
11
+ │ Figma Authentication │
12
+ │ │
13
+ │ Use a Personal Access Token (PAT). │
14
+ │ Figma → Settings → Security → │
15
+ │ Personal access tokens → Generate new token │
16
+ └─────────────────────────────────────────────┘
17
+ `);
18
+
19
+ const token = await new Promise((resolve) => {
20
+ rl.question('Paste your Figma personal access token: ', (a) => {
21
+ rl.close();
22
+ resolve(a.trim());
23
+ });
24
+ });
25
+
26
+ if (!token || token.length < 10) throw new Error('Invalid token');
27
+
28
+ saveCredentials(APP_ID, {
29
+ token,
30
+ tokenType: 'bearer',
31
+ expiresAt: null,
32
+ savedAt: new Date().toISOString(),
33
+ });
34
+
35
+ console.log('✅ Token saved for Figma');
36
+ }
37
+
38
+ export function getToken() {
39
+ return getCredentials(APP_ID)?.token || null;
40
+ }
41
+
42
+ export function buildAuthHeaders() {
43
+ const token = getToken();
44
+ if (!token) {
45
+ throw new Error('Not authenticated. Run: npx adoptai-figma-mcp --client cursor');
46
+ }
47
+ return { 'X-Figma-Token': token };
48
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { createMCPServer } from '../../core/server-base.js';
3
+ import { tools } from './tools.js';
4
+
5
+ const { start } = createMCPServer({
6
+ name: 'figma-adoptai',
7
+ version: '1.0.0',
8
+ tools,
9
+ });
10
+
11
+ start();
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "adoptai-figma-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for the Figma REST API (OpenAPI). Exposes Figma files, components, comments, variables, webhooks, and more as MCP tools.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "adoptai-figma-mcp": "./setup.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node index.js",
12
+ "setup": "node setup.js"
13
+ },
14
+ "keywords": ["mcp", "figma", "ai", "cursor", "claude", "adopt.ai"],
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "@modelcontextprotocol/sdk": "^1.0.0",
18
+ "axios": "^1.6.0"
19
+ },
20
+ "adoptai": {
21
+ "appId": "figma",
22
+ "toolCount": 63,
23
+ "sourceFormat": "openapi_3.1",
24
+ "authType": "bearer",
25
+ "generatedAt": "2025-03-27T00:00:00.000Z"
26
+ }
27
+ }
@@ -0,0 +1,384 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Publish integrations/figma/tools.js (63 tools) to adopt.ai.
4
+ * See docs/publisher.md — root .env:
5
+ * ADOPTAI_BEARER_TOKEN, ADOPTAI_INTEGRATION_ID, ADOPTAI_API_BASE_URL (optional)
6
+ *
7
+ * Figma REST uses X-Figma-Token; adopt payload also includes authorization per publisher.md.
8
+ *
9
+ * node publish-to-adoptai.js
10
+ */
11
+ import axios from 'axios';
12
+ import { existsSync, readFileSync } from 'fs';
13
+ import { dirname, join, resolve } from 'path';
14
+ import { fileURLToPath } from 'url';
15
+ import { tools } from './tools.js';
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+
19
+ function loadEnvFile(filePath) {
20
+ if (!existsSync(filePath)) return;
21
+ const text = readFileSync(filePath, 'utf8');
22
+ for (const line of text.split('\n')) {
23
+ const t = line.trim();
24
+ if (!t || t.startsWith('#')) continue;
25
+ const eq = t.indexOf('=');
26
+ if (eq <= 0) continue;
27
+ const key = t.slice(0, eq).trim();
28
+ let val = t.slice(eq + 1).trim();
29
+ if (
30
+ (val.startsWith('"') && val.endsWith('"')) ||
31
+ (val.startsWith("'") && val.endsWith("'"))
32
+ ) {
33
+ val = val.slice(1, -1);
34
+ }
35
+ if (process.env[key] === undefined) process.env[key] = val;
36
+ }
37
+ }
38
+
39
+ loadEnvFile(resolve(__dirname, '../../.env'));
40
+ loadEnvFile(resolve(__dirname, '.env'));
41
+
42
+ const ADOPTAI_API_BASE = (
43
+ process.env.ADOPTAI_API_BASE_URL || 'https://api.adopt.ai'
44
+ ).replace(/\/$/, '');
45
+ const ADOPTAI_API = `${ADOPTAI_API_BASE}/v1/mcp-integrations/add-update-integration-tool`;
46
+ const PUBLISH_DELAY_MS = Number(process.env.ADOPTAI_PUBLISH_DELAY_MS) || 300;
47
+
48
+ const BEARER_TOKEN = process.env.ADOPTAI_BEARER_TOKEN;
49
+ const INTEGRATION_ID = process.env.ADOPTAI_INTEGRATION_ID;
50
+
51
+ const BASE_ORIGIN = 'https://api.figma.com';
52
+ const APP_NAME = 'FIGMA';
53
+
54
+ if (!BEARER_TOKEN) throw new Error('ADOPTAI_BEARER_TOKEN not set in .env');
55
+ if (!INTEGRATION_ID) throw new Error('ADOPTAI_INTEGRATION_ID not set in .env');
56
+
57
+ const spec = JSON.parse(readFileSync(join(__dirname, '../../specs/figma-api.json'), 'utf8'));
58
+
59
+ /** Tool names that only exist in MCP (multi-step / URL parse); single GET /v1/me as catalog anchor. */
60
+ const COMPOSITE_TOOL_NAMES = new Set([
61
+ 'discover_figma_resources',
62
+ 'extract_design_tokens',
63
+ 'convert_tokens_to_tailwind',
64
+ 'convert_tokens_to_css_variables',
65
+ 'get_file_full_structure',
66
+ 'get_node_with_children',
67
+ 'export_design_spec',
68
+ 'search_nodes_by_name',
69
+ 'get_prototype_flows',
70
+ 'compare_file_versions',
71
+ ]);
72
+
73
+ /** Custom multipliers / renames: tool name → { method, path } (path includes /v1 or /v2). */
74
+ const FIGMA_ROUTE_OVERRIDES = {
75
+ get_file_pages: { method: 'GET', path: '/v1/files/{file_key}' },
76
+ get_file_components_summary: { method: 'GET', path: '/v1/files/{file_key}/components' },
77
+ get_node_css: { method: 'GET', path: '/v1/files/{file_key}/nodes' },
78
+ export_node_as_png: { method: 'GET', path: '/v1/images/{file_key}' },
79
+ export_node_as_svg: { method: 'GET', path: '/v1/images/{file_key}' },
80
+ export_node_as_pdf: { method: 'GET', path: '/v1/images/{file_key}' },
81
+ export_multiple_nodes: { method: 'GET', path: '/v1/images/{file_key}' },
82
+ get_file_comments: { method: 'GET', path: '/v1/files/{file_key}/comments' },
83
+ add_comment: { method: 'POST', path: '/v1/files/{file_key}/comments' },
84
+ resolve_comment: { method: 'PUT', path: '/v1/files/{file_key}/comments/{comment_id}' },
85
+ create_variables: { method: 'POST', path: '/v1/files/{file_key}/variables' },
86
+ update_variables: { method: 'POST', path: '/v1/files/{file_key}/variables' },
87
+ get_frame_by_name: { method: 'GET', path: '/v1/files/{file_key}' },
88
+ };
89
+
90
+ /** POST bodies: adopt placeholder keys → API JSON keys (MCP uses friendlier arg names). */
91
+ const SPECIAL_BODY = {
92
+ add_comment: {
93
+ message: '{message}',
94
+ client_meta: '{client_meta}',
95
+ comment_id: '{comment_id}',
96
+ },
97
+ create_variables: {
98
+ variableCollections: '{collections}',
99
+ variableModes: '{modes}',
100
+ variables: '{variables}',
101
+ variableModeValues: '{variableModeValues}',
102
+ },
103
+ update_variables: {
104
+ variables: '{variables}',
105
+ variableCollections: '{variableCollections}',
106
+ variableModes: '{variableModes}',
107
+ variableModeValues: '{variableModeValues}',
108
+ },
109
+ };
110
+
111
+ function operationIdToName(operationId) {
112
+ if (!operationId) return null;
113
+ return operationId
114
+ .replace(/([A-Z])/g, '_$1')
115
+ .toLowerCase()
116
+ .replace(/^_/, '')
117
+ .replace(/[^a-z0-9_]/g, '_')
118
+ .replace(/_+/g, '_')
119
+ .substring(0, 60);
120
+ }
121
+
122
+ function buildOpenApiRouteByToolName() {
123
+ const map = new Map();
124
+ const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'];
125
+ for (const [specPath, pathItem] of Object.entries(spec.paths || {})) {
126
+ for (const method of HTTP_METHODS) {
127
+ const op = pathItem[method];
128
+ if (!op || op.deprecated) continue;
129
+ const M = method.toUpperCase();
130
+ const n = operationIdToName(op.operationId);
131
+ if (n) map.set(n, { method: M, path: specPath });
132
+ }
133
+ }
134
+ return map;
135
+ }
136
+
137
+ const openApiRoutes = buildOpenApiRouteByToolName();
138
+
139
+ function pathParamsFromTemplate(pathTemplate) {
140
+ return [...String(pathTemplate).matchAll(/\{([^}]+)\}/g)].map((m) => m[1]);
141
+ }
142
+
143
+ function camelToSnake(p) {
144
+ return p.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
145
+ }
146
+
147
+ function schemaKeyForPathParam(param, propertyKeys) {
148
+ if (propertyKeys.includes(param)) return param;
149
+ const snake = camelToSnake(param);
150
+ if (propertyKeys.includes(snake)) return snake;
151
+ return null;
152
+ }
153
+
154
+ function mapArgType(prop) {
155
+ if (!prop || typeof prop !== 'object') return 'string';
156
+ const t = prop.type;
157
+ if (t === 'integer') return 'number';
158
+ if (t === 'number' || t === 'boolean' || t === 'object' || t === 'array') return t;
159
+ return 'string';
160
+ }
161
+
162
+ function extractApiRequestFromHandler(handler) {
163
+ if (typeof handler !== 'function') return null;
164
+ const src = Function.prototype.toString.call(handler);
165
+ const re = /apiRequest\s*\(\s*['"]([A-Z]+)['"]\s*,\s*['"]([^'"]+)['"]/is;
166
+ const m = src.match(re);
167
+ if (!m) return null;
168
+ return { method: m[1].toUpperCase(), path: m[2] };
169
+ }
170
+
171
+ function placeholderMap(keys) {
172
+ if (!keys?.length) return null;
173
+ return Object.fromEntries(keys.map((k) => [k, `{${k}}`]));
174
+ }
175
+
176
+ async function fetchExistingToolIdsByName() {
177
+ const map = new Map();
178
+ try {
179
+ const res = await axios.get(`${ADOPTAI_API_BASE}/v1/mcp-integrations/get-integration-tools`, {
180
+ params: { integration_id: INTEGRATION_ID },
181
+ headers: {
182
+ Authorization: `Bearer ${BEARER_TOKEN}`,
183
+ Accept: 'application/json',
184
+ },
185
+ });
186
+ if (Array.isArray(res.data)) {
187
+ for (const t of res.data) {
188
+ if (t?.name && t?.id) map.set(t.name, t.id);
189
+ }
190
+ }
191
+ } catch (err) {
192
+ console.warn(
193
+ '⚠️ Could not load existing tools; updates may 409:',
194
+ err.response?.data?.detail || err.message
195
+ );
196
+ }
197
+ return map;
198
+ }
199
+
200
+ function buildAlignedArgsAndRequired(pathTemplate, inputSchema) {
201
+ const pathParams = pathParamsFromTemplate(pathTemplate);
202
+ const properties = inputSchema?.properties || {};
203
+ const requiredIn = inputSchema?.required || [];
204
+ const pkeys = Object.keys(properties);
205
+
206
+ const args = {};
207
+ const requiredOut = [];
208
+
209
+ for (const pp of pathParams) {
210
+ const sk = schemaKeyForPathParam(pp, pkeys);
211
+ if (sk) {
212
+ args[pp] = mapArgType(properties[sk]);
213
+ if (requiredIn.includes(sk)) requiredOut.push(pp);
214
+ } else {
215
+ args[pp] = 'string';
216
+ if (requiredIn.includes(pp)) requiredOut.push(pp);
217
+ }
218
+ }
219
+
220
+ for (const [k, v] of Object.entries(properties)) {
221
+ const usedAsPath = pathParams.some((pp) => schemaKeyForPathParam(pp, pkeys) === k);
222
+ if (usedAsPath) continue;
223
+ args[k] = mapArgType(v);
224
+ }
225
+
226
+ for (const r of requiredIn) {
227
+ const pp = pathParams.find((p) => schemaKeyForPathParam(p, pkeys) === r);
228
+ const outKey = pp || r;
229
+ if (!requiredOut.includes(outKey)) requiredOut.push(outKey);
230
+ }
231
+
232
+ return { args, required: [...new Set(requiredOut)] };
233
+ }
234
+
235
+ function resolvePublishSpec(tool) {
236
+ if (COMPOSITE_TOOL_NAMES.has(tool.name)) {
237
+ return { method: 'GET', path: '/v1/me', composite: true };
238
+ }
239
+ const over = FIGMA_ROUTE_OVERRIDES[tool.name];
240
+ if (over) return { ...over, composite: false };
241
+ const fromSpec = openApiRoutes.get(tool.name);
242
+ if (fromSpec) return { ...fromSpec, composite: false };
243
+ const ex = extractApiRequestFromHandler(tool.handler);
244
+ if (ex) return { ...ex, composite: false };
245
+ throw new Error('Could not resolve method/path (no OpenAPI route, override, or handler literal)');
246
+ }
247
+
248
+ function buildPayload(tool, publish) {
249
+ const { method, path: pathTemplate, composite } = publish;
250
+ const properties = tool.inputSchema?.properties || {};
251
+ const pathParams = new Set(pathParamsFromTemplate(pathTemplate));
252
+ const pkeys = Object.keys(properties);
253
+
254
+ const { args, required } = buildAlignedArgsAndRequired(pathTemplate, tool.inputSchema);
255
+
256
+ const nonPathKeys = pkeys.filter((k) => {
257
+ for (const pp of pathParams) {
258
+ if (schemaKeyForPathParam(pp, pkeys) === k) return false;
259
+ }
260
+ return !pathParams.has(k);
261
+ });
262
+
263
+ const useQuery = method === 'GET' || method === 'DELETE';
264
+
265
+ let body = null;
266
+ let query_params = null;
267
+
268
+ if (composite) {
269
+ body = null;
270
+ query_params = null;
271
+ } else if (SPECIAL_BODY[tool.name]) {
272
+ body = { ...SPECIAL_BODY[tool.name] };
273
+ } else if (useQuery) {
274
+ query_params = placeholderMap(nonPathKeys);
275
+ } else {
276
+ body = placeholderMap(nonPathKeys);
277
+ if (body && Object.keys(body).length === 0) body = null;
278
+ }
279
+
280
+ const headers = {
281
+ authorization: '{secrets.bearer_token}',
282
+ 'x-figma-token': '{secrets.bearer_token}',
283
+ 'content-type': 'application/json',
284
+ };
285
+
286
+ return {
287
+ integration_id: INTEGRATION_ID,
288
+ name: `${APP_NAME}_${String(tool.name).toUpperCase()}`,
289
+ description: (tool.description || '').slice(0, 8000),
290
+ tool_spec: {
291
+ url: BASE_ORIGIN + pathTemplate,
292
+ method,
293
+ headers,
294
+ body,
295
+ query_params,
296
+ },
297
+ input_schema: {
298
+ args,
299
+ required,
300
+ },
301
+ // Keep false: adopt returns 404 "Org integration not found" when true for standard MCP integrations.
302
+ is_custom_integration: false,
303
+ is_published: false,
304
+ is_browser_call: false,
305
+ };
306
+ }
307
+
308
+ async function publishAll() {
309
+ console.log(`Publishing ${tools.length} Figma tools (adopt API: ${ADOPTAI_API_BASE})...\n`);
310
+
311
+ const existingIds = await fetchExistingToolIdsByName();
312
+ if (existingIds.size) {
313
+ console.log(`Loaded ${existingIds.size} existing tool id(s) for upsert.\n`);
314
+ }
315
+
316
+ let published = 0;
317
+ let updated = 0;
318
+ let skipped = 0;
319
+ let failed = 0;
320
+
321
+ for (const tool of tools) {
322
+ let payload;
323
+ try {
324
+ const spec = resolvePublishSpec(tool);
325
+ payload = buildPayload(tool, spec);
326
+ } catch (e) {
327
+ console.error(`❌ Skip: ${tool.name} — ${e.message}`);
328
+ failed++;
329
+ continue;
330
+ }
331
+
332
+ const existingId = existingIds.get(payload.name);
333
+ if (existingId) payload.id = existingId;
334
+
335
+ try {
336
+ await axios.post(ADOPTAI_API, payload, {
337
+ headers: {
338
+ Authorization: `Bearer ${BEARER_TOKEN}`,
339
+ 'Content-Type': 'application/json',
340
+ },
341
+ });
342
+
343
+ if (existingId) {
344
+ console.log(`✅ Updated: ${payload.name}`);
345
+ updated++;
346
+ } else {
347
+ console.log(`✅ Published: ${payload.name}`);
348
+ published++;
349
+ }
350
+
351
+ await new Promise((r) => setTimeout(r, PUBLISH_DELAY_MS));
352
+ } catch (err) {
353
+ const status = err.response?.status;
354
+ const data = err.response?.data;
355
+ const message =
356
+ (typeof data?.detail === 'string' && data.detail) || data?.message || err.message;
357
+
358
+ const isDuplicate =
359
+ status === 409 &&
360
+ typeof message === 'string' &&
361
+ message.toLowerCase().includes('already exists');
362
+
363
+ if (isDuplicate) {
364
+ console.log(`⏭️ Skipped (already exists, no id): ${payload.name}`);
365
+ skipped++;
366
+ } else {
367
+ console.error(`❌ Failed: ${payload.name} — ${status}: ${message}`);
368
+ failed++;
369
+ }
370
+
371
+ await new Promise((r) => setTimeout(r, PUBLISH_DELAY_MS));
372
+ }
373
+ }
374
+
375
+ console.log(`\n─────────────────────────────────`);
376
+ console.log(`✅ Published (new): ${published}`);
377
+ console.log(`✅ Updated: ${updated}`);
378
+ console.log(`⏭️ Skipped: ${skipped}`);
379
+ console.log(`❌ Failed: ${failed}`);
380
+ console.log(`─────────────────────────────────`);
381
+ console.log(`Total: ${tools.length} tools`);
382
+ }
383
+
384
+ publishAll();
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ import { writeConfig, removeConfig } from '../../core/config-writer.js';
3
+ import { getCredentials, isTokenExpired } from '../../core/auth-manager.js';
4
+ import { runAuth } from './auth.js';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, resolve } from 'path';
7
+ import readline from 'readline';
8
+ import { tools } from './tools.js';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const args = process.argv.slice(2);
12
+ const clientFlag = args.includes('--client') ? args[args.indexOf('--client') + 1] : 'cursor';
13
+ const isRemove = args.includes('--remove');
14
+ const isStatus = args.includes('--status');
15
+ const SUPPORTED = ['cursor', 'claude', 'windsurf', 'vscode'];
16
+
17
+ if (isRemove) {
18
+ removeConfig({ client: clientFlag, serverName: 'figma-adoptai' });
19
+ console.log(`✅ Figma MCP removed from ${clientFlag}`);
20
+ process.exit(0);
21
+ }
22
+
23
+ if (isStatus) {
24
+ const creds = getCredentials('figma');
25
+ if (!creds) {
26
+ console.log('❌ Not authenticated');
27
+ } else {
28
+ console.log(`✅ Authenticated\n Token: ${creds.token?.substring(0, 20)}...\n Saved: ${creds.savedAt}`);
29
+ }
30
+ process.exit(0);
31
+ }
32
+
33
+ if (!SUPPORTED.includes(clientFlag)) {
34
+ console.error(`Invalid client: ${clientFlag}\nSupported: ${SUPPORTED.join(', ')}`);
35
+ process.exit(1);
36
+ }
37
+
38
+ async function main() {
39
+ const existing = getCredentials('figma');
40
+
41
+ if (existing && !isTokenExpired('figma')) {
42
+ console.log('✅ Already authenticated');
43
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
44
+ const ans = await new Promise((r) =>
45
+ rl.question('Re-authenticate? (y/n): ', (a) => {
46
+ rl.close();
47
+ r(a.trim());
48
+ })
49
+ );
50
+ if (ans !== 'y') {
51
+ writeConfig({
52
+ client: clientFlag,
53
+ serverName: 'figma-adoptai',
54
+ command: 'node',
55
+ args: [resolve(__dirname, 'index.js')],
56
+ env: {},
57
+ });
58
+ process.exit(0);
59
+ }
60
+ }
61
+
62
+ await runAuth();
63
+
64
+ writeConfig({
65
+ client: clientFlag,
66
+ serverName: 'figma-adoptai',
67
+ command: 'node',
68
+ args: [resolve(__dirname, 'index.js')],
69
+ env: {},
70
+ });
71
+
72
+ console.log(`
73
+ ✅ Figma MCP is ready!
74
+ ────────────────────────────────────────
75
+ Your AI (${clientFlag}) can now use ${tools.length} tools.
76
+
77
+ Try asking:
78
+ "List pages in my Figma file ABC123"
79
+ "Export this frame as PNG from file key …"
80
+
81
+ Get a PAT: Figma → Settings → Security → Personal access tokens → Generate new token.
82
+
83
+ Run health check: node core/doctor.js --client ${clientFlag}
84
+ `);
85
+ }
86
+
87
+ main().catch((err) => {
88
+ console.error('❌ Setup failed:', err.message);
89
+ process.exit(1);
90
+ });