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
package/dist/cli/list.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { APP_SLUGS, countToolsForApp } from '../apps/registry.js';
|
|
3
|
+
import { hasAppCredentials } from '../config/credentials.js';
|
|
4
|
+
export async function runList() {
|
|
5
|
+
console.log(chalk.bold('\n Available Apps'));
|
|
6
|
+
console.log(chalk.dim(' ──────────────────────────────────────────'));
|
|
7
|
+
let total = 0;
|
|
8
|
+
for (const app of APP_SLUGS) {
|
|
9
|
+
const n = await countToolsForApp(app);
|
|
10
|
+
total += n;
|
|
11
|
+
const ok = hasAppCredentials(app);
|
|
12
|
+
const mark = ok ? chalk.green('✅') : chalk.red('❌');
|
|
13
|
+
const auth = ok ? chalk.green('authenticated') : chalk.dim('not authenticated');
|
|
14
|
+
console.log(` ${mark} ${app.padEnd(12)} ${String(n).padStart(4)} tools ${auth}`);
|
|
15
|
+
}
|
|
16
|
+
console.log(chalk.dim(' ──────────────────────────────────────────'));
|
|
17
|
+
console.log(chalk.bold(` Total: ${total.toLocaleString()} tools across ${APP_SLUGS.length} apps\n`));
|
|
18
|
+
console.log(chalk.cyan(' Run: npx adoptai-mcp add --app <name> --client cursor\n'));
|
|
19
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { isAppSlug } from '../apps/registry.js';
|
|
4
|
+
import { getCredentialsStore } from '../config/credentials.js';
|
|
5
|
+
import { isClientId, removeAdoptaiMcpServer } from '../config/clients.js';
|
|
6
|
+
export async function runRemove(argv) {
|
|
7
|
+
const app = argv.app.trim().toLowerCase();
|
|
8
|
+
if (!isAppSlug(app)) {
|
|
9
|
+
console.error(chalk.red(`Unknown app "${argv.app}".`));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const clientRaw = argv.client;
|
|
13
|
+
if (!isClientId(clientRaw)) {
|
|
14
|
+
console.error(chalk.red(`Unknown client "${clientRaw}".`));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const client = clientRaw;
|
|
18
|
+
const removed = removeAdoptaiMcpServer(client, app);
|
|
19
|
+
if (!removed) {
|
|
20
|
+
console.log(chalk.yellow(`No ${app}-adoptai entry found for ${client}.`));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log(chalk.green(`Removed ${app}-adoptai from ${client} config.`));
|
|
24
|
+
}
|
|
25
|
+
const { del } = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'confirm',
|
|
28
|
+
name: 'del',
|
|
29
|
+
message: 'Delete saved credentials too?',
|
|
30
|
+
default: false,
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
if (del) {
|
|
34
|
+
getCredentialsStore().delete(app);
|
|
35
|
+
console.log(chalk.green(`Deleted stored credentials for ${app}.`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isAppSlug } from '../apps/registry.js';
|
|
2
|
+
import { getCredentialsStore } from '../config/credentials.js';
|
|
3
|
+
import { applyCredentialsToEnv, loadToolsForApp } from '../server/tool-loader.js';
|
|
4
|
+
import { runMcpServer } from '../server/mcp-server.js';
|
|
5
|
+
function pickCredentials(app, store) {
|
|
6
|
+
const raw = store.get(app);
|
|
7
|
+
if (!raw || typeof raw !== 'object')
|
|
8
|
+
return null;
|
|
9
|
+
return raw;
|
|
10
|
+
}
|
|
11
|
+
export async function runServe(argv) {
|
|
12
|
+
const app = argv.app;
|
|
13
|
+
if (!isAppSlug(app)) {
|
|
14
|
+
console.error(`Unknown app "${app}".`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const store = getCredentialsStore();
|
|
18
|
+
const creds = pickCredentials(app, store);
|
|
19
|
+
if (!creds) {
|
|
20
|
+
console.error(`No saved credentials for "${app}". Run:\n npx adoptai-mcp add --app ${app} --client cursor`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
applyCredentialsToEnv(app, creds);
|
|
24
|
+
const tools = await loadToolsForApp(app);
|
|
25
|
+
const serverName = `${app}-adoptai`;
|
|
26
|
+
await runMcpServer({ name: serverName, version: '1.0.0', tools });
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { APP_SLUGS, countToolsForApp } from '../apps/registry.js';
|
|
3
|
+
import { hasAppCredentials } from '../config/credentials.js';
|
|
4
|
+
import { CLIENT_IDS, clientConfigPath, listAdoptaiAppsInClient } from '../config/clients.js';
|
|
5
|
+
export async function runStatus() {
|
|
6
|
+
console.log(chalk.bold('\n Clients'));
|
|
7
|
+
console.log(chalk.dim(' ──────────────────────────────────────────'));
|
|
8
|
+
for (const c of CLIENT_IDS) {
|
|
9
|
+
const apps = listAdoptaiAppsInClient(c);
|
|
10
|
+
const p = clientConfigPath(c);
|
|
11
|
+
const line = apps.length ? apps.join(', ') : chalk.dim('(none)');
|
|
12
|
+
console.log(` ${chalk.bold(c)}: ${line}`);
|
|
13
|
+
if (p)
|
|
14
|
+
console.log(chalk.dim(` ${p}`));
|
|
15
|
+
}
|
|
16
|
+
const connected = APP_SLUGS.filter((a) => hasAppCredentials(a));
|
|
17
|
+
let toolTotal = 0;
|
|
18
|
+
for (const a of connected) {
|
|
19
|
+
toolTotal += await countToolsForApp(a);
|
|
20
|
+
}
|
|
21
|
+
console.log(chalk.bold('\n Stored credentials (connected apps)'));
|
|
22
|
+
console.log(` ${connected.length ? connected.join(', ') : chalk.dim('none')}`);
|
|
23
|
+
console.log(chalk.bold(`\n Total tools (authenticated apps): ${toolTotal.toLocaleString()}\n`));
|
|
24
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
export const CLIENT_IDS = ['cursor', 'claude', 'windsurf', 'vscode'];
|
|
6
|
+
export function clientConfigPath(client) {
|
|
7
|
+
switch (client) {
|
|
8
|
+
case 'cursor':
|
|
9
|
+
return join(homedir(), '.cursor', 'mcp.json');
|
|
10
|
+
case 'claude':
|
|
11
|
+
return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
12
|
+
case 'windsurf':
|
|
13
|
+
return join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
14
|
+
case 'vscode':
|
|
15
|
+
return null;
|
|
16
|
+
default:
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function adoptaiServerKey(app) {
|
|
21
|
+
return `${app}-adoptai`;
|
|
22
|
+
}
|
|
23
|
+
export function mcpServerEntryForApp(app) {
|
|
24
|
+
return {
|
|
25
|
+
command: 'npx',
|
|
26
|
+
args: ['-y', 'adoptai-mcp', 'serve', '--app', app],
|
|
27
|
+
env: {},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function readClientConfigFile(client) {
|
|
31
|
+
const p = clientConfigPath(client);
|
|
32
|
+
if (!p || !existsSync(p))
|
|
33
|
+
return {};
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function readMcpServers(client) {
|
|
42
|
+
const j = readClientConfigFile(client);
|
|
43
|
+
const mcp = j.mcpServers;
|
|
44
|
+
if (mcp && typeof mcp === 'object' && !Array.isArray(mcp)) {
|
|
45
|
+
return mcp;
|
|
46
|
+
}
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
function writeClientConfigRaw(client, data) {
|
|
50
|
+
const p = clientConfigPath(client);
|
|
51
|
+
if (!p) {
|
|
52
|
+
throw new Error('No config path for this client');
|
|
53
|
+
}
|
|
54
|
+
const dir = dirname(p);
|
|
55
|
+
if (!existsSync(dir)) {
|
|
56
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
57
|
+
}
|
|
58
|
+
writeFileSync(p, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
|
|
59
|
+
}
|
|
60
|
+
export function upsertAdoptaiMcpServer(client, app) {
|
|
61
|
+
if (client === 'vscode') {
|
|
62
|
+
const entry = mcpServerEntryForApp(app);
|
|
63
|
+
const r = spawnSync('code', ['--add-mcp', adoptaiServerKey(app), entry.command, ...entry.args], {
|
|
64
|
+
encoding: 'utf8',
|
|
65
|
+
});
|
|
66
|
+
if (r.error || (r.status !== 0 && r.status !== null)) {
|
|
67
|
+
throw new Error('Could not run `code --add-mcp`. Add this server manually in VS Code MCP settings:\n' +
|
|
68
|
+
JSON.stringify({ [adoptaiServerKey(app)]: entry }, null, 2));
|
|
69
|
+
}
|
|
70
|
+
return 'VS Code (via code --add-mcp)';
|
|
71
|
+
}
|
|
72
|
+
const base = readClientConfigFile(client);
|
|
73
|
+
const mcpServers = { ...readMcpServers(client) };
|
|
74
|
+
mcpServers[adoptaiServerKey(app)] = mcpServerEntryForApp(app);
|
|
75
|
+
base.mcpServers = mcpServers;
|
|
76
|
+
writeClientConfigRaw(client, base);
|
|
77
|
+
return clientConfigPath(client) ?? '';
|
|
78
|
+
}
|
|
79
|
+
export function removeAdoptaiMcpServer(client, app) {
|
|
80
|
+
if (client === 'vscode') {
|
|
81
|
+
spawnSync('code', ['--remove-mcp', adoptaiServerKey(app)], { encoding: 'utf8' });
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
const p = clientConfigPath(client);
|
|
85
|
+
if (!p || !existsSync(p))
|
|
86
|
+
return false;
|
|
87
|
+
const base = readClientConfigFile(client);
|
|
88
|
+
const mcpServers = { ...readMcpServers(client) };
|
|
89
|
+
const key = adoptaiServerKey(app);
|
|
90
|
+
if (!mcpServers[key])
|
|
91
|
+
return false;
|
|
92
|
+
delete mcpServers[key];
|
|
93
|
+
base.mcpServers = mcpServers;
|
|
94
|
+
writeClientConfigRaw(client, base);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
export function listAdoptaiAppsInClient(client) {
|
|
98
|
+
const servers = readMcpServers(client);
|
|
99
|
+
const apps = [];
|
|
100
|
+
for (const name of Object.keys(servers)) {
|
|
101
|
+
if (!name.endsWith('-adoptai'))
|
|
102
|
+
continue;
|
|
103
|
+
const entry = servers[name];
|
|
104
|
+
if (!entry?.args || !Array.isArray(entry.args))
|
|
105
|
+
continue;
|
|
106
|
+
const i = entry.args.indexOf('--app');
|
|
107
|
+
if (i >= 0 && entry.args[i + 1]) {
|
|
108
|
+
apps.push(String(entry.args[i + 1]));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
apps.push(name.replace(/-adoptai$/, ''));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return [...new Set(apps)];
|
|
115
|
+
}
|
|
116
|
+
export function isClientId(s) {
|
|
117
|
+
return CLIENT_IDS.includes(s);
|
|
118
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
const ADOPTAI_DIR = join(homedir(), '.adoptai');
|
|
7
|
+
function getOrCreateEncryptionKey() {
|
|
8
|
+
if (!existsSync(ADOPTAI_DIR)) {
|
|
9
|
+
mkdirSync(ADOPTAI_DIR, { recursive: true, mode: 0o700 });
|
|
10
|
+
}
|
|
11
|
+
const keyPath = join(ADOPTAI_DIR, '.encryption-key');
|
|
12
|
+
if (existsSync(keyPath)) {
|
|
13
|
+
return readFileSync(keyPath, 'utf8').trim();
|
|
14
|
+
}
|
|
15
|
+
const key = randomBytes(32).toString('hex');
|
|
16
|
+
writeFileSync(keyPath, key, { mode: 0o600 });
|
|
17
|
+
return key;
|
|
18
|
+
}
|
|
19
|
+
let _store = null;
|
|
20
|
+
export function getCredentialsStore() {
|
|
21
|
+
if (!_store) {
|
|
22
|
+
_store = new Conf({
|
|
23
|
+
projectName: 'adoptai-mcp',
|
|
24
|
+
cwd: ADOPTAI_DIR,
|
|
25
|
+
configName: 'credentials',
|
|
26
|
+
encryptionKey: getOrCreateEncryptionKey(),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return _store;
|
|
30
|
+
}
|
|
31
|
+
export function hasAppCredentials(app) {
|
|
32
|
+
const store = getCredentialsStore();
|
|
33
|
+
return store.has(String(app));
|
|
34
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file auth-manager.js
|
|
3
|
+
* Unified token storage, retrieval, expiry detection, and refresh for adopt.ai MCP integrations.
|
|
4
|
+
* Storage: ~/.adoptai/<appId>_creds.json
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
const ADOPTAI_DIR = path.join(os.homedir(), '.adoptai');
|
|
12
|
+
const CREDS_SUFFIX = '_creds.json';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ensure ~/.adoptai directory exists.
|
|
16
|
+
* @returns {string} Path to .adoptai directory
|
|
17
|
+
*/
|
|
18
|
+
function ensureAdoptaiDir() {
|
|
19
|
+
if (!fs.existsSync(ADOPTAI_DIR)) {
|
|
20
|
+
fs.mkdirSync(ADOPTAI_DIR, { recursive: true, mode: 0o700 });
|
|
21
|
+
}
|
|
22
|
+
return ADOPTAI_DIR;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get path to credentials file for an app.
|
|
27
|
+
* @param {string} appId - Application id (e.g. 'github', 'slack')
|
|
28
|
+
* @returns {string} Full path to creds file
|
|
29
|
+
*/
|
|
30
|
+
function getCredsPath(appId) {
|
|
31
|
+
ensureAdoptaiDir();
|
|
32
|
+
return path.join(ADOPTAI_DIR, `${appId}${CREDS_SUFFIX}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Save credentials for an app.
|
|
37
|
+
* @param {string} appId - Application id
|
|
38
|
+
* @param {object} credObject - Credentials to store
|
|
39
|
+
* @param {string} [credObject.token] - Access token
|
|
40
|
+
* @param {string} [credObject.tokenType] - e.g. 'bearer'
|
|
41
|
+
* @param {string|null} [credObject.expiresAt] - ISO date or null if never expires
|
|
42
|
+
* @param {string|null} [credObject.refreshToken] - Refresh token if applicable
|
|
43
|
+
* @param {string[]} [credObject.scopes] - OAuth scopes
|
|
44
|
+
* @param {string} [credObject.endUserId] - 6sense end_user_id
|
|
45
|
+
* @param {object} [credObject.extra] - Any other keys (e.g. id_token)
|
|
46
|
+
*/
|
|
47
|
+
function saveCredentials(appId, credObject) {
|
|
48
|
+
if (process.env.ADOPTAI_MCP_SERVE === '1') {
|
|
49
|
+
const prev = memoryCreds.get(appId) || syntheticCredentialsFromEnv(appId) || {};
|
|
50
|
+
memoryCreds.set(appId, { ...prev, ...credObject, appId });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const credsPath = getCredsPath(appId);
|
|
55
|
+
const payload = {
|
|
56
|
+
appId,
|
|
57
|
+
token: credObject.token ?? null,
|
|
58
|
+
tokenType: credObject.tokenType ?? 'bearer',
|
|
59
|
+
expiresAt: credObject.expiresAt ?? null,
|
|
60
|
+
refreshToken: credObject.refreshToken ?? null,
|
|
61
|
+
scopes: credObject.scopes ?? [],
|
|
62
|
+
endUserId: credObject.endUserId ?? null,
|
|
63
|
+
savedAt: new Date().toISOString(),
|
|
64
|
+
...credObject,
|
|
65
|
+
};
|
|
66
|
+
fs.writeFileSync(credsPath, JSON.stringify(payload, null, 2), { mode: 0o600 });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** In-memory credentials when adoptai-mcp serve runs (refresh / session updates). */
|
|
70
|
+
const memoryCreds = new Map();
|
|
71
|
+
|
|
72
|
+
function trimEnv(v) {
|
|
73
|
+
if (typeof v !== 'string') return '';
|
|
74
|
+
return v.trim();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* When ADOPTAI_MCP_SERVE=1, prefer env-injected tokens (from ~/.adoptai credentials store)
|
|
79
|
+
* so all integration auth.js modules work without per-app cred files.
|
|
80
|
+
* @param {string} appId
|
|
81
|
+
* @returns {object|null}
|
|
82
|
+
*/
|
|
83
|
+
function syntheticCredentialsFromEnv(appId) {
|
|
84
|
+
if (process.env.ADOPTAI_MCP_SERVE !== '1') return null;
|
|
85
|
+
|
|
86
|
+
if (appId === 'github' || appId.startsWith('github-')) {
|
|
87
|
+
const token = trimEnv(process.env.GITHUB_TOKEN);
|
|
88
|
+
if (!token) return null;
|
|
89
|
+
return {
|
|
90
|
+
appId,
|
|
91
|
+
token,
|
|
92
|
+
tokenType: 'bearer',
|
|
93
|
+
expiresAt: null,
|
|
94
|
+
savedAt: new Date().toISOString(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (appId === 'notion') {
|
|
99
|
+
const token = trimEnv(process.env.NOTION_TOKEN);
|
|
100
|
+
if (!token) return null;
|
|
101
|
+
return {
|
|
102
|
+
appId,
|
|
103
|
+
token,
|
|
104
|
+
tokenType: 'bearer',
|
|
105
|
+
expiresAt: null,
|
|
106
|
+
savedAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (appId === 'figma') {
|
|
111
|
+
const token = trimEnv(process.env.FIGMA_TOKEN);
|
|
112
|
+
if (!token) return null;
|
|
113
|
+
return {
|
|
114
|
+
appId,
|
|
115
|
+
token,
|
|
116
|
+
tokenType: 'bearer',
|
|
117
|
+
expiresAt: null,
|
|
118
|
+
savedAt: new Date().toISOString(),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (appId === 'salesforce') {
|
|
123
|
+
const token = trimEnv(process.env.SALESFORCE_ACCESS_TOKEN);
|
|
124
|
+
if (!token) return null;
|
|
125
|
+
const instanceUrl = trimEnv(process.env.SALESFORCE_INSTANCE_URL);
|
|
126
|
+
const exp = trimEnv(process.env.SALESFORCE_EXPIRES_AT);
|
|
127
|
+
return {
|
|
128
|
+
appId,
|
|
129
|
+
token,
|
|
130
|
+
tokenType: 'bearer',
|
|
131
|
+
instanceUrl: instanceUrl || null,
|
|
132
|
+
refreshToken: trimEnv(process.env.SALESFORCE_REFRESH_TOKEN) || null,
|
|
133
|
+
expiresAt: exp || null,
|
|
134
|
+
savedAt: new Date().toISOString(),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (appId === 'canva') {
|
|
139
|
+
const token = trimEnv(process.env.CANVA_ACCESS_TOKEN);
|
|
140
|
+
if (!token) return null;
|
|
141
|
+
const exp = trimEnv(process.env.CANVA_EXPIRES_AT);
|
|
142
|
+
return {
|
|
143
|
+
appId,
|
|
144
|
+
token,
|
|
145
|
+
tokenType: 'bearer',
|
|
146
|
+
refreshToken: trimEnv(process.env.CANVA_REFRESH_TOKEN) || null,
|
|
147
|
+
expiresAt: exp || null,
|
|
148
|
+
scopes: [],
|
|
149
|
+
savedAt: new Date().toISOString(),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get credentials for an app.
|
|
158
|
+
* @param {string} appId - Application id
|
|
159
|
+
* @returns {object|null} Stored credentials or null if not found
|
|
160
|
+
*/
|
|
161
|
+
function getCredentials(appId) {
|
|
162
|
+
if (process.env.ADOPTAI_MCP_SERVE === '1') {
|
|
163
|
+
if (memoryCreds.has(appId)) return memoryCreds.get(appId);
|
|
164
|
+
const syn = syntheticCredentialsFromEnv(appId);
|
|
165
|
+
if (syn) {
|
|
166
|
+
memoryCreds.set(appId, syn);
|
|
167
|
+
return syn;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const credsPath = getCredsPath(appId);
|
|
172
|
+
if (!fs.existsSync(credsPath)) return null;
|
|
173
|
+
try {
|
|
174
|
+
const raw = fs.readFileSync(credsPath, 'utf8');
|
|
175
|
+
return JSON.parse(raw);
|
|
176
|
+
} catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if the token for an app is expired.
|
|
183
|
+
* @param {string} appId - Application id
|
|
184
|
+
* @returns {boolean} True if expired or missing, false if valid
|
|
185
|
+
*/
|
|
186
|
+
function isTokenExpired(appId) {
|
|
187
|
+
const creds = getCredentials(appId);
|
|
188
|
+
if (!creds || !creds.token) return true;
|
|
189
|
+
if (creds.expiresAt == null) return false;
|
|
190
|
+
try {
|
|
191
|
+
const expiresAt = new Date(creds.expiresAt).getTime();
|
|
192
|
+
return Date.now() >= expiresAt;
|
|
193
|
+
} catch {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Clear stored credentials for an app.
|
|
200
|
+
* @param {string} appId - Application id
|
|
201
|
+
*/
|
|
202
|
+
function clearCredentials(appId) {
|
|
203
|
+
if (process.env.ADOPTAI_MCP_SERVE === '1') {
|
|
204
|
+
memoryCreds.delete(appId);
|
|
205
|
+
}
|
|
206
|
+
const credsPath = getCredsPath(appId);
|
|
207
|
+
if (fs.existsSync(credsPath)) {
|
|
208
|
+
fs.unlinkSync(credsPath);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* List all adopt.ai credential files and their status.
|
|
214
|
+
* @returns {Array<{appId: string, hasToken: boolean, expired: boolean}>}
|
|
215
|
+
*/
|
|
216
|
+
function listAllCredentials() {
|
|
217
|
+
ensureAdoptaiDir();
|
|
218
|
+
const files = fs.readdirSync(ADOPTAI_DIR).filter((f) => f.endsWith(CREDS_SUFFIX));
|
|
219
|
+
return files.map((f) => {
|
|
220
|
+
const appId = f.replace(CREDS_SUFFIX, '');
|
|
221
|
+
const creds = getCredentials(appId);
|
|
222
|
+
const hasToken = !!(creds && creds.token);
|
|
223
|
+
const expired = isTokenExpired(appId);
|
|
224
|
+
return { appId, hasToken, expired };
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = {
|
|
229
|
+
ADOPTAI_DIR,
|
|
230
|
+
ensureAdoptaiDir,
|
|
231
|
+
getCredsPath,
|
|
232
|
+
saveCredentials,
|
|
233
|
+
getCredentials,
|
|
234
|
+
isTokenExpired,
|
|
235
|
+
clearCredentials,
|
|
236
|
+
listAllCredentials,
|
|
237
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file config-writer.js
|
|
3
|
+
* Writes MCP server config to supported AI clients (Cursor, Claude, Windsurf, VS Code).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
const SUPPORTED_CLIENTS = ['cursor', 'claude', 'claude-code', 'windsurf', 'vscode'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get config file path for a client. Uses platform-specific paths.
|
|
14
|
+
* @param {string} client - One of: cursor, claude, claude-code, windsurf, vscode
|
|
15
|
+
* @returns {{ path: string, isMcpJson: boolean }} Path; isMcpJson true means we read/write the `mcpServers` object (Cursor, Claude Desktop, Claude Code, etc.)
|
|
16
|
+
*/
|
|
17
|
+
function getConfigPath(client) {
|
|
18
|
+
const home = os.homedir();
|
|
19
|
+
const normalized = String(client).toLowerCase().trim();
|
|
20
|
+
if (normalized === 'cursor') {
|
|
21
|
+
return { path: path.join(home, '.cursor', 'mcp.json'), isMcpJson: true };
|
|
22
|
+
}
|
|
23
|
+
if (normalized === 'claude-code') {
|
|
24
|
+
return { path: path.join(home, '.claude.json'), isMcpJson: true };
|
|
25
|
+
}
|
|
26
|
+
if (normalized === 'claude') {
|
|
27
|
+
const base = process.platform === 'win32'
|
|
28
|
+
? path.join(process.env.APPDATA || home, 'Claude')
|
|
29
|
+
: path.join(home, 'Library', 'Application Support', 'Claude');
|
|
30
|
+
// Claude Desktop expects camelCase `mcpServers` (same as Cursor). Legacy snake_case `mcp_servers` is ignored by the app.
|
|
31
|
+
return { path: path.join(base, 'claude_desktop_config.json'), isMcpJson: true };
|
|
32
|
+
}
|
|
33
|
+
if (normalized === 'windsurf') {
|
|
34
|
+
return { path: path.join(home, '.windsurf', 'mcp.json'), isMcpJson: true };
|
|
35
|
+
}
|
|
36
|
+
if (normalized === 'vscode') {
|
|
37
|
+
return { path: path.join(home, '.vscode', 'mcp.json'), isMcpJson: true };
|
|
38
|
+
}
|
|
39
|
+
return { path: '', isMcpJson: false };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read existing config or return default structure.
|
|
44
|
+
* @param {string} configPath - Full path to config file
|
|
45
|
+
* @param {boolean} isMcpJson - True: use `mcpServers`. False: legacy `mcp_servers` only (unused for current clients).
|
|
46
|
+
* @returns {object} Config object
|
|
47
|
+
*/
|
|
48
|
+
function readConfig(configPath, isMcpJson) {
|
|
49
|
+
if (!fs.existsSync(configPath)) {
|
|
50
|
+
return isMcpJson ? { mcpServers: {} } : { mcp_servers: {} };
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
54
|
+
const data = JSON.parse(raw);
|
|
55
|
+
if (isMcpJson) {
|
|
56
|
+
if (!data.mcpServers) data.mcpServers = {};
|
|
57
|
+
const legacy = data.mcp_servers;
|
|
58
|
+
if (legacy && typeof legacy === 'object' && !Array.isArray(legacy)) {
|
|
59
|
+
data.mcpServers = { ...legacy, ...data.mcpServers };
|
|
60
|
+
delete data.mcp_servers;
|
|
61
|
+
}
|
|
62
|
+
} else if (!data.mcp_servers) {
|
|
63
|
+
data.mcp_servers = {};
|
|
64
|
+
}
|
|
65
|
+
return data;
|
|
66
|
+
} catch {
|
|
67
|
+
return isMcpJson ? { mcpServers: {} } : { mcp_servers: {} };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Write MCP server entry to AI client config.
|
|
73
|
+
* @param {object} opts
|
|
74
|
+
* @param {string} opts.client - cursor | claude | claude-code | windsurf | vscode
|
|
75
|
+
* @param {string} opts.serverName - Key for the server (e.g. 'github-adoptai')
|
|
76
|
+
* @param {string} opts.command - e.g. 'node'
|
|
77
|
+
* @param {string[]} opts.args - e.g. ['/path/to/index.js']
|
|
78
|
+
* @param {object} [opts.env] - Env vars for the server
|
|
79
|
+
* @returns {string} Human-readable integration name for success message
|
|
80
|
+
*/
|
|
81
|
+
function writeConfig({ client, serverName, command, args, env }) {
|
|
82
|
+
const normalized = String(client).toLowerCase().trim();
|
|
83
|
+
if (!SUPPORTED_CLIENTS.includes(normalized)) {
|
|
84
|
+
const msg = `Invalid client "${client}". Supported: ${SUPPORTED_CLIENTS.join(', ')}`;
|
|
85
|
+
throw new Error(msg);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const { path: configPath, isMcpJson } = getConfigPath(normalized);
|
|
89
|
+
const dir = path.dirname(configPath);
|
|
90
|
+
if (!fs.existsSync(dir)) {
|
|
91
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const config = readConfig(configPath, isMcpJson);
|
|
95
|
+
const servers = isMcpJson ? config.mcpServers : config.mcp_servers;
|
|
96
|
+
const entry = { command, args, env: env || {} };
|
|
97
|
+
if (normalized === 'claude-code') {
|
|
98
|
+
entry.type = 'stdio';
|
|
99
|
+
}
|
|
100
|
+
servers[serverName] = entry;
|
|
101
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
102
|
+
|
|
103
|
+
const label = serverName.replace(/-adoptai$/, '');
|
|
104
|
+
return label.charAt(0).toUpperCase() + label.slice(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Remove an MCP server entry from client config.
|
|
109
|
+
* @param {object} opts
|
|
110
|
+
* @param {string} opts.client - cursor | claude | claude-code | windsurf | vscode
|
|
111
|
+
* @param {string} opts.serverName - Key to remove (e.g. 'github-adoptai')
|
|
112
|
+
*/
|
|
113
|
+
function removeConfig({ client, serverName }) {
|
|
114
|
+
const normalized = String(client).toLowerCase().trim();
|
|
115
|
+
if (!SUPPORTED_CLIENTS.includes(normalized)) {
|
|
116
|
+
throw new Error(`Invalid client "${client}". Supported: ${SUPPORTED_CLIENTS.join(', ')}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { path: configPath, isMcpJson } = getConfigPath(normalized);
|
|
120
|
+
if (!fs.existsSync(configPath)) return;
|
|
121
|
+
|
|
122
|
+
const config = readConfig(configPath, isMcpJson);
|
|
123
|
+
const servers = isMcpJson ? config.mcpServers : config.mcp_servers;
|
|
124
|
+
delete servers[serverName];
|
|
125
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* List adopt.ai MCP entries for a client (entries whose key ends with -adoptai or contains adoptai).
|
|
130
|
+
* @param {object} opts
|
|
131
|
+
* @param {string} opts.client - cursor | claude | claude-code | windsurf | vscode
|
|
132
|
+
* @returns {Array<{ name: string, command: string, args: string[] }>}
|
|
133
|
+
*/
|
|
134
|
+
function listConfigs({ client }) {
|
|
135
|
+
const normalized = String(client).toLowerCase().trim();
|
|
136
|
+
if (!SUPPORTED_CLIENTS.includes(normalized)) {
|
|
137
|
+
throw new Error(`Invalid client "${client}". Supported: ${SUPPORTED_CLIENTS.join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { path: configPath, isMcpJson } = getConfigPath(normalized);
|
|
141
|
+
if (!fs.existsSync(configPath)) return [];
|
|
142
|
+
|
|
143
|
+
const config = readConfig(configPath, isMcpJson);
|
|
144
|
+
const servers = isMcpJson ? config.mcpServers : config.mcp_servers;
|
|
145
|
+
return Object.entries(servers)
|
|
146
|
+
.filter(([name]) => /adoptai/i.test(name))
|
|
147
|
+
.map(([name, entry]) => ({
|
|
148
|
+
name,
|
|
149
|
+
command: entry.command || '',
|
|
150
|
+
args: entry.args || [],
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
SUPPORTED_CLIENTS,
|
|
156
|
+
getConfigPath,
|
|
157
|
+
readConfig,
|
|
158
|
+
writeConfig,
|
|
159
|
+
removeConfig,
|
|
160
|
+
listConfigs,
|
|
161
|
+
};
|