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.
- package/README.md +70 -0
- package/bin/adoptai-mcp.js +2 -0
- package/dist/apps/canva.js +1 -0
- package/dist/apps/figma.js +1 -0
- package/dist/apps/github.js +2 -0
- package/dist/apps/notion.js +1 -0
- package/dist/apps/registry.js +20 -0
- package/dist/apps/salesforce.js +1 -0
- package/dist/cli/add.js +532 -0
- package/dist/cli/index.js +39 -0
- package/dist/cli/list.js +19 -0
- package/dist/cli/remove.js +37 -0
- package/dist/cli/serve.js +27 -0
- package/dist/cli/status.js +24 -0
- package/dist/config/clients.js +118 -0
- package/dist/config/credentials.js +34 -0
- package/dist/core/auth-manager.js +237 -0
- package/dist/core/config-writer.js +161 -0
- package/dist/core/doctor.js +199 -0
- package/dist/core/package.json +3 -0
- package/dist/core/server-base.js +81 -0
- package/dist/integrations/canva/.env +3 -0
- package/dist/integrations/canva/auth.js +287 -0
- package/dist/integrations/canva/env.js +9 -0
- package/dist/integrations/canva/index.js +12 -0
- package/dist/integrations/canva/package.json +31 -0
- package/dist/integrations/canva/publish-to-adoptai.js +365 -0
- package/dist/integrations/canva/setup.js +90 -0
- package/dist/integrations/canva/tools.js +1315 -0
- package/dist/integrations/canva/tools.original.js +1315 -0
- package/dist/integrations/figma/auth.js +48 -0
- package/dist/integrations/figma/index.js +11 -0
- package/dist/integrations/figma/package.json +27 -0
- package/dist/integrations/figma/publish-to-adoptai.js +384 -0
- package/dist/integrations/figma/setup.js +90 -0
- package/dist/integrations/figma/tools.js +1137 -0
- package/dist/integrations/github/auth.js +53 -0
- package/dist/integrations/github/index.js +11 -0
- package/dist/integrations/github/package.json +28 -0
- package/dist/integrations/github/publish-to-adoptai.js +240 -0
- package/dist/integrations/github/setup.js +103 -0
- package/dist/integrations/github/tools.js +78 -0
- package/dist/integrations/github-actions/auth.js +53 -0
- package/dist/integrations/github-actions/index.js +11 -0
- package/dist/integrations/github-actions/package.json +27 -0
- package/dist/integrations/github-actions/setup.js +103 -0
- package/dist/integrations/github-actions/tools.js +5642 -0
- package/dist/integrations/github-activity/auth.js +53 -0
- package/dist/integrations/github-activity/index.js +11 -0
- package/dist/integrations/github-activity/package.json +27 -0
- package/dist/integrations/github-activity/setup.js +103 -0
- package/dist/integrations/github-activity/tools.js +925 -0
- package/dist/integrations/github-apps/auth.js +53 -0
- package/dist/integrations/github-apps/index.js +11 -0
- package/dist/integrations/github-apps/package.json +27 -0
- package/dist/integrations/github-apps/setup.js +103 -0
- package/dist/integrations/github-apps/tools.js +791 -0
- package/dist/integrations/github-billing/auth.js +53 -0
- package/dist/integrations/github-billing/index.js +11 -0
- package/dist/integrations/github-billing/package.json +27 -0
- package/dist/integrations/github-billing/setup.js +103 -0
- package/dist/integrations/github-billing/tools.js +438 -0
- package/dist/integrations/github-checks/auth.js +53 -0
- package/dist/integrations/github-checks/index.js +11 -0
- package/dist/integrations/github-checks/package.json +27 -0
- package/dist/integrations/github-checks/setup.js +103 -0
- package/dist/integrations/github-checks/tools.js +607 -0
- package/dist/integrations/github-code-scanning/auth.js +53 -0
- package/dist/integrations/github-code-scanning/index.js +11 -0
- package/dist/integrations/github-code-scanning/package.json +27 -0
- package/dist/integrations/github-code-scanning/setup.js +103 -0
- package/dist/integrations/github-code-scanning/tools.js +987 -0
- package/dist/integrations/github-dependabot/auth.js +53 -0
- package/dist/integrations/github-dependabot/index.js +11 -0
- package/dist/integrations/github-dependabot/package.json +27 -0
- package/dist/integrations/github-dependabot/setup.js +103 -0
- package/dist/integrations/github-dependabot/tools.js +915 -0
- package/dist/integrations/github-gists/auth.js +53 -0
- package/dist/integrations/github-gists/index.js +11 -0
- package/dist/integrations/github-gists/package.json +27 -0
- package/dist/integrations/github-gists/setup.js +103 -0
- package/dist/integrations/github-gists/tools.js +545 -0
- package/dist/integrations/github-git/auth.js +53 -0
- package/dist/integrations/github-git/index.js +11 -0
- package/dist/integrations/github-git/package.json +27 -0
- package/dist/integrations/github-git/setup.js +103 -0
- package/dist/integrations/github-git/tools.js +513 -0
- package/dist/integrations/github-issues/auth.js +53 -0
- package/dist/integrations/github-issues/index.js +11 -0
- package/dist/integrations/github-issues/package.json +27 -0
- package/dist/integrations/github-issues/setup.js +103 -0
- package/dist/integrations/github-issues/tools.js +2232 -0
- package/dist/integrations/github-orgs/auth.js +53 -0
- package/dist/integrations/github-orgs/index.js +11 -0
- package/dist/integrations/github-orgs/package.json +27 -0
- package/dist/integrations/github-orgs/setup.js +103 -0
- package/dist/integrations/github-orgs/tools.js +3512 -0
- package/dist/integrations/github-packages/auth.js +53 -0
- package/dist/integrations/github-packages/index.js +11 -0
- package/dist/integrations/github-packages/package.json +27 -0
- package/dist/integrations/github-packages/setup.js +103 -0
- package/dist/integrations/github-packages/tools.js +1088 -0
- package/dist/integrations/github-pulls/auth.js +53 -0
- package/dist/integrations/github-pulls/index.js +11 -0
- package/dist/integrations/github-pulls/package.json +27 -0
- package/dist/integrations/github-pulls/setup.js +103 -0
- package/dist/integrations/github-pulls/tools.js +1252 -0
- package/dist/integrations/github-reactions/auth.js +53 -0
- package/dist/integrations/github-reactions/index.js +11 -0
- package/dist/integrations/github-reactions/package.json +27 -0
- package/dist/integrations/github-reactions/setup.js +103 -0
- package/dist/integrations/github-reactions/tools.js +706 -0
- package/dist/integrations/github-repos/auth.js +53 -0
- package/dist/integrations/github-repos/index.js +11 -0
- package/dist/integrations/github-repos/package.json +27 -0
- package/dist/integrations/github-repos/setup.js +103 -0
- package/dist/integrations/github-repos/tools.js +7286 -0
- package/dist/integrations/github-search/auth.js +53 -0
- package/dist/integrations/github-search/index.js +11 -0
- package/dist/integrations/github-search/package.json +27 -0
- package/dist/integrations/github-search/setup.js +103 -0
- package/dist/integrations/github-search/tools.js +370 -0
- package/dist/integrations/github-teams/auth.js +53 -0
- package/dist/integrations/github-teams/index.js +11 -0
- package/dist/integrations/github-teams/package.json +27 -0
- package/dist/integrations/github-teams/setup.js +103 -0
- package/dist/integrations/github-teams/tools.js +633 -0
- package/dist/integrations/github-users/auth.js +53 -0
- package/dist/integrations/github-users/index.js +11 -0
- package/dist/integrations/github-users/package.json +27 -0
- package/dist/integrations/github-users/setup.js +103 -0
- package/dist/integrations/github-users/tools.js +1118 -0
- package/dist/integrations/notion/api.js +108 -0
- package/dist/integrations/notion/auth.js +59 -0
- package/dist/integrations/notion/endpoints.json +630 -0
- package/dist/integrations/notion/index.js +11 -0
- package/dist/integrations/notion/package.json +33 -0
- package/dist/integrations/notion/publish-to-adoptai.js +271 -0
- package/dist/integrations/notion/scripts/generate-endpoints.mjs +306 -0
- package/dist/integrations/notion/setup.js +89 -0
- package/dist/integrations/notion/tools.js +586 -0
- package/dist/integrations/notion/tools.original.js +568 -0
- package/dist/integrations/salesforce/.env +8 -0
- package/dist/integrations/salesforce/.env.example +15 -0
- package/dist/integrations/salesforce/auth.js +311 -0
- package/dist/integrations/salesforce/endpoints.json +1359 -0
- package/dist/integrations/salesforce/env.js +9 -0
- package/dist/integrations/salesforce/index.js +12 -0
- package/dist/integrations/salesforce/package.json +42 -0
- package/dist/integrations/salesforce/publish-smart-specs.js +890 -0
- package/dist/integrations/salesforce/publish-to-adoptai.js +386 -0
- package/dist/integrations/salesforce/scripts/extract-postman.mjs +222 -0
- package/dist/integrations/salesforce/setup.js +112 -0
- package/dist/integrations/salesforce/tools.js +4544 -0
- package/dist/integrations/salesforce/tools.original.js +4487 -0
- package/dist/server/mcp-server.js +50 -0
- package/dist/server/tool-loader.js +47 -0
- package/dist/specs/figma-api.json +13621 -0
- package/dist/specs/split/salesforce-auth.json +3931 -0
- package/dist/specs/split/salesforce-bulk-v1.json +1489 -0
- package/dist/specs/split/salesforce-bulk-v2.json +1951 -0
- package/dist/specs/split/salesforce-composite.json +1246 -0
- package/dist/specs/split/salesforce-connect.json +11639 -0
- package/dist/specs/split/salesforce-einstein-prediction-service.json +576 -0
- package/dist/specs/split/salesforce-event-platform.json +2682 -0
- package/dist/specs/split/salesforce-graphql.json +1754 -0
- package/dist/specs/split/salesforce-industries.json +4115 -0
- package/dist/specs/split/salesforce-metadata.json +555 -0
- package/dist/specs/split/salesforce-rest.json +4798 -0
- package/dist/specs/split/salesforce-soap.json +210 -0
- package/dist/specs/split/salesforce-subscription-management.json +1299 -0
- package/dist/specs/split/salesforce-tooling.json +2026 -0
- package/dist/specs/split/salesforce-ui.json +7426 -0
- package/package.json +47 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Publish integrations/notion/tools.js (31 base + 17 smart) to adopt.ai.
|
|
4
|
+
* See Docs/publisher.md — env: ADOPTAI_BEARER_TOKEN, ADOPTAI_INTEGRATION_ID, ADOPTAI_API_BASE_URL (optional).
|
|
5
|
+
*
|
|
6
|
+
* node publish-to-adoptai.js
|
|
7
|
+
*/
|
|
8
|
+
import axios from 'axios';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { dirname, join, resolve } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { smartTools, tools } from './tools.js';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
function loadEnvFile(filePath) {
|
|
17
|
+
if (!existsSync(filePath)) return;
|
|
18
|
+
const text = readFileSync(filePath, 'utf8');
|
|
19
|
+
for (const line of text.split('\n')) {
|
|
20
|
+
const t = line.trim();
|
|
21
|
+
if (!t || t.startsWith('#')) continue;
|
|
22
|
+
const eq = t.indexOf('=');
|
|
23
|
+
if (eq <= 0) continue;
|
|
24
|
+
const key = t.slice(0, eq).trim();
|
|
25
|
+
let val = t.slice(eq + 1).trim();
|
|
26
|
+
if (
|
|
27
|
+
(val.startsWith('"') && val.endsWith('"')) ||
|
|
28
|
+
(val.startsWith("'") && val.endsWith("'"))
|
|
29
|
+
) {
|
|
30
|
+
val = val.slice(1, -1);
|
|
31
|
+
}
|
|
32
|
+
process.env[key] = val;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
loadEnvFile(resolve(__dirname, '../../.env'));
|
|
37
|
+
|
|
38
|
+
const ADOPTAI_API_BASE = (
|
|
39
|
+
process.env.ADOPTAI_API_BASE_URL || 'https://api.adopt.ai'
|
|
40
|
+
).replace(/\/$/, '');
|
|
41
|
+
const ADOPTAI_API = `${ADOPTAI_API_BASE}/v1/mcp-integrations/add-update-integration-tool`;
|
|
42
|
+
const PUBLISH_DELAY_MS = Number(process.env.ADOPTAI_PUBLISH_DELAY_MS) || 300;
|
|
43
|
+
|
|
44
|
+
const BEARER_TOKEN = process.env.ADOPTAI_BEARER_TOKEN;
|
|
45
|
+
const INTEGRATION_ID = process.env.ADOPTAI_INTEGRATION_ID;
|
|
46
|
+
|
|
47
|
+
const BASE_URL = 'https://api.notion.com';
|
|
48
|
+
const APP_NAME = 'NOTION';
|
|
49
|
+
const NOTION_VERSION = '2025-09-03';
|
|
50
|
+
|
|
51
|
+
if (!BEARER_TOKEN) throw new Error('ADOPTAI_BEARER_TOKEN not set in ../../.env');
|
|
52
|
+
if (!INTEGRATION_ID) throw new Error('ADOPTAI_INTEGRATION_ID not set in ../../.env');
|
|
53
|
+
|
|
54
|
+
function pathParamsFromTemplate(pathTemplate) {
|
|
55
|
+
return [...String(pathTemplate).matchAll(/\{([^}]+)\}/g)].map((m) => m[1]);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function mapArgType(prop) {
|
|
59
|
+
if (!prop || typeof prop !== 'object') return 'string';
|
|
60
|
+
const t = prop.type;
|
|
61
|
+
if (t === 'integer') return 'number';
|
|
62
|
+
if (t === 'number' || t === 'boolean' || t === 'object' || t === 'array') {
|
|
63
|
+
return t;
|
|
64
|
+
}
|
|
65
|
+
return 'string';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function placeholderMap(keys) {
|
|
69
|
+
if (!keys?.length) return null;
|
|
70
|
+
return Object.fromEntries(keys.map((k) => [k, `{${k}}`]));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function fetchExistingToolIdsByName() {
|
|
74
|
+
const map = new Map();
|
|
75
|
+
try {
|
|
76
|
+
const res = await axios.get(
|
|
77
|
+
`${ADOPTAI_API_BASE}/v1/mcp-integrations/get-integration-tools`,
|
|
78
|
+
{
|
|
79
|
+
params: { integration_id: INTEGRATION_ID },
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: `Bearer ${BEARER_TOKEN}`,
|
|
82
|
+
Accept: 'application/json',
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
if (Array.isArray(res.data)) {
|
|
87
|
+
for (const t of res.data) {
|
|
88
|
+
if (t?.name && t?.id) map.set(t.name, t.id);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.warn(
|
|
93
|
+
'⚠️ Could not load existing tools; updates may 409:',
|
|
94
|
+
err.response?.data?.detail || err.message
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return map;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildToolsForPublish() {
|
|
101
|
+
const manifest = JSON.parse(
|
|
102
|
+
readFileSync(join(__dirname, 'endpoints.json'), 'utf8')
|
|
103
|
+
);
|
|
104
|
+
const inputByName = new Map(
|
|
105
|
+
tools.map((t) => [t.name, t.inputSchema])
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const base = manifest.endpoints.map((ep) => ({
|
|
109
|
+
name: ep.name,
|
|
110
|
+
description: ep.description,
|
|
111
|
+
method: ep.method,
|
|
112
|
+
path: ep.path,
|
|
113
|
+
inputSchema: inputByName.get(ep.name) || ep.inputSchema,
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
const smart = smartTools.map((t) => {
|
|
117
|
+
if (!t.publish?.method || !t.publish?.path) {
|
|
118
|
+
throw new Error(`Smart tool "${t.name}" missing publish.method/path`);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
name: t.name,
|
|
122
|
+
description: t.description,
|
|
123
|
+
method: t.publish.method,
|
|
124
|
+
path: t.publish.path,
|
|
125
|
+
inputSchema: t.inputSchema,
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const merged = [...base, ...smart];
|
|
130
|
+
if (merged.length !== 48) {
|
|
131
|
+
console.warn(`⚠️ Expected 48 tools, got ${merged.length}`);
|
|
132
|
+
}
|
|
133
|
+
return merged;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {{ name: string, description?: string, method: string, path: string, inputSchema?: object }} tool
|
|
138
|
+
*/
|
|
139
|
+
function buildPayload(tool) {
|
|
140
|
+
const method = String(tool.method).toUpperCase();
|
|
141
|
+
const pathTemplate = tool.path;
|
|
142
|
+
const pathParams = pathParamsFromTemplate(pathTemplate);
|
|
143
|
+
const pathSet = new Set(pathParams);
|
|
144
|
+
const properties = tool.inputSchema?.properties || {};
|
|
145
|
+
|
|
146
|
+
const nonPathKeys = Object.keys(properties).filter((k) => !pathSet.has(k));
|
|
147
|
+
|
|
148
|
+
const useQuery = method === 'GET' || method === 'DELETE';
|
|
149
|
+
|
|
150
|
+
const paramPlaceholders = placeholderMap(nonPathKeys);
|
|
151
|
+
|
|
152
|
+
let body = null;
|
|
153
|
+
let query_params = null;
|
|
154
|
+
if (useQuery) {
|
|
155
|
+
query_params = paramPlaceholders;
|
|
156
|
+
} else {
|
|
157
|
+
body = paramPlaceholders;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const args = Object.fromEntries(
|
|
161
|
+
Object.entries(properties).map(([k, v]) => [k, mapArgType(v)])
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
integration_id: INTEGRATION_ID,
|
|
166
|
+
name: `${APP_NAME}_${String(tool.name).toUpperCase()}`,
|
|
167
|
+
description: tool.description || '',
|
|
168
|
+
tool_spec: {
|
|
169
|
+
url: BASE_URL + pathTemplate,
|
|
170
|
+
method,
|
|
171
|
+
headers: {
|
|
172
|
+
authorization: '{secrets.bearer_token}',
|
|
173
|
+
'content-type': 'application/json',
|
|
174
|
+
'notion-version': NOTION_VERSION,
|
|
175
|
+
},
|
|
176
|
+
body,
|
|
177
|
+
query_params,
|
|
178
|
+
},
|
|
179
|
+
input_schema: {
|
|
180
|
+
args,
|
|
181
|
+
required: tool.inputSchema?.required || [],
|
|
182
|
+
},
|
|
183
|
+
is_custom_integration: false,
|
|
184
|
+
is_published: false,
|
|
185
|
+
is_browser_call: false,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function publishAll() {
|
|
190
|
+
const toPublish = buildToolsForPublish();
|
|
191
|
+
console.log(
|
|
192
|
+
`Publishing ${toPublish.length} Notion tools (adopt API: ${ADOPTAI_API_BASE})...\n`
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const existingIds = await fetchExistingToolIdsByName();
|
|
196
|
+
if (existingIds.size) {
|
|
197
|
+
console.log(`Loaded ${existingIds.size} existing tool id(s) for upsert.\n`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let published = 0;
|
|
201
|
+
let updated = 0;
|
|
202
|
+
let skipped = 0;
|
|
203
|
+
let failed = 0;
|
|
204
|
+
|
|
205
|
+
for (const tool of toPublish) {
|
|
206
|
+
let payload;
|
|
207
|
+
try {
|
|
208
|
+
payload = buildPayload(tool);
|
|
209
|
+
} catch (e) {
|
|
210
|
+
console.error(`❌ Skip: ${tool.name} — ${e.message}`);
|
|
211
|
+
failed++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const existingId = existingIds.get(payload.name);
|
|
216
|
+
if (existingId) {
|
|
217
|
+
payload.id = existingId;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
await axios.post(ADOPTAI_API, payload, {
|
|
222
|
+
headers: {
|
|
223
|
+
Authorization: `Bearer ${BEARER_TOKEN}`,
|
|
224
|
+
'Content-Type': 'application/json',
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
if (existingId) {
|
|
229
|
+
console.log(`✅ Updated: ${payload.name}`);
|
|
230
|
+
updated++;
|
|
231
|
+
} else {
|
|
232
|
+
console.log(`✅ Published: ${payload.name}`);
|
|
233
|
+
published++;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
await new Promise((r) => setTimeout(r, PUBLISH_DELAY_MS));
|
|
237
|
+
} catch (err) {
|
|
238
|
+
const status = err.response?.status;
|
|
239
|
+
const data = err.response?.data;
|
|
240
|
+
const message =
|
|
241
|
+
(typeof data?.detail === 'string' && data.detail) ||
|
|
242
|
+
data?.message ||
|
|
243
|
+
err.message;
|
|
244
|
+
|
|
245
|
+
const isDuplicate =
|
|
246
|
+
status === 409 &&
|
|
247
|
+
typeof message === 'string' &&
|
|
248
|
+
message.toLowerCase().includes('already exists');
|
|
249
|
+
|
|
250
|
+
if (isDuplicate) {
|
|
251
|
+
console.log(`⏭️ Skipped (already exists, no id): ${payload.name}`);
|
|
252
|
+
skipped++;
|
|
253
|
+
} else {
|
|
254
|
+
console.error(`❌ Failed: ${payload.name} — ${status}: ${message}`);
|
|
255
|
+
failed++;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
await new Promise((r) => setTimeout(r, PUBLISH_DELAY_MS));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log(`\n─────────────────────────────────`);
|
|
263
|
+
console.log(`✅ Published (new): ${published}`);
|
|
264
|
+
console.log(`✅ Updated: ${updated}`);
|
|
265
|
+
console.log(`⏭️ Skipped: ${skipped}`);
|
|
266
|
+
console.log(`❌ Failed: ${failed}`);
|
|
267
|
+
console.log(`─────────────────────────────────`);
|
|
268
|
+
console.log(`Total: ${toPublish.length} tools`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
publishAll();
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generates integrations/notion/endpoints.json from Notion Postman collections.
|
|
4
|
+
* Follows docs/instructions.md Section B (recursive extract, path/query/body schema).
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const NOTION_DIR = join(__dirname, '..');
|
|
12
|
+
const REPO_ROOT = join(__dirname, '..', '..', '..');
|
|
13
|
+
|
|
14
|
+
const SPEC_PRIMARY = join(REPO_ROOT, 'specs/notion/Notion API (2025-09-03).postman_collection.json');
|
|
15
|
+
const SPEC_LEGACY = join(REPO_ROOT, 'specs/notion/Notion API.postman_collection.json');
|
|
16
|
+
const OUT = join(NOTION_DIR, 'endpoints.json');
|
|
17
|
+
|
|
18
|
+
function resolveVariable(str, variables) {
|
|
19
|
+
let s = str ?? '';
|
|
20
|
+
for (const v of variables || []) {
|
|
21
|
+
s = s.replaceAll(`{{${v.key}}}`, v.value ?? '');
|
|
22
|
+
}
|
|
23
|
+
return s;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function extractAllRequests(items) {
|
|
27
|
+
const requests = [];
|
|
28
|
+
for (const item of items || []) {
|
|
29
|
+
if (Array.isArray(item.item)) {
|
|
30
|
+
requests.push(...extractAllRequests(item.item));
|
|
31
|
+
} else if (item.request) {
|
|
32
|
+
requests.push({ name: item.name, request: item.request });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return requests;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function inferType(value) {
|
|
39
|
+
if (value == null) return 'string';
|
|
40
|
+
const v = String(value).toLowerCase().trim();
|
|
41
|
+
if (v === '<integer>' || v === '<number>') return 'number';
|
|
42
|
+
if (v === '<boolean>') return 'boolean';
|
|
43
|
+
if (v === '<datetime>' || v === '<date>') return 'string';
|
|
44
|
+
if (v === '<array>') return 'array';
|
|
45
|
+
if (v === '<object>') return 'object';
|
|
46
|
+
if (!Number.isNaN(Number(v)) && v !== '') return 'number';
|
|
47
|
+
if (v === 'true' || v === 'false') return 'boolean';
|
|
48
|
+
return 'string';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Path segment → MCP path token. Do not resolve {{var}} to empty collection defaults;
|
|
53
|
+
* those must stay as path parameters (instructions §B1 / §G).
|
|
54
|
+
*/
|
|
55
|
+
function normalizeSegment(seg) {
|
|
56
|
+
if (!seg) return '';
|
|
57
|
+
const t = String(seg).trim();
|
|
58
|
+
const br = /^\{\{([^}]+)\}\}$/.exec(t);
|
|
59
|
+
if (br) return `{${br[1]}}`;
|
|
60
|
+
if (t.startsWith(':')) return `{${t.slice(1)}}`;
|
|
61
|
+
return t.replace(/\{\{([^}]+)\}\}/g, '{$1}');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build API path starting with /v1/... from Postman url object.
|
|
66
|
+
* Prefer path[] so {{base_url}} mismatch (e.g. .../v1 + /v1/...) cannot duplicate segments.
|
|
67
|
+
*/
|
|
68
|
+
function buildApiPath(urlObj, variables) {
|
|
69
|
+
if (typeof urlObj === 'string') {
|
|
70
|
+
const resolved = resolveVariable(urlObj, variables);
|
|
71
|
+
try {
|
|
72
|
+
const u = new URL(resolved.split('?')[0]);
|
|
73
|
+
let p = u.pathname.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}');
|
|
74
|
+
if (!p.startsWith('/')) p = '/' + p;
|
|
75
|
+
return p;
|
|
76
|
+
} catch {
|
|
77
|
+
return '/';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const segments = (urlObj.path || []).map((s) => normalizeSegment(s));
|
|
81
|
+
const p = '/' + segments.filter(Boolean).join('/');
|
|
82
|
+
return p.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildSchemaFromPostman(request, normalizedPath, variables) {
|
|
86
|
+
const properties = {};
|
|
87
|
+
const required = [];
|
|
88
|
+
|
|
89
|
+
const pathParams = [...normalizedPath.matchAll(/\{([^}]+)\}/g)];
|
|
90
|
+
for (const [, name] of pathParams) {
|
|
91
|
+
properties[name] = { type: 'string', description: `The ${name}` };
|
|
92
|
+
required.push(name);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const seenKeys = new Set();
|
|
96
|
+
const queryItems = typeof request.url === 'object' ? request.url.query || [] : [];
|
|
97
|
+
for (const param of queryItems) {
|
|
98
|
+
if (!param.key || seenKeys.has(param.key) || param.disabled) continue;
|
|
99
|
+
seenKeys.add(param.key);
|
|
100
|
+
properties[param.key] = {
|
|
101
|
+
type: inferType(param.value),
|
|
102
|
+
description: param.description || `The ${param.key} parameter`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (request.body?.mode === 'raw' && request.body.raw) {
|
|
107
|
+
try {
|
|
108
|
+
const bodyJson = JSON.parse(request.body.raw);
|
|
109
|
+
for (const [key, value] of Object.entries(bodyJson)) {
|
|
110
|
+
if (properties[key]) continue;
|
|
111
|
+
properties[key] = {
|
|
112
|
+
type: inferType(typeof value === 'object' ? JSON.stringify(value) : String(value)),
|
|
113
|
+
description: `The ${key} field`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
/* non-JSON body */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (request.body?.mode === 'urlencoded' || request.body?.mode === 'formdata') {
|
|
122
|
+
const items = request.body.urlencoded || request.body.formdata || [];
|
|
123
|
+
for (const row of items) {
|
|
124
|
+
if (!row.key || properties[row.key]) continue;
|
|
125
|
+
properties[row.key] = { type: 'string', description: row.description || row.key };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { type: 'object', properties, required: [...new Set(required)] };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Stable MCP tool id from method + path (instructions §C1 priority 2). */
|
|
133
|
+
function generateToolName(method, path) {
|
|
134
|
+
const prefix = {
|
|
135
|
+
GET: 'get',
|
|
136
|
+
POST: 'create',
|
|
137
|
+
PUT: 'update',
|
|
138
|
+
PATCH: 'update',
|
|
139
|
+
DELETE: 'delete',
|
|
140
|
+
}[method] || method.toLowerCase();
|
|
141
|
+
|
|
142
|
+
const cleanPath = path
|
|
143
|
+
.replace(/\/v[0-9]+\//g, '/')
|
|
144
|
+
.replace(/\/[0-9]{4}-[0-9]{2}\//g, '/')
|
|
145
|
+
.replace(/\{([^}]+)\}/g, 'by_$1')
|
|
146
|
+
.replace(/\//g, '_')
|
|
147
|
+
.replace(/^_+|_+$/g, '')
|
|
148
|
+
.replace(/_+/g, '_')
|
|
149
|
+
.toLowerCase()
|
|
150
|
+
.substring(0, 50);
|
|
151
|
+
|
|
152
|
+
return `${prefix}_${cleanPath}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function dedupeRouteKey(method, path) {
|
|
156
|
+
const norm = path.replace(/\{[^}]+\}/g, '{}');
|
|
157
|
+
return `${method} ${norm}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function methodOrder(m) {
|
|
161
|
+
const order = { GET: 0, POST: 1, PUT: 2, PATCH: 2, DELETE: 3 };
|
|
162
|
+
return order[m] ?? 9;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function shouldSkip(method, path, description) {
|
|
166
|
+
const p = path.toLowerCase();
|
|
167
|
+
const d = (description || '').toLowerCase();
|
|
168
|
+
if (['OPTIONS', 'HEAD', 'TRACE'].includes(method)) return true;
|
|
169
|
+
if (p.includes('/oauth') || p.includes('/token') || p.includes('/login') || p.includes('/logout') || p.includes('/webhook'))
|
|
170
|
+
return true;
|
|
171
|
+
// Skip OAuth token path explicitly
|
|
172
|
+
if (p.endsWith('/oauth/token') || p.includes('/v1/oauth/')) return true;
|
|
173
|
+
if (d.includes('deprecated')) return true;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function requestToEndpoint(item, variables, sourceLabel) {
|
|
178
|
+
const req = item.request;
|
|
179
|
+
const method = String(req.method || 'GET').toUpperCase();
|
|
180
|
+
const path = buildApiPath(req.url, variables);
|
|
181
|
+
|
|
182
|
+
const descRaw =
|
|
183
|
+
typeof req.description === 'string' ? req.description : req.description?.content || item.name || path;
|
|
184
|
+
const description = descRaw.substring(0, 200);
|
|
185
|
+
|
|
186
|
+
if (shouldSkip(method, path, descRaw)) return null;
|
|
187
|
+
|
|
188
|
+
const inputSchema = buildSchemaFromPostman(req, path, variables);
|
|
189
|
+
const name = generateToolName(method, path);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
name,
|
|
193
|
+
description: description.trim(),
|
|
194
|
+
method,
|
|
195
|
+
path,
|
|
196
|
+
inputSchema,
|
|
197
|
+
_sortMethod: methodOrder(method),
|
|
198
|
+
_dedupeKey: dedupeRouteKey(method, path),
|
|
199
|
+
_source: sourceLabel,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function loadCollection(path) {
|
|
204
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function processCollection(collection, sourceLabel) {
|
|
208
|
+
const variables = collection.variable || [];
|
|
209
|
+
const items = extractAllRequests(collection.item);
|
|
210
|
+
const out = [];
|
|
211
|
+
for (const item of items) {
|
|
212
|
+
const ep = requestToEndpoint(item, variables, sourceLabel);
|
|
213
|
+
if (ep) out.push(ep);
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function main() {
|
|
219
|
+
const primary = loadCollection(SPEC_PRIMARY);
|
|
220
|
+
const legacy = loadCollection(SPEC_LEGACY);
|
|
221
|
+
|
|
222
|
+
const primaryEps = processCollection(primary, '2025-09-03');
|
|
223
|
+
const legacyEps = processCollection(legacy, 'legacy');
|
|
224
|
+
|
|
225
|
+
/** Same HTTP route (ignoring path param names): prefer 2025-09-03 over legacy. */
|
|
226
|
+
const byRoute = new Map();
|
|
227
|
+
for (const ep of legacyEps) {
|
|
228
|
+
byRoute.set(ep._dedupeKey, ep);
|
|
229
|
+
}
|
|
230
|
+
for (const ep of primaryEps) {
|
|
231
|
+
byRoute.set(ep._dedupeKey, ep);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let endpoints = [...byRoute.values()];
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Cursor MCP: combined server id + tool name must be < 60 chars.
|
|
238
|
+
* Server name is `notion-adoptai` (14) → tool names must be <= 45.
|
|
239
|
+
*/
|
|
240
|
+
const MCP_SERVER_ID = 'notion-adoptai';
|
|
241
|
+
const MCP_MAX_COMBINED_LEN = 59;
|
|
242
|
+
const mcpMaxToolLen = MCP_MAX_COMBINED_LEN - MCP_SERVER_ID.length;
|
|
243
|
+
const shortToolNameByRoute = new Map([
|
|
244
|
+
['GET /v1/pages/{page_id}/properties/{property_id}', 'get_page_property_item'],
|
|
245
|
+
['POST /v1/file_uploads/{file_upload_id}/complete', 'complete_file_upload'],
|
|
246
|
+
]);
|
|
247
|
+
for (const ep of endpoints) {
|
|
248
|
+
const routeKey = `${ep.method} ${ep.path}`;
|
|
249
|
+
const shortName = shortToolNameByRoute.get(routeKey);
|
|
250
|
+
if (shortName) {
|
|
251
|
+
ep.name = shortName;
|
|
252
|
+
} else if (ep.name.length > mcpMaxToolLen) {
|
|
253
|
+
ep.name = ep.name.slice(0, mcpMaxToolLen).replace(/_+$/g, '');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const seenNames = new Map();
|
|
258
|
+
for (const ep of endpoints) {
|
|
259
|
+
if (seenNames.has(ep.name)) {
|
|
260
|
+
const n = (seenNames.get(ep.name) || 1) + 1;
|
|
261
|
+
seenNames.set(ep.name, n);
|
|
262
|
+
ep.name = `${ep.name}_v${n}`;
|
|
263
|
+
} else {
|
|
264
|
+
seenNames.set(ep.name, 1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
endpoints.sort((a, b) => {
|
|
269
|
+
if (a._sortMethod !== b._sortMethod) return a._sortMethod - b._sortMethod;
|
|
270
|
+
return a.path.localeCompare(b.path);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
for (const ep of endpoints) {
|
|
274
|
+
delete ep._sortMethod;
|
|
275
|
+
delete ep._source;
|
|
276
|
+
delete ep._dedupeKey;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const listComments = endpoints.find((e) => e.method === 'GET' && e.path === '/v1/comments');
|
|
280
|
+
if (listComments) {
|
|
281
|
+
listComments.inputSchema.properties.page_id = {
|
|
282
|
+
type: 'string',
|
|
283
|
+
description: 'List comments for this page (use block_id or page_id per Notion API).',
|
|
284
|
+
};
|
|
285
|
+
listComments.inputSchema.properties.start_cursor = {
|
|
286
|
+
type: 'string',
|
|
287
|
+
description: 'Pagination cursor from the previous response.',
|
|
288
|
+
};
|
|
289
|
+
listComments.inputSchema.properties.page_size = {
|
|
290
|
+
type: 'number',
|
|
291
|
+
description: 'Max number of results (Notion default applies if omitted).',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const payload = {
|
|
296
|
+
baseUrl: 'https://api.notion.com',
|
|
297
|
+
generatedAt: new Date().toISOString(),
|
|
298
|
+
sources: ['specs/notion/Notion API (2025-09-03).postman_collection.json', 'specs/notion/Notion API.postman_collection.json'],
|
|
299
|
+
endpoints,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
writeFileSync(OUT, JSON.stringify(payload, null, 2), 'utf8');
|
|
303
|
+
console.log(`Wrote ${endpoints.length} endpoints → ${OUT}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
main();
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
const toolCount = tools.length;
|
|
17
|
+
|
|
18
|
+
if (isRemove) {
|
|
19
|
+
removeConfig({ client: clientFlag, serverName: 'notion-adoptai' });
|
|
20
|
+
console.log(`✅ Notion MCP removed from ${clientFlag}`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (isStatus) {
|
|
25
|
+
const creds = getCredentials('notion');
|
|
26
|
+
if (!creds) {
|
|
27
|
+
console.log('❌ Not authenticated');
|
|
28
|
+
} else {
|
|
29
|
+
console.log(`✅ Authenticated\n Token: ${creds.token?.substring(0, 20)}...\n Saved: ${creds.savedAt}`);
|
|
30
|
+
}
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!SUPPORTED.includes(clientFlag)) {
|
|
35
|
+
console.error(`Invalid client: ${clientFlag}\nSupported: ${SUPPORTED.join(', ')}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
const existing = getCredentials('notion');
|
|
41
|
+
|
|
42
|
+
if (existing && !isTokenExpired('notion')) {
|
|
43
|
+
console.log('✅ Already authenticated');
|
|
44
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
45
|
+
const ans = await new Promise((r) =>
|
|
46
|
+
rl.question('Re-authenticate? (y/n): ', (a) => {
|
|
47
|
+
rl.close();
|
|
48
|
+
r(a.trim());
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
if (ans !== 'y') {
|
|
52
|
+
writeConfig({
|
|
53
|
+
client: clientFlag,
|
|
54
|
+
serverName: 'notion-adoptai',
|
|
55
|
+
command: 'node',
|
|
56
|
+
args: [resolve(__dirname, 'index.js')],
|
|
57
|
+
env: {},
|
|
58
|
+
});
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await runAuth();
|
|
64
|
+
|
|
65
|
+
writeConfig({
|
|
66
|
+
client: clientFlag,
|
|
67
|
+
serverName: 'notion-adoptai',
|
|
68
|
+
command: 'node',
|
|
69
|
+
args: [resolve(__dirname, 'index.js')],
|
|
70
|
+
env: {},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(`
|
|
74
|
+
✅ Notion MCP is ready!
|
|
75
|
+
────────────────────────────────────────
|
|
76
|
+
Your AI (${clientFlag}) can now use ${toolCount} tools.
|
|
77
|
+
|
|
78
|
+
Try asking:
|
|
79
|
+
"Retrieve my Notion bot user"
|
|
80
|
+
"Search my workspace for pages about the roadmap"
|
|
81
|
+
|
|
82
|
+
Run health check: node core/doctor.js --client ${clientFlag}
|
|
83
|
+
`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main().catch((err) => {
|
|
87
|
+
console.error('❌ Setup failed:', err.message);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|