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,386 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Publish Salesforce tools to adopt.ai (Docs/publisher.md).
|
|
4
|
+
* Root .env: ADOPTAI_BEARER_TOKEN, ADOPTAI_INTEGRATION_ID, optional ADOPTAI_API_BASE_URL,
|
|
5
|
+
* SALESFORCE_PUBLISH_API_VERSION (default v59.0).
|
|
6
|
+
*
|
|
7
|
+
* Secrets: salesforce_instance_url, bearer_token; optional salesforce_einstein_platform_url (default https://api.einstein.ai/v2).
|
|
8
|
+
*
|
|
9
|
+
* node publish-to-adoptai.js
|
|
10
|
+
*/
|
|
11
|
+
import axios from 'axios';
|
|
12
|
+
import { existsSync, readFileSync } from 'fs';
|
|
13
|
+
import { dirname, resolve } from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import { SMART_PUBLISH_SPECS } from './publish-smart-specs.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
|
+
process.env[key] = val;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
loadEnvFile(resolve(__dirname, '../../.env'));
|
|
40
|
+
|
|
41
|
+
const ADOPTAI_API_BASE = (
|
|
42
|
+
process.env.ADOPTAI_API_BASE_URL || 'https://api.adopt.ai'
|
|
43
|
+
).replace(/\/$/, '');
|
|
44
|
+
const ADOPTAI_API = `${ADOPTAI_API_BASE}/v1/mcp-integrations/add-update-integration-tool`;
|
|
45
|
+
const PUBLISH_DELAY_MS = Number(process.env.ADOPTAI_PUBLISH_DELAY_MS) || 400;
|
|
46
|
+
|
|
47
|
+
const BEARER_TOKEN = process.env.ADOPTAI_BEARER_TOKEN;
|
|
48
|
+
const INTEGRATION_ID = process.env.ADOPTAI_INTEGRATION_ID;
|
|
49
|
+
|
|
50
|
+
const API_VER_RAW = process.env.SALESFORCE_PUBLISH_API_VERSION || 'v59.0';
|
|
51
|
+
const API_VER = API_VER_RAW.startsWith('v') ? API_VER_RAW : `v${API_VER_RAW}`;
|
|
52
|
+
const API_VER_NUM = API_VER.replace(/^v/i, '');
|
|
53
|
+
|
|
54
|
+
const INSTANCE = '{secrets.salesforce_instance_url}';
|
|
55
|
+
const EINSTEIN_BASE = '{secrets.salesforce_einstein_platform_url}';
|
|
56
|
+
|
|
57
|
+
const APP_NAME = 'SALESFORCE';
|
|
58
|
+
|
|
59
|
+
if (!BEARER_TOKEN) throw new Error('ADOPTAI_BEARER_TOKEN not set in ../../.env');
|
|
60
|
+
if (!INTEGRATION_ID) throw new Error('ADOPTAI_INTEGRATION_ID not set in ../../.env');
|
|
61
|
+
|
|
62
|
+
function publishUrlPrefix(urlKind) {
|
|
63
|
+
switch (urlKind) {
|
|
64
|
+
case 'apex':
|
|
65
|
+
return `${INSTANCE}/services/apexrest`;
|
|
66
|
+
case 'data-catalog':
|
|
67
|
+
return `${INSTANCE}/services/data`;
|
|
68
|
+
case 'tooling':
|
|
69
|
+
return `${INSTANCE}/services/data/${API_VER}/tooling`;
|
|
70
|
+
case 'ui-api':
|
|
71
|
+
return `${INSTANCE}/services/data/${API_VER}/ui-api`;
|
|
72
|
+
case 'graphql':
|
|
73
|
+
return `${INSTANCE}/services/data/${API_VER}/graphql`;
|
|
74
|
+
case 'bulk-v1-async':
|
|
75
|
+
return `${INSTANCE}/services/async/${API_VER_NUM}`;
|
|
76
|
+
case 'soap-metadata':
|
|
77
|
+
return `${INSTANCE}/services/Soap/m/${API_VER_NUM}`;
|
|
78
|
+
case 'einstein-platform':
|
|
79
|
+
return EINSTEIN_BASE.replace(/\/$/, '');
|
|
80
|
+
default:
|
|
81
|
+
return `${INSTANCE}/services/data/${API_VER}`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function fullToolUrl(urlKind, pathTemplate) {
|
|
86
|
+
if (urlKind === 'graphql') {
|
|
87
|
+
return publishUrlPrefix('graphql');
|
|
88
|
+
}
|
|
89
|
+
if (urlKind === 'einstein-platform') {
|
|
90
|
+
const base = publishUrlPrefix('einstein-platform');
|
|
91
|
+
const p =
|
|
92
|
+
!pathTemplate || pathTemplate === ''
|
|
93
|
+
? ''
|
|
94
|
+
: pathTemplate.startsWith('/')
|
|
95
|
+
? pathTemplate
|
|
96
|
+
: `/${pathTemplate}`;
|
|
97
|
+
return base + p;
|
|
98
|
+
}
|
|
99
|
+
if (urlKind === 'soap-metadata') {
|
|
100
|
+
return publishUrlPrefix('soap-metadata');
|
|
101
|
+
}
|
|
102
|
+
const p =
|
|
103
|
+
pathTemplate == null || pathTemplate === ''
|
|
104
|
+
? ''
|
|
105
|
+
: pathTemplate.startsWith('/')
|
|
106
|
+
? pathTemplate
|
|
107
|
+
: `/${pathTemplate}`;
|
|
108
|
+
return publishUrlPrefix(urlKind) + p;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function pathParamsFromTemplate(pathTemplate) {
|
|
112
|
+
const s = String(pathTemplate ?? '');
|
|
113
|
+
return [...s.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function pathArgKeySet(pathTemplate, pathPlaceholderArgs) {
|
|
117
|
+
const segs = pathParamsFromTemplate(pathTemplate);
|
|
118
|
+
const map = pathPlaceholderArgs || {};
|
|
119
|
+
return new Set(segs.map((s) => map[s] || s));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function mapArgType(prop) {
|
|
123
|
+
if (!prop || typeof prop !== 'object') return 'string';
|
|
124
|
+
const t = prop.type;
|
|
125
|
+
if (t === 'integer') return 'number';
|
|
126
|
+
if (t === 'number' || t === 'boolean' || t === 'object' || t === 'array') {
|
|
127
|
+
return t;
|
|
128
|
+
}
|
|
129
|
+
return 'string';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function placeholderMap(keys) {
|
|
133
|
+
if (!keys?.length) return null;
|
|
134
|
+
return Object.fromEntries(keys.map((k) => [k, `{${k}}`]));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function fetchExistingToolIdsByName() {
|
|
138
|
+
const map = new Map();
|
|
139
|
+
try {
|
|
140
|
+
const res = await axios.get(
|
|
141
|
+
`${ADOPTAI_API_BASE}/v1/mcp-integrations/get-integration-tools`,
|
|
142
|
+
{
|
|
143
|
+
params: { integration_id: INTEGRATION_ID },
|
|
144
|
+
headers: {
|
|
145
|
+
Authorization: `Bearer ${BEARER_TOKEN}`,
|
|
146
|
+
Accept: 'application/json',
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
if (Array.isArray(res.data)) {
|
|
151
|
+
for (const t of res.data) {
|
|
152
|
+
if (t?.name && t?.id) map.set(t.name, t.id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.warn(
|
|
157
|
+
'⚠️ Could not load existing tools; updates may 409:',
|
|
158
|
+
err.response?.data?.detail || err.message
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return map;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const PLACEHOLDER_RE = /^\{[a-zA-Z0-9_]+\}$/;
|
|
165
|
+
|
|
166
|
+
function buildPayload(tool) {
|
|
167
|
+
const spec = SMART_PUBLISH_SPECS[tool.name] || null;
|
|
168
|
+
|
|
169
|
+
const method = String((spec?.method ?? tool.method) || '').toUpperCase();
|
|
170
|
+
const urlKind = spec?.urlKind ?? tool.urlKind;
|
|
171
|
+
let pathTemplate =
|
|
172
|
+
spec?.path !== undefined ? spec.path : tool.path != null ? tool.path : '';
|
|
173
|
+
|
|
174
|
+
if (!method || !urlKind) {
|
|
175
|
+
throw new Error('missing method or urlKind');
|
|
176
|
+
}
|
|
177
|
+
const pathOk =
|
|
178
|
+
urlKind === 'graphql' ||
|
|
179
|
+
urlKind === 'soap-metadata' ||
|
|
180
|
+
typeof pathTemplate === 'string';
|
|
181
|
+
if (!pathOk) {
|
|
182
|
+
throw new Error('missing path');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const inputSchema = spec?.inputSchemaOverride ?? tool.inputSchema;
|
|
186
|
+
const properties = inputSchema?.properties || {};
|
|
187
|
+
const required = inputSchema?.required || [];
|
|
188
|
+
|
|
189
|
+
const pathKeys = pathArgKeySet(pathTemplate, spec?.pathPlaceholderArgs);
|
|
190
|
+
const omit = new Set(spec?.omitArgsFromHttp || []);
|
|
191
|
+
|
|
192
|
+
const fullUrl = fullToolUrl(urlKind, pathTemplate);
|
|
193
|
+
const useQuery = method === 'GET' || method === 'DELETE';
|
|
194
|
+
|
|
195
|
+
let query_params = null;
|
|
196
|
+
let body = null;
|
|
197
|
+
|
|
198
|
+
if (spec?.queryParamsLiteral) {
|
|
199
|
+
query_params = { ...spec.queryParamsLiteral };
|
|
200
|
+
} else if (spec?.queryParamsExplicit) {
|
|
201
|
+
query_params = { ...spec.queryParamsExplicit };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (spec?.rawStringBodyArg) {
|
|
205
|
+
body = `{${spec.rawStringBodyArg}}`;
|
|
206
|
+
} else if (spec?.wholeBodyPlaceholderArg) {
|
|
207
|
+
body = `{${spec.wholeBodyPlaceholderArg}}`;
|
|
208
|
+
} else if (spec?.bodyLiteral) {
|
|
209
|
+
body = { ...spec.bodyLiteral };
|
|
210
|
+
} else if (spec?.bodyExplicit) {
|
|
211
|
+
body = {};
|
|
212
|
+
for (const [k, v] of Object.entries(spec.bodyExplicit)) {
|
|
213
|
+
if (typeof v === 'string' && PLACEHOLDER_RE.test(v)) {
|
|
214
|
+
body[k] = v;
|
|
215
|
+
} else {
|
|
216
|
+
body[k] = v;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} else if (urlKind === 'graphql') {
|
|
220
|
+
const nk = Object.keys(properties).filter((k) => !pathKeys.has(k) && !omit.has(k));
|
|
221
|
+
body = placeholderMap(nk);
|
|
222
|
+
} else if (useQuery && !spec?.queryParamsExplicit && !spec?.queryParamsLiteral) {
|
|
223
|
+
const nk = Object.keys(properties).filter((k) => !pathKeys.has(k) && !omit.has(k));
|
|
224
|
+
query_params = placeholderMap(nk);
|
|
225
|
+
} else if (!useQuery) {
|
|
226
|
+
const nk = Object.keys(properties).filter((k) => !pathKeys.has(k) && !omit.has(k));
|
|
227
|
+
body = placeholderMap(nk);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (
|
|
231
|
+
body &&
|
|
232
|
+
typeof body === 'object' &&
|
|
233
|
+
!spec?.bodyLiteral &&
|
|
234
|
+
!spec?.rawStringBodyArg &&
|
|
235
|
+
!spec?.wholeBodyPlaceholderArg &&
|
|
236
|
+
Object.keys(body).length === 0
|
|
237
|
+
) {
|
|
238
|
+
body = null;
|
|
239
|
+
}
|
|
240
|
+
if (
|
|
241
|
+
query_params &&
|
|
242
|
+
typeof query_params === 'object' &&
|
|
243
|
+
Object.keys(query_params).length === 0
|
|
244
|
+
) {
|
|
245
|
+
query_params = null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const args = Object.fromEntries(
|
|
249
|
+
Object.entries(properties).map(([k, v]) => [k, mapArgType(v)])
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const baseHeaders = {
|
|
253
|
+
authorization: '{secrets.bearer_token}',
|
|
254
|
+
'content-type': spec?.contentTypeOverride || 'application/json',
|
|
255
|
+
};
|
|
256
|
+
const extra = spec?.extraHeaders || {};
|
|
257
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
258
|
+
baseHeaders[k.toLowerCase()] = v;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let description = (tool.description || '').slice(0, 7500);
|
|
262
|
+
if (spec?.note) {
|
|
263
|
+
description = `${description}\n\n[HTTP catalog] ${spec.note}`.slice(0, 8000);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
integration_id: INTEGRATION_ID,
|
|
268
|
+
name: `${APP_NAME}_${String(tool.name).toUpperCase()}`,
|
|
269
|
+
description,
|
|
270
|
+
tool_spec: {
|
|
271
|
+
url: fullUrl,
|
|
272
|
+
method,
|
|
273
|
+
headers: baseHeaders,
|
|
274
|
+
body,
|
|
275
|
+
query_params,
|
|
276
|
+
},
|
|
277
|
+
input_schema: {
|
|
278
|
+
args,
|
|
279
|
+
required,
|
|
280
|
+
},
|
|
281
|
+
is_custom_integration: false,
|
|
282
|
+
is_published: false,
|
|
283
|
+
is_browser_call: false,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function isPublishableTool(tool) {
|
|
288
|
+
if (!tool?.name) return false;
|
|
289
|
+
if (SMART_PUBLISH_SPECS[tool.name]) return true;
|
|
290
|
+
return Boolean(
|
|
291
|
+
tool.method &&
|
|
292
|
+
tool.urlKind &&
|
|
293
|
+
(tool.urlKind === 'graphql' || typeof tool.path === 'string')
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function publishAll() {
|
|
298
|
+
const { tools } = await import('./tools.js');
|
|
299
|
+
const publishable = tools.filter(isPublishableTool);
|
|
300
|
+
const skipped = tools.length - publishable.length;
|
|
301
|
+
|
|
302
|
+
console.log(
|
|
303
|
+
`Publishing ${publishable.length} Salesforce tools (${skipped} skipped) to adopt API: ${ADOPTAI_API_BASE}…\n`
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const existingIds = await fetchExistingToolIdsByName();
|
|
307
|
+
if (existingIds.size) {
|
|
308
|
+
console.log(`Loaded ${existingIds.size} existing tool id(s) for upsert.\n`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let published = 0;
|
|
312
|
+
let updated = 0;
|
|
313
|
+
let skipDup = 0;
|
|
314
|
+
let failed = 0;
|
|
315
|
+
|
|
316
|
+
for (const tool of publishable) {
|
|
317
|
+
let payload;
|
|
318
|
+
try {
|
|
319
|
+
payload = buildPayload(tool);
|
|
320
|
+
} catch (e) {
|
|
321
|
+
console.error(`❌ Skip: ${tool.name} — ${e.message}`);
|
|
322
|
+
failed++;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const existingId = existingIds.get(payload.name);
|
|
327
|
+
if (existingId) {
|
|
328
|
+
payload.id = existingId;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
await axios.post(ADOPTAI_API, payload, {
|
|
333
|
+
headers: {
|
|
334
|
+
Authorization: `Bearer ${BEARER_TOKEN}`,
|
|
335
|
+
'Content-Type': 'application/json',
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
if (existingId) {
|
|
340
|
+
console.log(`✅ Updated: ${payload.name}`);
|
|
341
|
+
updated++;
|
|
342
|
+
} else {
|
|
343
|
+
console.log(`✅ Published: ${payload.name}`);
|
|
344
|
+
published++;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await new Promise((r) => setTimeout(r, PUBLISH_DELAY_MS));
|
|
348
|
+
} catch (err) {
|
|
349
|
+
const status = err.response?.status;
|
|
350
|
+
const data = err.response?.data;
|
|
351
|
+
const message =
|
|
352
|
+
(typeof data?.detail === 'string' && data.detail) ||
|
|
353
|
+
data?.message ||
|
|
354
|
+
err.message;
|
|
355
|
+
|
|
356
|
+
const isDuplicate =
|
|
357
|
+
status === 409 &&
|
|
358
|
+
typeof message === 'string' &&
|
|
359
|
+
message.toLowerCase().includes('already exists');
|
|
360
|
+
|
|
361
|
+
if (isDuplicate) {
|
|
362
|
+
console.log(`⏭️ Skipped (already exists, no id): ${payload.name}`);
|
|
363
|
+
skipDup++;
|
|
364
|
+
} else {
|
|
365
|
+
console.error(`❌ Failed: ${payload.name} — ${status}: ${message}`);
|
|
366
|
+
failed++;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
await new Promise((r) => setTimeout(r, PUBLISH_DELAY_MS));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
console.log(`\n─────────────────────────────────`);
|
|
374
|
+
console.log(`✅ Published (new): ${published}`);
|
|
375
|
+
console.log(`✅ Updated: ${updated}`);
|
|
376
|
+
console.log(`⏭️ Skipped (409): ${skipDup}`);
|
|
377
|
+
console.log(`❌ Failed: ${failed}`);
|
|
378
|
+
console.log(`⏭️ Skipped (no spec): ${skipped}`);
|
|
379
|
+
console.log(`─────────────────────────────────`);
|
|
380
|
+
console.log(`Total tools in tools.js: ${tools.length}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
publishAll().catch((e) => {
|
|
384
|
+
console.error(e);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Extract REST tools from specs/split/salesforce-rest.json (Section B).
|
|
4
|
+
* Outputs integrations/salesforce/endpoints.json
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const SPEC = join(__dirname, '../../../specs/split/salesforce-rest.json');
|
|
12
|
+
const OUT = join(__dirname, '../endpoints.json');
|
|
13
|
+
|
|
14
|
+
function buildVarMap(collection) {
|
|
15
|
+
const map = {};
|
|
16
|
+
for (const v of collection.variable || []) {
|
|
17
|
+
map[v.key] = v.value ?? '';
|
|
18
|
+
}
|
|
19
|
+
return map;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveTemplate(str, map) {
|
|
23
|
+
if (str == null) return str;
|
|
24
|
+
let out = String(str);
|
|
25
|
+
for (const [k, v] of Object.entries(map)) {
|
|
26
|
+
out = out.split(`{{${k}}}`).join(v);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const SKIP_PATH_RE =
|
|
32
|
+
/\/oauth|\/token|\/login|\/logout|\/services\/oauth2|\/webhook|\/Soap\/|\/soap\/|soap\.jsp/i;
|
|
33
|
+
|
|
34
|
+
function shouldSkipUrl(fullPath) {
|
|
35
|
+
const p = fullPath || '';
|
|
36
|
+
return SKIP_PATH_RE.test(p);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Parse Salesforce request URL into { urlKind, path } for tools.
|
|
41
|
+
* - versioned: path under /services/data/vXX/...
|
|
42
|
+
* - data-catalog: GET /services/data (list API versions)
|
|
43
|
+
* - apex: /services/apexrest/...
|
|
44
|
+
*/
|
|
45
|
+
function parseSalesforceUrl(rawUrl, varMap) {
|
|
46
|
+
const resolved = resolveTemplate(rawUrl, varMap);
|
|
47
|
+
const noQuery = resolved.split('?')[0];
|
|
48
|
+
const pathOnly = noQuery.replace(/^https?:\/\/[^/?#]+/i, '');
|
|
49
|
+
|
|
50
|
+
if (shouldSkipUrl(pathOnly)) return null;
|
|
51
|
+
|
|
52
|
+
if (pathOnly.startsWith('/services/apexrest')) {
|
|
53
|
+
let rest = pathOnly.slice('/services/apexrest'.length);
|
|
54
|
+
if (!rest || rest === '/') rest = '/{urlMapping}';
|
|
55
|
+
rest = rest.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}');
|
|
56
|
+
return { urlKind: 'apex', path: rest };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (pathOnly === '/services/data' || pathOnly === '/services/data/') {
|
|
60
|
+
return { urlKind: 'data-catalog', path: '/' };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!pathOnly.startsWith('/services/data/')) {
|
|
64
|
+
return { urlKind: 'versioned', path: pathOnly };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const afterData = pathOnly.slice('/services/data/'.length);
|
|
68
|
+
const m = afterData.match(/^(v[0-9.]+)(\/.*)?$/);
|
|
69
|
+
if (m) {
|
|
70
|
+
let rest = m[2] || '/';
|
|
71
|
+
if (rest === '') rest = '/';
|
|
72
|
+
rest = rest.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, '{$1}');
|
|
73
|
+
return { urlKind: 'versioned', path: rest };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { urlKind: 'data-catalog', path: '/' };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function inferType(value) {
|
|
80
|
+
if (value == null) return 'string';
|
|
81
|
+
const v = String(value).toLowerCase().trim();
|
|
82
|
+
if (v === '<integer>' || v === '<number>') return 'number';
|
|
83
|
+
if (v === '<boolean>') return 'boolean';
|
|
84
|
+
if (v === '<datetime>' || v === '<date>') return 'string';
|
|
85
|
+
if (v === '<array>') return 'array';
|
|
86
|
+
if (v === '<object>') return 'object';
|
|
87
|
+
if (!Number.isNaN(Number(v)) && v !== '') return 'number';
|
|
88
|
+
if (v === 'true' || v === 'false') return 'boolean';
|
|
89
|
+
return 'string';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function descFromPostman(req, itemName) {
|
|
93
|
+
const d = req.description;
|
|
94
|
+
if (typeof d === 'string') return d.slice(0, 500);
|
|
95
|
+
if (d && typeof d === 'object' && d.content) return String(d.content).slice(0, 500);
|
|
96
|
+
return itemName;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function generateToolName(method, path, itemName) {
|
|
100
|
+
const clean = (itemName || '')
|
|
101
|
+
.replace(/[^a-zA-Z0-9_]+/g, '_')
|
|
102
|
+
.replace(/^_+|_+$/g, '')
|
|
103
|
+
.toLowerCase();
|
|
104
|
+
if (clean && !clean.includes('/') && clean.length < 60) return clean.slice(0, 60);
|
|
105
|
+
|
|
106
|
+
const prefix =
|
|
107
|
+
{ GET: 'get', POST: 'create', PUT: 'update', PATCH: 'update', DELETE: 'delete' }[method] ||
|
|
108
|
+
method.toLowerCase();
|
|
109
|
+
let cleanPath = path
|
|
110
|
+
.replace(/\/v[0-9.]+\//g, '/')
|
|
111
|
+
.replace(/\{([^}]+)\}/g, 'by_$1')
|
|
112
|
+
.replace(/\//g, '_')
|
|
113
|
+
.replace(/^_+|_+$/g, '')
|
|
114
|
+
.replace(/_+/g, '_')
|
|
115
|
+
.toLowerCase()
|
|
116
|
+
.slice(0, 50);
|
|
117
|
+
return `${prefix}_${cleanPath}`.replace(/_+/g, '_').slice(0, 60);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildSchema(req, normalizedPath, varMap) {
|
|
121
|
+
const properties = {};
|
|
122
|
+
const required = [];
|
|
123
|
+
|
|
124
|
+
for (const [, name] of normalizedPath.matchAll(/\{([^}]+)\}/g)) {
|
|
125
|
+
properties[name] = { type: 'string', description: `Path parameter: ${name}` };
|
|
126
|
+
required.push(name);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const seen = new Set();
|
|
130
|
+
if (typeof req.url === 'object' && req.url?.query) {
|
|
131
|
+
for (const param of req.url.query) {
|
|
132
|
+
if (!param.key || param.disabled || seen.has(param.key)) continue;
|
|
133
|
+
seen.add(param.key);
|
|
134
|
+
if (properties[param.key]) continue;
|
|
135
|
+
properties[param.key] = {
|
|
136
|
+
type: inferType(param.value),
|
|
137
|
+
description: param.description || `Query: ${param.key}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (req.body?.mode === 'raw' && req.body.raw) {
|
|
143
|
+
try {
|
|
144
|
+
const bodyJson = JSON.parse(req.body.raw);
|
|
145
|
+
for (const [key, value] of Object.entries(bodyJson)) {
|
|
146
|
+
if (properties[key]) continue;
|
|
147
|
+
properties[key] = { type: inferType(String(value)), description: `Body: ${key}` };
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
/* not JSON */
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (req.body?.mode === 'urlencoded' || req.body?.mode === 'formdata') {
|
|
155
|
+
const items = req.body.urlencoded || req.body.formdata || [];
|
|
156
|
+
for (const item of items) {
|
|
157
|
+
if (!item.key || properties[item.key]) continue;
|
|
158
|
+
properties[item.key] = { type: 'string', description: item.description || item.key };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { type: 'object', properties, required: [...new Set(required)] };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function extractRequests(items, varMap, out) {
|
|
166
|
+
for (const item of items || []) {
|
|
167
|
+
if (Array.isArray(item.item)) {
|
|
168
|
+
extractRequests(item.item, varMap, out);
|
|
169
|
+
} else if (item.request) {
|
|
170
|
+
const req = item.request;
|
|
171
|
+
const method = String(req.method || 'GET').toUpperCase();
|
|
172
|
+
if (['OPTIONS', 'HEAD', 'TRACE'].includes(method)) continue;
|
|
173
|
+
|
|
174
|
+
let rawUrl = typeof req.url === 'string' ? req.url : req.url?.raw || '';
|
|
175
|
+
const parsed = parseSalesforceUrl(rawUrl, varMap);
|
|
176
|
+
if (!parsed) continue;
|
|
177
|
+
|
|
178
|
+
const { urlKind, path } = parsed;
|
|
179
|
+
const name = generateToolName(method, path, item.name);
|
|
180
|
+
out.push({
|
|
181
|
+
name,
|
|
182
|
+
description: descFromPostman(req, item.name),
|
|
183
|
+
method,
|
|
184
|
+
path,
|
|
185
|
+
urlKind,
|
|
186
|
+
inputSchema: buildSchema(req, path, varMap),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const collection = JSON.parse(readFileSync(SPEC, 'utf8'));
|
|
193
|
+
const varMap = buildVarMap(collection);
|
|
194
|
+
const endpoints = [];
|
|
195
|
+
extractRequests(collection.item, varMap, endpoints);
|
|
196
|
+
|
|
197
|
+
const seenKeys = new Set();
|
|
198
|
+
const deduped = [];
|
|
199
|
+
for (const e of endpoints) {
|
|
200
|
+
const key = `${e.method}|${e.urlKind}|${e.path}`;
|
|
201
|
+
if (seenKeys.has(key)) continue;
|
|
202
|
+
seenKeys.add(key);
|
|
203
|
+
deduped.push(e);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const used = new Map();
|
|
207
|
+
for (const e of deduped) {
|
|
208
|
+
let n = e.name;
|
|
209
|
+
let c = 1;
|
|
210
|
+
while (used.has(n)) {
|
|
211
|
+
c += 1;
|
|
212
|
+
n = `${e.name}_v${c}`;
|
|
213
|
+
}
|
|
214
|
+
used.set(n, true);
|
|
215
|
+
e.name = n;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
writeFileSync(
|
|
219
|
+
OUT,
|
|
220
|
+
JSON.stringify({ generatedAt: new Date().toISOString(), endpoints: deduped }, null, 2)
|
|
221
|
+
);
|
|
222
|
+
console.error('Wrote', OUT, 'count', deduped.length);
|