clay-server 5.2.0-beta.3 → 5.2.0-beta.5
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/lib/account-auth-socket.js +1 -0
- package/lib/builtin-mates.js +19 -16
- package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
- package/lib/canonical-clay-record-authority.js +15 -0
- package/lib/canonical-clay-scheduling-authority.js +19 -0
- package/lib/claude-hook-installer.js +28 -0
- package/lib/clay-mate-chat-receipts.js +26 -0
- package/lib/clay-mate-chat.js +87 -0
- package/lib/clay-mate-knowledge-input.js +26 -0
- package/lib/clay-mate-knowledge.js +95 -0
- package/lib/clay-mate-lifecycle.js +95 -0
- package/lib/clay-product-control-input.js +33 -0
- package/lib/clay-product-control-tools.js +51 -0
- package/lib/clay-product-control.js +237 -0
- package/lib/clay-project-lifecycle.js +154 -0
- package/lib/clay-project-record-input.js +44 -0
- package/lib/clay-project-records.js +83 -0
- package/lib/clay-scheduling-input.js +21 -0
- package/lib/clay-scheduling-receipts.js +26 -0
- package/lib/clay-scheduling.js +119 -0
- package/lib/clay-screen-controls.js +96 -0
- package/lib/clay-workspace-controls.js +83 -0
- package/lib/clay-workspace-input.js +17 -0
- package/lib/clay-workspace-receipts.js +26 -0
- package/lib/daemon-project-access.js +11 -0
- package/lib/daemon-project-lifecycle-callbacks.js +15 -0
- package/lib/daemon-project-lifecycle.js +262 -0
- package/lib/daemon-projects.js +1 -1
- package/lib/daemon.js +73 -221
- package/lib/issues-service.js +29 -15
- package/lib/knowledge-db-import.js +5 -4
- package/lib/knowledge-db-service.js +9 -6
- package/lib/knowledge-document-index.js +2 -1
- package/lib/knowledge-file-store.js +7 -1
- package/lib/knowledge-product-operations.js +46 -0
- package/lib/mate-avatar-catalog.js +12 -0
- package/lib/mate-avatar-migration.js +41 -0
- package/lib/mate-creation-mcp-server.js +2 -0
- package/lib/mate-knowledge-canonical-access.js +10 -0
- package/lib/mate-knowledge-service.js +7 -4
- package/lib/mate-lifecycle-service.js +94 -0
- package/lib/mate-message-delivery.js +43 -0
- package/lib/mate-ready-creation.js +9 -1
- package/lib/mate-schedule-input.js +29 -0
- package/lib/mate-schedule-lifecycle.js +151 -0
- package/lib/mate-scheduling.js +103 -0
- package/lib/mates.js +18 -6
- package/lib/mcp-agent-tools.js +117 -0
- package/lib/mcp-connection-store.js +119 -15
- package/lib/mcp-import-credentials.js +31 -0
- package/lib/mcp-local-transport.js +13 -0
- package/lib/mcp-oauth.js +8 -6
- package/lib/mcp-query-bridge.js +30 -0
- package/lib/mcp-remote-connections.js +74 -20
- package/lib/mcp-source-adapters.js +91 -0
- package/lib/mcp-source-context.js +42 -0
- package/lib/mcp-source-discovery.js +169 -0
- package/lib/mcp-source-plugins.js +68 -0
- package/lib/mcp-source-restrictions.js +47 -0
- package/lib/mcp-tool-policy.js +34 -0
- package/lib/notes-checklist.js +50 -0
- package/lib/notes.js +49 -3
- package/lib/os-users.js +20 -5
- package/lib/project-admin-overview.js +51 -0
- package/lib/project-ask-user.js +8 -4
- package/lib/project-debate.js +1 -0
- package/lib/project-delegated-follow-up.js +2 -1
- package/lib/project-directory-claim.js +65 -0
- package/lib/project-file-operations.js +61 -0
- package/lib/project-filesystem.js +13 -87
- package/lib/project-full-access.js +23 -2
- package/lib/project-knowledge.js +32 -18
- package/lib/project-lifecycle-operations.js +98 -0
- package/lib/project-lifecycle-process-witness.js +32 -0
- package/lib/project-lifecycle-runtime.js +50 -0
- package/lib/project-linux-access.js +22 -0
- package/lib/project-logs-canonical-clay.js +27 -0
- package/lib/project-logs-clay-query.js +16 -0
- package/lib/project-logs-schema.js +4 -2
- package/lib/project-logs-service.js +8 -7
- package/lib/project-logs.js +1 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-mate-chat-control.js +55 -0
- package/lib/project-mate-creation-proposal.js +18 -3
- package/lib/project-mate-interaction.js +8 -0
- package/lib/project-mcp.js +54 -16
- package/lib/project-models.js +14 -31
- package/lib/project-notifications.js +74 -16
- package/lib/project-ownership.js +77 -0
- package/lib/project-pair-result-delivery.js +14 -3
- package/lib/project-pending-message-queue.js +1 -0
- package/lib/project-product-control.js +87 -0
- package/lib/project-product-session-queue.js +46 -0
- package/lib/project-push-access.js +28 -0
- package/lib/project-scheduled-message-queue.js +30 -0
- package/lib/project-scheduled-messages.js +63 -64
- package/lib/project-scheduled-task-control.js +83 -0
- package/lib/project-scheduled-tasks.js +13 -2
- package/lib/project-scheduling-access.js +10 -0
- package/lib/project-session-actions.js +65 -0
- package/lib/project-session-delete.js +8 -1
- package/lib/project-session-document.js +8 -3
- package/lib/project-session-github.js +63 -19
- package/lib/project-session-history-actions.js +165 -0
- package/lib/project-session-linear.js +14 -6
- package/lib/project-session-notes.js +7 -3
- package/lib/project-session-runtime-actions.js +63 -0
- package/lib/project-session-vendor.js +54 -0
- package/lib/project-sessions.js +68 -241
- package/lib/project-shared-browser.js +56 -12
- package/lib/project-user-message.js +30 -1
- package/lib/project-workspace-operations.js +126 -0
- package/lib/project-workspace-query.js +9 -2
- package/lib/project.js +144 -37
- package/lib/public/app.js +5 -0
- package/lib/public/appearance-bootstrap.js +14 -0
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/clay-workspace.css +29 -0
- package/lib/public/css/home-debate-planning.css +2 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- package/lib/public/css/mate-avatar-gallery.css +12 -0
- package/lib/public/css/mate-conversation.css +10 -5
- package/lib/public/css/overlays.css +4 -2
- package/lib/public/css/project-admin-overview.css +13 -0
- package/lib/public/css/session-folders.css +11 -0
- package/lib/public/css/session-github.css +46 -1
- package/lib/public/css/shared-browser.css +35 -1
- package/lib/public/css/sticky-notes.css +61 -1
- package/lib/public/index.html +11 -5
- package/lib/public/mate-avatars/B01.png +0 -0
- package/lib/public/mate-avatars/B02.png +0 -0
- package/lib/public/mate-avatars/B03.png +0 -0
- package/lib/public/mate-avatars/B04.png +0 -0
- package/lib/public/mate-avatars/B05.png +0 -0
- package/lib/public/mate-avatars/B06.png +0 -0
- package/lib/public/mate-avatars/B07.png +0 -0
- package/lib/public/mate-avatars/B08.png +0 -0
- package/lib/public/mate-avatars/B09.png +0 -0
- package/lib/public/mate-avatars/B10.png +0 -0
- package/lib/public/mate-avatars/B11.png +0 -0
- package/lib/public/mate-avatars/B12.png +0 -0
- package/lib/public/mate-avatars/B13.png +0 -0
- package/lib/public/mate-avatars/B14.png +0 -0
- package/lib/public/mate-avatars/B15.png +0 -0
- package/lib/public/mate-avatars/B16.png +0 -0
- package/lib/public/mate-avatars/B17.png +0 -0
- package/lib/public/mate-avatars/B18.png +0 -0
- package/lib/public/mate-avatars/B19.png +0 -0
- package/lib/public/mate-avatars/B20.png +0 -0
- package/lib/public/mate-avatars/B21.png +0 -0
- package/lib/public/mate-avatars/B22.png +0 -0
- package/lib/public/mate-avatars/B23.png +0 -0
- package/lib/public/mate-avatars/B24.png +0 -0
- package/lib/public/mate-avatars/B25.png +0 -0
- package/lib/public/mate-avatars/B26.png +0 -0
- package/lib/public/mate-avatars/B27.png +0 -0
- package/lib/public/mate-avatars/B28.png +0 -0
- package/lib/public/mate-avatars/B29.png +0 -0
- package/lib/public/mate-avatars/B30.png +0 -0
- package/lib/public/mate-avatars/B31.png +0 -0
- package/lib/public/mate-avatars/B32.png +0 -0
- package/lib/public/mate-avatars/B33.png +0 -0
- package/lib/public/mate-avatars/B34.png +0 -0
- package/lib/public/mate-avatars/B35.png +0 -0
- package/lib/public/mate-avatars/B36.png +0 -0
- package/lib/public/mate-avatars/B37.png +0 -0
- package/lib/public/mate-avatars/B38.png +0 -0
- package/lib/public/mate-avatars/B39.png +0 -0
- package/lib/public/mate-avatars/B40.png +0 -0
- package/lib/public/mate-avatars/B41.png +0 -0
- package/lib/public/mate-avatars/B42.png +0 -0
- package/lib/public/mate-avatars/B43.png +0 -0
- package/lib/public/mate-avatars/B44.png +0 -0
- package/lib/public/mate-avatars/B45.png +0 -0
- package/lib/public/mate-avatars/B46.png +0 -0
- package/lib/public/mate-avatars/B47.png +0 -0
- package/lib/public/mate-avatars/B48.png +0 -0
- package/lib/public/mate-avatars/C01.png +0 -0
- package/lib/public/mate-avatars/C02.png +0 -0
- package/lib/public/mate-avatars/C03.png +0 -0
- package/lib/public/mate-avatars/C04.png +0 -0
- package/lib/public/mate-avatars/C05.png +0 -0
- package/lib/public/mate-avatars/C06.png +0 -0
- package/lib/public/mate-avatars/C07.png +0 -0
- package/lib/public/mate-avatars/C08.png +0 -0
- package/lib/public/mate-avatars/C09.png +0 -0
- package/lib/public/mate-avatars/C10.png +0 -0
- package/lib/public/mate-avatars/C11.png +0 -0
- package/lib/public/mate-avatars/C12.png +0 -0
- package/lib/public/mate-avatars/C13.png +0 -0
- package/lib/public/mate-avatars/C14.png +0 -0
- package/lib/public/mate-avatars/C15.png +0 -0
- package/lib/public/mate-avatars/C16.png +0 -0
- package/lib/public/mate-avatars/C17.png +0 -0
- package/lib/public/mate-avatars/C18.png +0 -0
- package/lib/public/mate-avatars/C19.png +0 -0
- package/lib/public/mate-avatars/C20.png +0 -0
- package/lib/public/mate-avatars/C21.png +0 -0
- package/lib/public/mate-avatars/C22.png +0 -0
- package/lib/public/mate-avatars/C23.png +0 -0
- package/lib/public/mate-avatars/C24.png +0 -0
- package/lib/public/mate-avatars/C25.png +0 -0
- package/lib/public/mate-avatars/C26.png +0 -0
- package/lib/public/mate-avatars/C27.png +0 -0
- package/lib/public/mate-avatars/C28.png +0 -0
- package/lib/public/mate-avatars/C29.png +0 -0
- package/lib/public/mate-avatars/C30.png +0 -0
- package/lib/public/mate-avatars/C31.png +0 -0
- package/lib/public/mate-avatars/C32.png +0 -0
- package/lib/public/mate-avatars/C33.png +0 -0
- package/lib/public/mate-avatars/C34.png +0 -0
- package/lib/public/mate-avatars/C35.png +0 -0
- package/lib/public/mate-avatars/C36.png +0 -0
- package/lib/public/mate-avatars/C37.png +0 -0
- package/lib/public/mate-avatars/C38.png +0 -0
- package/lib/public/mate-avatars/C39.png +0 -0
- package/lib/public/mate-avatars/C40.png +0 -0
- package/lib/public/mate-avatars/C41.png +0 -0
- package/lib/public/mate-avatars/C42.png +0 -0
- package/lib/public/mate-avatars/C43.png +0 -0
- package/lib/public/mate-avatars/C44.png +0 -0
- package/lib/public/mate-avatars/C45.png +0 -0
- package/lib/public/mate-avatars/C46.png +0 -0
- package/lib/public/mate-avatars/C47.png +0 -0
- package/lib/public/mate-avatars/C48.png +0 -0
- package/lib/public/mate-avatars/catalog.json +482 -0
- package/lib/public/mate-avatars/lead-clay.png +0 -0
- package/lib/public/modules/admin-settings-projects.js +6 -2
- package/lib/public/modules/app-connection.js +8 -1
- package/lib/public/modules/app-message-router.js +4 -0
- package/lib/public/modules/app-messages.js +31 -5
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/app-rendering.js +1 -1
- package/lib/public/modules/avatar.js +15 -41
- package/lib/public/modules/chat-render-runtime.js +10 -1
- package/lib/public/modules/clay-browser-links.js +47 -0
- package/lib/public/modules/clay-log-links.js +1 -1
- package/lib/public/modules/clay-owner-navigation.js +27 -0
- package/lib/public/modules/clay-tool-activity.js +140 -0
- package/lib/public/modules/clay-tool-presentation.js +338 -0
- package/lib/public/modules/command-palette.js +2 -2
- package/lib/public/modules/debate.js +1 -0
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/history-replay-batch.js +5 -1
- package/lib/public/modules/home-debate-live.js +2 -0
- package/lib/public/modules/home-debate-planning.js +7 -7
- package/lib/public/modules/home-mate-avatar-editor.js +127 -208
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/home-mate-settings.js +19 -12
- package/lib/public/modules/markdown.js +4 -0
- package/lib/public/modules/mate-avatar-catalog.js +97 -0
- package/lib/public/modules/mate-management.js +40 -7
- package/lib/public/modules/mcp-connection-view.js +31 -13
- package/lib/public/modules/mcp-discovery-view.js +31 -0
- package/lib/public/modules/mcp-ui.js +23 -4
- package/lib/public/modules/mention.js +3 -0
- package/lib/public/modules/pane-browser-bridge.js +20 -0
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/profile.js +1 -238
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/project-mate-navigation.js +99 -14
- package/lib/public/modules/search-clay-chat.js +16 -2
- package/lib/public/modules/session-folder-layout.js +1 -1
- package/lib/public/modules/session-folder-view-menu.js +9 -0
- package/lib/public/modules/session-github.js +85 -14
- package/lib/public/modules/session-work-primary.js +51 -0
- package/lib/public/modules/shared-browser-settings.js +112 -0
- package/lib/public/modules/shared-browser-tabs.js +13 -3
- package/lib/public/modules/shared-browser.js +27 -6
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/split-view.js +2 -0
- package/lib/public/modules/sticky-note-markdown.js +72 -6
- package/lib/public/modules/sticky-notes-actions.js +30 -0
- package/lib/public/modules/sticky-notes-browser.js +9 -2
- package/lib/public/modules/sticky-notes-card.js +23 -7
- package/lib/public/modules/sticky-notes-checklist.js +71 -0
- package/lib/public/modules/sticky-notes-editor.js +108 -44
- package/lib/public/modules/sticky-notes-shared.js +50 -2
- package/lib/public/modules/sticky-notes-state.js +66 -0
- package/lib/public/modules/sticky-notes.js +34 -51
- package/lib/public/modules/theme.js +8 -1
- package/lib/public/modules/thinking-lifecycle.js +33 -3
- package/lib/public/modules/thinking-view.js +23 -1
- package/lib/public/modules/tools.js +36 -7
- package/lib/public/modules/user-settings-appearance.js +10 -3
- package/lib/public/modules/user-settings-editors.js +1 -1
- package/lib/public/modules/user-settings-layout.js +11 -2
- package/lib/public/modules/user-settings-preferences.js +6 -2
- package/lib/public/modules/user-settings-profile.js +12 -2
- package/lib/public/modules/user-settings.js +3 -2
- package/lib/public/style.css +3 -0
- package/lib/public/sw.js +3 -1
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +228 -23
- package/lib/sdk-message-processor.js +2 -0
- package/lib/sdk-response-recovery.js +155 -0
- package/lib/server-admin.js +63 -16
- package/lib/server-appearance-settings.js +13 -1
- package/lib/server-dm.js +2 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +18 -4
- package/lib/server-home-chat.js +12 -16
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +14 -0
- package/lib/server-mates.js +25 -62
- package/lib/server-owner-ui.js +77 -0
- package/lib/server-path-access.js +38 -0
- package/lib/server-project-removal.js +22 -0
- package/lib/server-settings.js +4 -1
- package/lib/server.js +144 -41
- package/lib/session-config-transition.js +45 -0
- package/lib/session-folders.js +7 -2
- package/lib/session-github.js +9 -2
- package/lib/session-visibility.js +5 -2
- package/lib/session-work-primary.js +50 -0
- package/lib/sessions.js +13 -0
- package/lib/shared-browser-cookie-sync.js +32 -0
- package/lib/shared-browser-cookies.js +79 -0
- package/lib/shared-browser-runtime.js +25 -9
- package/lib/shared-browser-storage-sync.js +43 -0
- package/lib/shared-browser-storage.js +101 -0
- package/lib/shared-browser-tools.js +2 -1
- package/lib/shared-browser-worker.js +46 -10
- package/lib/terminal-manager.js +52 -0
- package/lib/users-permissions.js +2 -8
- package/lib/users.js +4 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +8 -2
- package/lib/yoke/acp-agent-profiles.js +2 -2
- package/lib/yoke/adapters/claude.js +47 -8
- package/lib/yoke/adapters/codex.js +52 -111
- package/lib/yoke/claude-worker-startup.js +28 -0
- package/lib/yoke/codex-idle-recovery.js +81 -1
- package/lib/yoke/codex-recovery-admission.js +164 -0
- package/lib/yoke/session-tool-transport.js +3 -0
- package/package.json +5 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function requiredNames(row) {
|
|
2
|
+
var names = (row.environmentNames || []).filter(function (name) { return !Object.prototype.hasOwnProperty.call(row.env || {}, name); });
|
|
3
|
+
(row.references || []).forEach(function (reference) {
|
|
4
|
+
var match = /^(?:\$\{?|\{env:)([A-Za-z_][A-Za-z0-9_]*)/.exec(reference);
|
|
5
|
+
var name = match ? match[1] : /^[A-Za-z_][A-Za-z0-9_]*$/.test(reference) ? reference : 'Unsupported reference';
|
|
6
|
+
names.push(name);
|
|
7
|
+
});
|
|
8
|
+
return Array.from(new Set(names));
|
|
9
|
+
}
|
|
10
|
+
function applyCredentials(row, values) {
|
|
11
|
+
var names = requiredNames(row);
|
|
12
|
+
if (names.indexOf('Unsupported reference') >= 0) throw new Error('This source requires a vendor-specific helper or file reference. Configure a direct Clay connection instead.');
|
|
13
|
+
names.forEach(function (name) {
|
|
14
|
+
if (!Object.prototype.hasOwnProperty.call(values, name) || typeof values[name] !== 'string' || !values[name] || values[name].length > 8192 || /[\r\n\0]/.test(values[name])) throw new Error('Enter each required credential value.');
|
|
15
|
+
});
|
|
16
|
+
function expand(value) {
|
|
17
|
+
if (typeof value === 'string') return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-[^}]*)?\}|\$([A-Za-z_][A-Za-z0-9_]*)|\{env:([^}]+)\}/g, function (all, first, second, third) {
|
|
18
|
+
var name = first || second || third; return Object.prototype.hasOwnProperty.call(values, name) ? values[name] : all;
|
|
19
|
+
});
|
|
20
|
+
if (Array.isArray(value)) return value.map(expand);
|
|
21
|
+
if (value && typeof value === 'object') { var next = {}; Object.keys(value).forEach(function (key) { next[key] = expand(value[key]); }); return next; }
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
var next = {};
|
|
25
|
+
['command', 'args', 'env', 'headers', 'url'].forEach(function (field) { if (row[field] !== undefined) next[field] = expand(row[field]); });
|
|
26
|
+
next.headers = next.headers || {}; next.env = next.env || {};
|
|
27
|
+
Object.keys(row.envHeaders || {}).forEach(function (name) { next.headers[name] = (name === 'Authorization' && row.bearerTokenEnv === row.envHeaders[name] ? 'Bearer ' : '') + values[row.envHeaders[name]]; });
|
|
28
|
+
(row.environmentNames || []).forEach(function (name) { if (values[name] !== undefined) next.env[name] = values[name]; });
|
|
29
|
+
next.envHeaders = {}; next.references = []; return next;
|
|
30
|
+
}
|
|
31
|
+
module.exports = { requiredNames: requiredNames, applyCredentials: applyCredentials };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
var StdioClientTransport = require('@modelcontextprotocol/sdk/client/stdio.js').StdioClientTransport;
|
|
2
|
+
var wrapSpawnAsUser = require('./os-users').wrapSpawnAsUser;
|
|
3
|
+
var os = require('os');
|
|
4
|
+
function localTransport(row, identity) {
|
|
5
|
+
if ((row.references || []).length || (row.environmentNames || []).some(function (name) { return !Object.prototype.hasOwnProperty.call(row.env || {}, name); })) throw new Error('Configure required environment values before connecting.');
|
|
6
|
+
var home = identity ? identity.home : os.homedir();
|
|
7
|
+
var env = Object.assign({ HOME: home, USER: identity ? identity.user : os.userInfo().username, LOGNAME: identity ? identity.user : os.userInfo().username, SHELL: identity ? identity.shell || '/bin/sh' : '/bin/sh', PATH: '/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin' }, row.env || {});
|
|
8
|
+
var wrapped = wrapSpawnAsUser(row.command, row.args, identity ? { uid: identity.uid, gid: identity.gid, env: env } : { env: env });
|
|
9
|
+
// The SDK cannot apply uid/gid itself; never fall back to spawning as the daemon.
|
|
10
|
+
if (wrapped.options.uid != null || wrapped.options.gid != null) throw new Error('Install util-linux setpriv to run MCP as the account OS user.');
|
|
11
|
+
return new StdioClientTransport({ command: wrapped.command, args: wrapped.args, env: env, cwd: row.cwd, stderr: 'ignore' });
|
|
12
|
+
}
|
|
13
|
+
module.exports = { localTransport: localTransport };
|
package/lib/mcp-oauth.js
CHANGED
|
@@ -2,21 +2,21 @@ var crypto = require('crypto');
|
|
|
2
2
|
var http = require('http');
|
|
3
3
|
var assertPublicUrl = require('./mcp-network').assertPublicUrl;
|
|
4
4
|
|
|
5
|
-
function createOAuthProvider(record, persist, flow) {
|
|
5
|
+
function createOAuthProvider(record, persist, flow, authorize) {
|
|
6
6
|
record.auth = record.auth || {};
|
|
7
7
|
var auth = record.auth;
|
|
8
|
-
function guard() { if (flow && flow.cancelled) throw new Error('Sign-in expired.'); }
|
|
8
|
+
function guard() { if (flow && flow.cancelled) throw new Error('Sign-in expired.'); if (authorize) authorize(); }
|
|
9
9
|
function save() { persist(); }
|
|
10
10
|
return {
|
|
11
11
|
get redirectUrl() { return flow ? flow.redirectUrl : auth.redirectUrl; },
|
|
12
12
|
get clientMetadata() { return { client_name: 'Clay', redirect_uris: [flow ? flow.redirectUrl : auth.redirectUrl],
|
|
13
13
|
grant_types: ['authorization_code', 'refresh_token'], response_types: ['code'], token_endpoint_auth_method: 'none' }; },
|
|
14
14
|
state: function () { return flow ? flow.state : crypto.randomBytes(32).toString('hex'); },
|
|
15
|
-
clientInformation: function () { return auth.client; },
|
|
15
|
+
clientInformation: function () { guard(); return auth.client; },
|
|
16
16
|
saveClientInformation: function (value) { guard(); auth.client = value; save(); },
|
|
17
|
-
tokens: function () { return auth.tokens; },
|
|
17
|
+
tokens: function () { guard(); return auth.tokens; },
|
|
18
18
|
saveTokens: function (value) { guard(); auth.tokens = value; save(); },
|
|
19
|
-
saveCodeVerifier: function (value) { if (flow) flow.verifier = value; },
|
|
19
|
+
saveCodeVerifier: function (value) { guard(); if (flow) flow.verifier = value; },
|
|
20
20
|
codeVerifier: function () { if (!flow || !flow.verifier) throw new Error('Sign in again.'); return flow.verifier; },
|
|
21
21
|
redirectToAuthorization: async function (url) {
|
|
22
22
|
if (!flow) { var required = new Error('Sign in to connect this server.'); required.name = 'UnauthorizedError'; throw required; }
|
|
@@ -25,13 +25,15 @@ function createOAuthProvider(record, persist, flow) {
|
|
|
25
25
|
if (url.searchParams.get('state') !== flow.state || url.searchParams.get('redirect_uri') !== flow.redirectUrl || !url.searchParams.get('code_challenge')) throw new Error('Invalid sign-in request.');
|
|
26
26
|
flow.authorizationUrl = url.href;
|
|
27
27
|
},
|
|
28
|
-
discoveryState: function () { return auth.discovery; },
|
|
28
|
+
discoveryState: function () { guard(); return auth.discovery; },
|
|
29
29
|
saveDiscoveryState: function (value) {
|
|
30
|
+
guard();
|
|
30
31
|
// Bind client credentials to the discovered issuer.
|
|
31
32
|
if (auth.discovery && auth.discovery.authorizationServerUrl !== value.authorizationServerUrl) { delete auth.client; delete auth.tokens; }
|
|
32
33
|
guard(); auth.discovery = value; save();
|
|
33
34
|
},
|
|
34
35
|
invalidateCredentials: function (which) {
|
|
36
|
+
guard();
|
|
35
37
|
if (which === 'all' || which === 'client') delete auth.client;
|
|
36
38
|
if (which === 'all' || which === 'tokens') delete auth.tokens;
|
|
37
39
|
if (which === 'all' || which === 'discovery') delete auth.discovery;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
var descriptors = require('./sdk-mcp-descriptors').extractMcpDescriptors;
|
|
2
|
+
function createQueryBridge(ctx) {
|
|
3
|
+
function valid() {
|
|
4
|
+
if (!ctx.valid()) throw new Error('MCP tools require the current authorized query.');
|
|
5
|
+
}
|
|
6
|
+
function list() {
|
|
7
|
+
if (!ctx.valid()) return [];
|
|
8
|
+
var servers = descriptors(ctx.getServers()) || [], tools = [];
|
|
9
|
+
servers.forEach(function (server) {
|
|
10
|
+
server.tools.forEach(function (tool) { tools.push(Object.assign({ server: server.serverName }, tool)); });
|
|
11
|
+
});
|
|
12
|
+
return tools;
|
|
13
|
+
}
|
|
14
|
+
async function call(serverName, toolName, args) {
|
|
15
|
+
valid();
|
|
16
|
+
var server = ctx.getServers()[serverName], tool = server && server.instance && server.instance._registeredTools[toolName];
|
|
17
|
+
if (!tool || typeof (tool.handler || tool.callback) !== 'function') throw new Error('MCP tool is unavailable.');
|
|
18
|
+
var result = await (tool.handler || tool.callback)(args || {});
|
|
19
|
+
valid(); return result;
|
|
20
|
+
}
|
|
21
|
+
function dynamicDefs() {
|
|
22
|
+
return list().map(function (tool) {
|
|
23
|
+
return { name: 'mcp__' + tool.server + '__' + tool.name, permissionName: 'mcp__' + tool.server + '__' + tool.name,
|
|
24
|
+
description: tool.description, inputSchema: tool.inputSchema, queryBound: true,
|
|
25
|
+
handler: function (args) { return call(tool.server, tool.name, args); } };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return { list: list, call: call, dynamicDefs: dynamicDefs };
|
|
29
|
+
}
|
|
30
|
+
module.exports = { createQueryBridge: createQueryBridge };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
var crypto = require('crypto');
|
|
2
2
|
var Client = require('@modelcontextprotocol/sdk/client/index.js').Client;
|
|
3
|
+
var SSEClientTransport = require('@modelcontextprotocol/sdk/client/sse.js').SSEClientTransport;
|
|
4
|
+
var localTransport = require('./mcp-local-transport').localTransport;
|
|
3
5
|
var StreamableHTTPClientTransport = require('@modelcontextprotocol/sdk/client/streamableHttp.js').StreamableHTTPClientTransport;
|
|
4
6
|
var network = require('./mcp-network');
|
|
5
7
|
var oauth = require('./mcp-oauth');
|
|
@@ -9,12 +11,33 @@ function createRemoteConnections(ctx) {
|
|
|
9
11
|
var accounts = new Map(), live = new Map(), flows = new Map(), queues = new Map();
|
|
10
12
|
var remoteFetch = ctx.remoteFetch || network.createRemoteFetch();
|
|
11
13
|
var closed = false;
|
|
14
|
+
function sameRuntimeConfig(left, right) {
|
|
15
|
+
return left && right && left.url === right.url && left.enabled === right.enabled && left.authMode === right.authMode && left.token === right.token && JSON.stringify(left.auth || null) === JSON.stringify(right.auth || null) && JSON.stringify([left.transport, left.command, left.args, left.env, left.cwd, left.headers, left.envHeaders, left.references, left.disabledTools, left.enabledTools, left.toolPatterns, left.toolPrefix]) === JSON.stringify([right.transport, right.command, right.args, right.env, right.cwd, right.headers, right.envHeaders, right.references, right.disabledTools, right.enabledTools, right.toolPatterns, right.toolPrefix]);
|
|
16
|
+
}
|
|
17
|
+
function invalidate(owner, previous, current) {
|
|
18
|
+
var byId = new Map(current.filter(function (row) { return row.source !== 'computer'; }).map(function (row) { return [row.id, row]; }));
|
|
19
|
+
previous.filter(function (row) { return row.source !== 'computer'; }).forEach(function (row) {
|
|
20
|
+
var next = byId.get(row.id);
|
|
21
|
+
if (!next || !sameRuntimeConfig(row, next)) disconnect(owner, row.id);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
12
24
|
function records(owner) {
|
|
13
25
|
ctx.authorize(owner);
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
var account = accounts.get(owner), snapshot = typeof ctx.storage.snapshot === 'function' ? ctx.storage.snapshot(owner) : { records: ctx.storage.load(owner), revision: typeof ctx.storage.revision === 'function' ? ctx.storage.revision(owner) : null };
|
|
27
|
+
var loaded = snapshot.records, revision = snapshot.revision;
|
|
28
|
+
if (!account) accounts.set(owner, account = { records: loaded, revision: revision });
|
|
29
|
+
else if (account.revision !== revision) { invalidate(owner, account.records, loaded); account.records = loaded; account.revision = revision; }
|
|
30
|
+
return account.records;
|
|
31
|
+
}
|
|
32
|
+
function persist(owner) {
|
|
33
|
+
ctx.authorize(owner); if (closed) throw new Error('MCP connections closed.');
|
|
34
|
+
var account = accounts.get(owner);
|
|
35
|
+
if (!account) throw new Error('MCP connection snapshot is unavailable.');
|
|
36
|
+
var revision;
|
|
37
|
+
try { revision = ctx.storage.save(owner, account.records, account.revision); }
|
|
38
|
+
catch (error) { accounts.delete(owner); invalidate(owner, account.records, []); throw error; }
|
|
39
|
+
if (account) account.revision = revision;
|
|
16
40
|
}
|
|
17
|
-
function persist(owner) { ctx.authorize(owner); if (closed) throw new Error('MCP connections closed.'); ctx.storage.save(owner, records(owner)); }
|
|
18
41
|
function find(owner, id) {
|
|
19
42
|
var row = records(owner).find(function (item) { return item.id === id; });
|
|
20
43
|
if (!row) throw new Error('Connection is unavailable.');
|
|
@@ -23,14 +46,15 @@ function createRemoteConnections(ctx) {
|
|
|
23
46
|
function key(owner, id) { return JSON.stringify([owner, id]); }
|
|
24
47
|
function check(owner, row) {
|
|
25
48
|
if (closed || find(owner, row.id) !== row) throw new Error('Connection changed.');
|
|
49
|
+
if (row.source === 'local' && ctx.ownerIdentity) ctx.ownerIdentity(owner);
|
|
26
50
|
}
|
|
27
51
|
function changed(owner) { if (!closed) ctx.changed(owner); }
|
|
28
52
|
function view(owner) {
|
|
29
|
-
return records(owner).filter(function (row) { return row.source
|
|
53
|
+
return records(owner).filter(function (row) { return row.source !== 'computer'; }).map(function (row) {
|
|
30
54
|
var entry = live.get(key(owner, row.id));
|
|
31
|
-
return { id: row.id, name: row.id, label: row.label, source:
|
|
55
|
+
return { id: row.id, name: row.id, label: row.label, source: row.source, transport: row.transport || 'http', url: row.url ? new URL(row.url).origin + new URL(row.url).pathname : null, provenance: row.provenance, references: require('./mcp-import-credentials').requiredNames(row),
|
|
32
56
|
projectEnabled: !!row.enabled, extensionEnabled: true, tools: row.tools || [], toolCount: (row.tools || []).length,
|
|
33
|
-
status: entry ? entry.status : (row.tools ? 'idle' : 'unavailable'), error: entry ? entry.error : '', authMode: row.authMode || 'auto' };
|
|
57
|
+
status: entry ? entry.status : (row.provenance || row.tools ? 'idle' : 'unavailable'), error: entry ? entry.error : '', authMode: row.authMode || 'auto' };
|
|
34
58
|
});
|
|
35
59
|
}
|
|
36
60
|
async function disconnect(owner, id) {
|
|
@@ -39,7 +63,7 @@ function createRemoteConnections(ctx) {
|
|
|
39
63
|
if (entry && entry.client) await entry.client.close().catch(function () {});
|
|
40
64
|
}
|
|
41
65
|
function provider(owner, row, flow) {
|
|
42
|
-
return oauth.createOAuthProvider(row, function () { check(owner, row); persist(owner); }, flow);
|
|
66
|
+
return oauth.createOAuthProvider(row, function () { check(owner, row); persist(owner); }, flow, function () { check(owner, row); });
|
|
43
67
|
}
|
|
44
68
|
async function connect(owner, row, flow) {
|
|
45
69
|
check(owner, row);
|
|
@@ -47,17 +71,20 @@ function createRemoteConnections(ctx) {
|
|
|
47
71
|
check(owner, row);
|
|
48
72
|
if (flow && flow.cancelled) throw new Error('Sign-in expired.');
|
|
49
73
|
var client = new Client({ name: 'clay', version: '1.0.0' }, { capabilities: {} });
|
|
50
|
-
|
|
51
|
-
|
|
74
|
+
if (row.source !== 'local' && ((row.references || []).length || Object.keys(row.envHeaders || {}).length)) throw new Error('Configure required credentials in Clay before connecting.');
|
|
75
|
+
var options = { fetch: async function (input, init) { check(owner, row); var response = await remoteFetch.fetch(input, init); check(owner, row); return response; }, requestInit: { headers: row.headers || {} } };
|
|
76
|
+
if (row.authMode === 'token' && row.token) options.requestInit = { headers: Object.assign({}, row.headers || {}, { Authorization: 'Bearer ' + row.token }) };
|
|
52
77
|
else if (flow || (row.auth && row.auth.tokens)) options.authProvider = provider(owner, row, flow);
|
|
53
|
-
var transport = new StreamableHTTPClientTransport(new URL(row.url), options);
|
|
78
|
+
var transport = row.source === 'local' ? localTransport(row, ctx.ownerIdentity ? ctx.ownerIdentity(owner) : null) : (row.transport === 'sse' ? new SSEClientTransport(new URL(row.url), options) : new StreamableHTTPClientTransport(new URL(row.url), options));
|
|
54
79
|
var entry = { client: client, transport: transport, status: 'connecting', error: '' };
|
|
55
80
|
live.set(key(owner, row.id), entry); changed(owner);
|
|
56
81
|
try {
|
|
57
82
|
await client.connect(transport, { timeout: 30000 });
|
|
83
|
+
check(owner, row);
|
|
58
84
|
var tools = [], cursor, seen = new Set();
|
|
59
85
|
do {
|
|
60
86
|
var page = await client.listTools(cursor ? { cursor: cursor } : {}, { timeout: 30000 });
|
|
87
|
+
check(owner, row);
|
|
61
88
|
tools = tools.concat(page.tools);
|
|
62
89
|
if (tools.length > 1000 || seen.size >= 100 || (page.nextCursor && seen.has(page.nextCursor))) throw new Error('Tool catalog exceeds the supported limit.');
|
|
63
90
|
cursor = page.nextCursor; seen.add(cursor);
|
|
@@ -65,13 +92,16 @@ function createRemoteConnections(ctx) {
|
|
|
65
92
|
if (JSON.stringify(tools).length > 2 * 1024 * 1024) throw new Error('Tool catalog exceeds the supported limit.');
|
|
66
93
|
check(owner, row);
|
|
67
94
|
if (live.get(key(owner, row.id)) !== entry || (flow && flow.cancelled)) throw new Error('Connection changed.');
|
|
95
|
+
tools = tools.filter(function (tool) {
|
|
96
|
+
return require('./mcp-tool-policy').permits(row, tool.name);
|
|
97
|
+
});
|
|
68
98
|
require('./mcp-proxy').validateDefinitions(tools);
|
|
69
99
|
row.tools = tools; entry.status = 'ready'; persist(owner);
|
|
70
100
|
return entry;
|
|
71
101
|
} catch (error) {
|
|
72
102
|
var requiresAuth = error.code === 401 || error.code === 403 || error.name === 'UnauthorizedError' || (flow && flow.authorizationUrl);
|
|
73
103
|
entry.status = requiresAuth ? 'auth_required' : 'unavailable';
|
|
74
|
-
entry.error = requiresAuth ? 'Sign in or provide an access token to connect.' : 'Could not connect. Check the address and try again.';
|
|
104
|
+
entry.error = requiresAuth ? 'Sign in or provide an access token to connect.' : row.source === 'local' ? 'Could not start the command. Check its dependency and working directory.' : 'Could not connect. Check the address and try again.';
|
|
75
105
|
if (!flow) await client.close().catch(function () {});
|
|
76
106
|
return entry;
|
|
77
107
|
} finally { changed(owner); }
|
|
@@ -101,6 +131,7 @@ function createRemoteConnections(ctx) {
|
|
|
101
131
|
if (ready.status !== 'ready') throw new Error('Connection failed after sign-in.');
|
|
102
132
|
setTimeout(function () { if (flows.get(key(owner, row.id)) === flow) { cancelLogin(owner, row.id); changed(owner); } }, 500);
|
|
103
133
|
});
|
|
134
|
+
try { check(owner, row); } catch (error) { flow.close(); throw error; }
|
|
104
135
|
flows.set(key(owner, row.id), flow);
|
|
105
136
|
flow.timer = setTimeout(function () { cancelLogin(owner, row.id); }, 10 * 60 * 1000); flow.timer.unref();
|
|
106
137
|
row.authMode = 'auto'; delete row.token;
|
|
@@ -123,40 +154,61 @@ function createRemoteConnections(ctx) {
|
|
|
123
154
|
pending.finally(function () { if (queues.get(owner) === pending) queues.delete(owner); }).catch(function () {});
|
|
124
155
|
return pending;
|
|
125
156
|
}
|
|
157
|
+
function commitImport(owner, select, authorize) {
|
|
158
|
+
authorize();
|
|
159
|
+
var imported = select();
|
|
160
|
+
records(owner);
|
|
161
|
+
if (accounts.get(owner).revision !== imported.revision) throw new Error('Connections changed. Scan again.');
|
|
162
|
+
if (records(owner).some(function (item) { return item.importSignature === imported.signature || imported.config.url && item.url === imported.config.url; })) throw new Error('This server is already imported.');
|
|
163
|
+
authorize();
|
|
164
|
+
var row = Object.assign({}, imported.config, { id: 'remote-' + crypto.randomBytes(8).toString('hex'), importSignature: imported.signature, enabled: false });
|
|
165
|
+
records(owner).push(row); persist(owner);
|
|
166
|
+
return row;
|
|
167
|
+
}
|
|
168
|
+
function importConnection(owner, select, authorize) {
|
|
169
|
+
return serial(owner, function () { var row = commitImport(owner, select, authorize); changed(owner); return row.id; });
|
|
170
|
+
}
|
|
126
171
|
function command(owner, msg, ws) {
|
|
127
172
|
// Revocation takes effect before any queued network work completes.
|
|
128
173
|
if (msg.action === 'remove' || (msg.action === 'enable' && msg.enabled !== true)) {
|
|
129
174
|
try {
|
|
130
175
|
var revoked = find(owner, msg.id);
|
|
131
|
-
if (revoked.source
|
|
176
|
+
if (revoked.source === 'computer') throw new Error('Not a remote connection.');
|
|
132
177
|
revoked.enabled = false; cancelLogin(owner, revoked.id); persist(owner);
|
|
133
178
|
disconnect(owner, revoked.id); changed(owner);
|
|
134
179
|
} catch (error) { return Promise.reject(error); }
|
|
135
180
|
}
|
|
136
181
|
return serial(owner, async function () {
|
|
137
182
|
var row;
|
|
138
|
-
if (msg.action === '
|
|
183
|
+
if (msg.action === 'import') {
|
|
184
|
+
row = commitImport(owner, function () { return ctx.selectImport(owner, ws, msg); }, function () { if (!ctx.canAccess(ws, owner)) throw new Error('MCP account is unavailable.'); });
|
|
185
|
+
} else if (msg.action === 'add') {
|
|
139
186
|
var url = network.remoteUrl(msg.url).href;
|
|
140
|
-
if (records(owner).filter(function (item) { return item.source
|
|
187
|
+
if (records(owner).filter(function (item) { return item.source !== 'computer'; }).length >= 30) throw new Error('You can add up to 30 remote connections per project.');
|
|
141
188
|
if (records(owner).some(function (item) { return item.url === url; })) throw new Error('This server is already connected. Open it from your connections.');
|
|
142
189
|
row = { id: 'remote-' + crypto.randomBytes(8).toString('hex'), source: 'url', url: url, label: new URL(url).hostname, enabled: false };
|
|
143
190
|
records(owner).push(row); persist(owner);
|
|
144
191
|
await connect(owner, row);
|
|
145
192
|
} else {
|
|
146
193
|
row = find(owner, msg.id);
|
|
147
|
-
if (row.source
|
|
148
|
-
if (msg.action === 'enable') { row.enabled = msg.enabled === true; persist(owner); }
|
|
194
|
+
if (row.source === 'computer') throw new Error('Not a remote connection.');
|
|
195
|
+
if (msg.action === 'enable') { row.enabled = msg.enabled === true; persist(owner); if (row.enabled && row.provenance) { try { await connect(owner, row); } catch (error) { row.enabled = false; persist(owner); throw error; } } }
|
|
149
196
|
else if (msg.action === 'rename') {
|
|
150
197
|
if (typeof msg.label !== 'string' || !msg.label.trim() || msg.label.length > 100) throw new Error('Use a name between 1 and 100 characters.');
|
|
151
198
|
row.label = msg.label.trim(); persist(owner);
|
|
152
199
|
} else if (msg.action === 'remove') {
|
|
153
200
|
cancelLogin(owner, row.id); await disconnect(owner, row.id);
|
|
154
|
-
|
|
201
|
+
var account = accounts.get(owner);
|
|
202
|
+
accounts.set(owner, { records: records(owner).filter(function (item) { return item !== row; }), revision: account && account.revision }); persist(owner);
|
|
155
203
|
} else if (msg.action === 'test') { cancelLogin(owner, row.id); await connect(owner, row); }
|
|
156
|
-
else if (msg.action === '
|
|
204
|
+
else if (msg.action === 'credentials') {
|
|
205
|
+
if (!msg.values || typeof msg.values !== 'object' || Array.isArray(msg.values)) throw new Error('Enter required credential values.');
|
|
206
|
+
var next = require('./mcp-import-credentials').applyCredentials(row, msg.values);
|
|
207
|
+
cancelLogin(owner, row.id); Object.assign(row, next); persist(owner); await disconnect(owner, row.id);
|
|
208
|
+
} else if (msg.action === 'token') {
|
|
157
209
|
if (typeof msg.token !== 'string' || !msg.token.trim() || msg.token.length > 8192 || /[\r\n]/.test(msg.token)) throw new Error('Enter a valid access token.');
|
|
158
210
|
cancelLogin(owner, row.id); row.token = msg.token.trim(); row.authMode = 'token'; delete row.auth; persist(owner); await connect(owner, row);
|
|
159
|
-
} else if (msg.action === 'signin') await signIn(owner, row, ws);
|
|
211
|
+
} else if (msg.action === 'signin') { if (row.source === 'local') throw new Error('This server uses process environment credentials.'); await signIn(owner, row, ws); }
|
|
160
212
|
else throw new Error('Unsupported connection action.');
|
|
161
213
|
}
|
|
162
214
|
changed(owner);
|
|
@@ -174,6 +226,7 @@ function createRemoteConnections(ctx) {
|
|
|
174
226
|
return serial(owner, async function () {
|
|
175
227
|
authorize(); var row = find(owner, id);
|
|
176
228
|
if (!row.enabled) throw new Error('Connection is disabled.');
|
|
229
|
+
if (!require('./mcp-tool-policy').permits(row, name)) throw new Error('Tool is excluded by the imported vendor policy.');
|
|
177
230
|
var entry = live.get(key(owner, id));
|
|
178
231
|
if (!entry || entry.status !== 'ready') entry = await connect(owner, row);
|
|
179
232
|
authorize(); check(owner, row);
|
|
@@ -183,6 +236,7 @@ function createRemoteConnections(ctx) {
|
|
|
183
236
|
var result;
|
|
184
237
|
try { result = await entry.client.callTool({ name: name, arguments: args || {} }, undefined, { timeout: 60000 }); }
|
|
185
238
|
catch (error) {
|
|
239
|
+
authorize(); check(owner, row);
|
|
186
240
|
if (live.get(key(owner, id)) === entry) {
|
|
187
241
|
entry.status = error.code === 401 || error.code === 403 || error.name === 'UnauthorizedError' ? 'auth_required' : 'unavailable';
|
|
188
242
|
entry.error = entry.status === 'auth_required' ? 'Sign in again to reconnect.' : 'The tool call could not complete. Check the service before retrying.';
|
|
@@ -209,7 +263,7 @@ function createRemoteConnections(ctx) {
|
|
|
209
263
|
closed = true; live.forEach(function (entry) { entry.client.close().catch(function () {}); }); live.clear();
|
|
210
264
|
remoteFetch.close();
|
|
211
265
|
}
|
|
212
|
-
return { view: view, command: command, call: call, handleBrowser: handleBrowser, destroy: destroy,
|
|
266
|
+
return { view: view, command: command, importConnection: importConnection, call: call, handleBrowser: handleBrowser, destroy: destroy,
|
|
213
267
|
computerEnabled: computerEnabled, toggleComputer: toggleComputer };
|
|
214
268
|
}
|
|
215
269
|
module.exports = { createRemoteConnections: createRemoteConnections };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// File-only adapters. Discovery never loads vendor code, plugins, or OAuth stores.
|
|
2
|
+
var path = require('path');
|
|
3
|
+
var toml = require('@iarna/toml');
|
|
4
|
+
var jsonc = require('jsonc-parser');
|
|
5
|
+
var yaml = require('yaml');
|
|
6
|
+
var network = require('./mcp-network');
|
|
7
|
+
var crypto = require('crypto');
|
|
8
|
+
var ADAPTERS = {
|
|
9
|
+
claude: { user: ['.claude.json'], project: ['.mcp.json'], key: 'mcpServers' },
|
|
10
|
+
codex: { user: ['.codex/config.toml'], project: ['.codex/config.toml'], key: 'mcp_servers' },
|
|
11
|
+
kiro: { user: ['.kiro/settings/mcp.json', '.kiro/mcp.json'], project: ['.kiro/settings/mcp.json', '.kiro/mcp.json'], key: 'mcpServers', agents: '.kiro/agents' },
|
|
12
|
+
opencode: { user: ['.config/opencode/opencode.json', '.config/opencode/opencode.jsonc'], project: ['opencode.json', 'opencode.jsonc'], key: 'mcp' },
|
|
13
|
+
kimi: { user: ['.kimi-code/mcp.json', '.kimi/mcp.json'], project: ['.kimi-code/mcp.json'], key: 'mcpServers' },
|
|
14
|
+
copilot: { user: ['.copilot/mcp-config.json'], project: ['.mcp.json', '.github/mcp.json'], key: 'mcpServers' },
|
|
15
|
+
qwen: { user: ['.qwen/settings.json'], project: ['.qwen/settings.json'], key: 'mcpServers' },
|
|
16
|
+
junie: { user: ['.junie/mcp/mcp.json'], project: ['.junie/mcp/mcp.json'], key: 'mcpServers' },
|
|
17
|
+
grok: { user: ['.grok/config.toml'], project: ['.grok/config.toml'], key: 'mcp_servers' },
|
|
18
|
+
antigravity: { user: ['.gemini/config/mcp_config.json', '.gemini/antigravity-cli/mcp_config.json'], project: ['.agents/mcp_config.json'], key: 'mcpServers', plugins: '.gemini/antigravity-cli/plugins' },
|
|
19
|
+
acp: { user: [], project: [], key: 'mcpServers' },
|
|
20
|
+
};
|
|
21
|
+
function parse(file, content) {
|
|
22
|
+
if (file.endsWith('.md')) {
|
|
23
|
+
var match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
|
|
24
|
+
if (!match) throw new Error('Agent profile requires YAML frontmatter.');
|
|
25
|
+
var document = yaml.parseDocument(match[1]);
|
|
26
|
+
if (document.errors.length) throw new Error('Invalid agent frontmatter.');
|
|
27
|
+
return document.toJS({ maxAliasCount: 50 });
|
|
28
|
+
}
|
|
29
|
+
if (file.endsWith('.toml')) return toml.parse(content);
|
|
30
|
+
var errors = [], value = jsonc.parse(content, errors, { allowTrailingComma: true });
|
|
31
|
+
if (errors.length || !value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid MCP source format.');
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
function stringMap(value) {
|
|
35
|
+
if (!value) return {};
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
var map = {}; value.forEach(function (entry) { if (!entry || typeof entry.name !== 'string' || typeof entry.value !== 'string') throw new Error('Invalid name/value field.'); map[entry.name] = entry.value; });
|
|
38
|
+
return map;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value !== 'object') throw new Error('Invalid environment or headers.');
|
|
41
|
+
Object.keys(value).forEach(function (name) { if (typeof value[name] !== 'string') throw new Error('Invalid environment or headers.'); });
|
|
42
|
+
return Object.assign({}, value);
|
|
43
|
+
}
|
|
44
|
+
function normalize(vendor, name, value, source) {
|
|
45
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid server definition.');
|
|
46
|
+
['enabled', 'disabled'].forEach(function (key) { if (value[key] !== undefined && typeof value[key] !== 'boolean') throw new Error('Invalid source enablement: ' + key); });
|
|
47
|
+
if (value.oauth !== undefined && value.oauth !== true) throw new Error('Vendor OAuth policy requires reviewed Clay-owned authentication setup.');
|
|
48
|
+
var command = value.command, args = value.args || [];
|
|
49
|
+
if (vendor === 'opencode' && Array.isArray(command)) { args = command.slice(1); command = command[0]; }
|
|
50
|
+
var url = value.url || value.httpUrl || value.serverUrl;
|
|
51
|
+
if (command && url) throw new Error('Conflicting stdio and remote transport definitions.');
|
|
52
|
+
var type = value.type || value.transport || (url ? vendor === 'qwen' && !value.httpUrl ? 'sse' : 'http' : 'stdio');
|
|
53
|
+
if (type === 'local') type = 'stdio'; if (type === 'remote' || type === 'streamable-http') type = 'http';
|
|
54
|
+
if (['stdio', 'http', 'sse'].indexOf(type) < 0) throw new Error('Unsupported transport. Clay supports stdio, HTTP and SSE.');
|
|
55
|
+
if (value.headersHelper || value.oauth && (value.oauth.clientSecret || value.oauth.clientId)) throw new Error('Vendor authentication helper requires Clay-owned credential setup.');
|
|
56
|
+
var policy = require('./mcp-tool-policy').normalize(vendor, value);
|
|
57
|
+
var config = { transport: type, enabled: false, label: name, source: type === 'stdio' ? 'local' : 'url',
|
|
58
|
+
provenance: source, sourceEnabled: value.enabled === false || value.disabled === true ? false : source.scope.indexOf('plugin') === 0 ? null : true };
|
|
59
|
+
if (type === 'stdio') {
|
|
60
|
+
if (typeof command !== 'string' || !command || command.includes('\0') || !Array.isArray(args) || args.some(function (arg) { return typeof arg !== 'string' || arg.includes('\0'); })) throw new Error('Invalid stdio command or arguments.');
|
|
61
|
+
config.command = command; config.args = args; config.env = stringMap(value.env || value.environment); config.cwd = value.cwd || source.cwd;
|
|
62
|
+
if (typeof config.cwd !== 'string' || !path.isAbsolute(config.cwd)) throw new Error('Stdio working directory must be absolute.');
|
|
63
|
+
} else {
|
|
64
|
+
// Do not expand credential references during inspection.
|
|
65
|
+
config.url = network.remoteUrl(url).href; config.headers = stringMap(value.headers || value.http_headers);
|
|
66
|
+
config.envHeaders = stringMap(value.env_http_headers);
|
|
67
|
+
var bearer = value.bearer_token_env_var || value.bearerTokenEnvVar;
|
|
68
|
+
if (bearer) { config.bearerTokenEnv = bearer; config.envHeaders.Authorization = bearer; }
|
|
69
|
+
}
|
|
70
|
+
config.environmentNames = (value.env_vars || []).concat(Object.keys(config.env || {}));
|
|
71
|
+
if (value.env_vars !== undefined && !Array.isArray(value.env_vars)) throw new Error('Invalid environment allowlist.');
|
|
72
|
+
Object.assign(config, policy);
|
|
73
|
+
if (source.sourceEnabled !== undefined) config.sourceEnabled = config.sourceEnabled === false || source.sourceEnabled === false ? false : source.sourceEnabled;
|
|
74
|
+
var references = JSON.stringify([config.command, config.args, config.env, config.headers, config.url]).match(/\$\{[A-Za-z_][A-Za-z0-9_]*(?::-[^}]*)?\}|\$[A-Z_][A-Z0-9_]*|\{env:[^}]+\}|\{file:[^}]+\}|\{\{[^}]+\}\}/g) || [];
|
|
75
|
+
config.references = Array.from(new Set(references.concat(Object.values(config.envHeaders || {})).concat(value.env_vars || [])));
|
|
76
|
+
// Plugins may reference their own installed directory without reading any credential file.
|
|
77
|
+
if (source.pluginRoot) {
|
|
78
|
+
function pluginValue(value) {
|
|
79
|
+
if (typeof value === 'string') return value.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, function () { return source.pluginRoot; }).replace(/\$\{CLAUDE_PROJECT_DIR\}/g, function () { return source.cwd; });
|
|
80
|
+
if (Array.isArray(value)) return value.map(pluginValue);
|
|
81
|
+
if (value && typeof value === 'object') { var next = {}; Object.keys(value).forEach(function (key) { next[key] = pluginValue(value[key]); }); return next; }
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
config = pluginValue(config); config.references = config.references.filter(function (ref) { return ref !== source.pluginRoot && ref !== source.cwd; });
|
|
85
|
+
}
|
|
86
|
+
return config;
|
|
87
|
+
}
|
|
88
|
+
function signature(config) {
|
|
89
|
+
return crypto.createHash('sha256').update(JSON.stringify([config.transport, config.url, config.command, config.args, config.env, config.headers, config.envHeaders, config.cwd, config.disabledTools, config.enabledTools, config.toolPatterns, config.toolPrefix, config.references])).digest('hex');
|
|
90
|
+
}
|
|
91
|
+
module.exports = { adapters: ADAPTERS, parse: parse, normalize: normalize, signature: signature };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
var path = require('path');
|
|
2
|
+
var crypto = require('crypto');
|
|
3
|
+
var adapters = require('./mcp-source-adapters').adapters;
|
|
4
|
+
var KEYS = ['CODEX_HOME', 'CLAUDE_CONFIG_DIR', 'OPENCODE_CONFIG', 'OPENCODE_CONFIG_DIR', 'OPENCODE_CONFIG_CONTENT', 'XDG_CONFIG_HOME', 'KIMI_CODE_HOME', 'COPILOT_HOME', 'GROK_HOME', 'QWEN_HOME', 'QWEN_CODE_SYSTEM_SETTINGS_PATH', 'QWEN_CODE_SYSTEM_DEFAULTS_PATH'];
|
|
5
|
+
function resolve(ctx, owner, ws, home, identity) {
|
|
6
|
+
// Only the production runtime resolver may supply mapped-user overrides.
|
|
7
|
+
var env = ctx.runtimeEnv ? ctx.runtimeEnv(owner, ws) : identity ? {} : ctx.home ? {} : process.env;
|
|
8
|
+
var selected = {};
|
|
9
|
+
KEYS.forEach(function (key) { if (env[key] !== undefined) selected[key] = env[key]; });
|
|
10
|
+
function qwenRoot(value) { if (!value) return path.join(home, '.qwen'); return path.resolve(ctx.cwd, value.replace(/^~(?=\/|$)/, home)); }
|
|
11
|
+
var roots = { copilot: path.resolve(home, env.COPILOT_HOME || '.copilot'), grok: path.resolve(home, env.GROK_HOME || '.grok'), qwen: qwenRoot(env.QWEN_HOME), codex: path.resolve(home, env.CODEX_HOME || '.codex'), claude: path.resolve(home, env.CLAUDE_CONFIG_DIR || '.claude'),
|
|
12
|
+
opencode: path.resolve(home, env.XDG_CONFIG_HOME || '.config', 'opencode'), kimi: path.resolve(home, env.KIMI_CODE_HOME || '.kimi-code') };
|
|
13
|
+
var sources = [];
|
|
14
|
+
function add(vendor, file, scope, extra) { sources.push(Object.assign({ vendor: vendor, file: file, scope: scope, cwd: ctx.cwd }, extra)); }
|
|
15
|
+
Object.keys(adapters).forEach(function (vendor) {
|
|
16
|
+
var files = adapters[vendor].user;
|
|
17
|
+
if (vendor === 'codex') files = [path.join(roots.codex, 'config.toml')];
|
|
18
|
+
if (vendor === 'claude') files = [env.CLAUDE_CONFIG_DIR ? path.join(roots.claude, '.claude.json') : path.join(home, '.claude.json')];
|
|
19
|
+
if (vendor === 'opencode') files = ['opencode.json', 'opencode.jsonc'].map(function (file) { return path.join(roots.opencode, file); });
|
|
20
|
+
if (vendor === 'copilot') files = [path.join(roots.copilot, 'mcp-config.json')];
|
|
21
|
+
if (vendor === 'grok') files = [path.join(roots.grok, 'config.toml')];
|
|
22
|
+
if (vendor === 'qwen') files = [path.join(roots.qwen, 'settings.json')];
|
|
23
|
+
if (vendor === 'kimi' && env.KIMI_CODE_HOME) files = [path.join(roots.kimi, 'mcp.json')];
|
|
24
|
+
files.forEach(function (file) { add(vendor, path.resolve(home, file), 'user'); });
|
|
25
|
+
adapters[vendor].project.forEach(function (file) { add(vendor, path.join(ctx.cwd, file), 'project'); });
|
|
26
|
+
});
|
|
27
|
+
['opencode.json', 'opencode.jsonc'].forEach(function (file) { add('opencode', path.join(ctx.cwd, '.opencode', file), 'runtime', { configDirectory: true }); });
|
|
28
|
+
if (env.OPENCODE_CONFIG) add('opencode', path.resolve(ctx.cwd, env.OPENCODE_CONFIG), 'runtime');
|
|
29
|
+
if (env.OPENCODE_CONFIG_DIR) ['opencode.json', 'opencode.jsonc'].forEach(function (file) { add('opencode', path.resolve(ctx.cwd, env.OPENCODE_CONFIG_DIR, file), 'runtime', { configDirectory: true }); });
|
|
30
|
+
if (env.OPENCODE_CONFIG_CONTENT) add('opencode', 'runtime:OPENCODE_CONFIG_CONTENT', 'runtime-inline', { inline: env.OPENCODE_CONFIG_CONTENT });
|
|
31
|
+
return { roots: roots, policyPaths: { qwenSettings: env.QWEN_CODE_SYSTEM_SETTINGS_PATH && path.resolve(ctx.cwd, env.QWEN_CODE_SYSTEM_SETTINGS_PATH), qwenDefaults: env.QWEN_CODE_SYSTEM_DEFAULTS_PATH && path.resolve(ctx.cwd, env.QWEN_CODE_SYSTEM_DEFAULTS_PATH) }, sources: sources, fingerprint: crypto.createHash('sha256').update(JSON.stringify(selected)).digest('hex') };
|
|
32
|
+
}
|
|
33
|
+
function priority(source) {
|
|
34
|
+
if (source.scope === 'runtime-inline') return 80;
|
|
35
|
+
if (source.scope === 'configured') return 70;
|
|
36
|
+
if (source.scope === 'runtime') return source.configDirectory ? 65 : 30;
|
|
37
|
+
if (source.scope === 'local') return 60;
|
|
38
|
+
if (source.scope === 'project') return 50 + (source.vendor === 'copilot' && path.basename(source.file) === '.mcp.json' ? 1 : 0);
|
|
39
|
+
if (source.scope === 'project-ancestor') return 40 + source.file.split(path.sep).length * 0.01 + (path.basename(source.file) === '.mcp.json' ? 0.001 : 0);
|
|
40
|
+
return source.scope.indexOf('plugin') === 0 ? 10 : 20;
|
|
41
|
+
}
|
|
42
|
+
module.exports = { resolve: resolve, priority: priority };
|