@agent-native/core 0.7.2 → 0.7.6
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 +6 -5
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +8 -4
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/types.d.ts +1 -1
- package/dist/agent/engine/types.d.ts.map +1 -1
- package/dist/agent/production-agent.d.ts +7 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +153 -118
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +8 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/index.js +8 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/info.d.ts +2 -0
- package/dist/cli/info.d.ts.map +1 -0
- package/dist/cli/info.js +103 -0
- package/dist/cli/info.js.map +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +171 -68
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/FeedbackButton.d.ts +3 -1
- package/dist/client/FeedbackButton.d.ts.map +1 -1
- package/dist/client/FeedbackButton.js +115 -40
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +12 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +3 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +46 -2
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/VoiceButton.d.ts +21 -0
- package/dist/client/composer/VoiceButton.d.ts.map +1 -0
- package/dist/client/composer/VoiceButton.js +51 -0
- package/dist/client/composer/VoiceButton.js.map +1 -0
- package/dist/client/composer/useVoiceDictation.d.ts +38 -0
- package/dist/client/composer/useVoiceDictation.d.ts.map +1 -0
- package/dist/client/composer/useVoiceDictation.js +398 -0
- package/dist/client/composer/useVoiceDictation.js.map +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.js +2 -2
- package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts +5 -4
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +90 -24
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/resources/McpServerDetail.d.ts +15 -0
- package/dist/client/resources/McpServerDetail.d.ts.map +1 -0
- package/dist/client/resources/McpServerDetail.js +65 -0
- package/dist/client/resources/McpServerDetail.js.map +1 -0
- package/dist/client/resources/ResourceEditor.js +1 -1
- package/dist/client/resources/ResourceEditor.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts +6 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +18 -7
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +191 -20
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/resources/use-mcp-servers.d.ts +68 -0
- package/dist/client/resources/use-mcp-servers.d.ts.map +1 -0
- package/dist/client/resources/use-mcp-servers.js +83 -0
- package/dist/client/resources/use-mcp-servers.js.map +1 -0
- package/dist/client/resources/use-resources.d.ts +27 -1
- package/dist/client/resources/use-resources.d.ts.map +1 -1
- package/dist/client/resources/use-resources.js +63 -0
- package/dist/client/resources/use-resources.js.map +1 -1
- package/dist/client/settings/SecretsSection.d.ts +12 -0
- package/dist/client/settings/SecretsSection.d.ts.map +1 -0
- package/dist/client/settings/SecretsSection.js +148 -0
- package/dist/client/settings/SecretsSection.js.map +1 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +101 -2
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.d.ts +14 -0
- package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -0
- package/dist/client/settings/VoiceTranscriptionSection.js +111 -0
- package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -0
- package/dist/client/settings/index.d.ts +1 -0
- package/dist/client/settings/index.d.ts.map +1 -1
- package/dist/client/settings/index.js +1 -0
- package/dist/client/settings/index.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +16 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -0
- package/dist/client/sharing/ShareButton.js +308 -0
- package/dist/client/sharing/ShareButton.js.map +1 -0
- package/dist/client/sharing/ShareDialog.d.ts +33 -0
- package/dist/client/sharing/ShareDialog.d.ts.map +1 -0
- package/dist/client/sharing/ShareDialog.js +231 -0
- package/dist/client/sharing/ShareDialog.js.map +1 -0
- package/dist/client/sharing/VisibilityBadge.d.ts +11 -0
- package/dist/client/sharing/VisibilityBadge.d.ts.map +1 -0
- package/dist/client/sharing/VisibilityBadge.js +19 -0
- package/dist/client/sharing/VisibilityBadge.js.map +1 -0
- package/dist/client/sharing/index.d.ts +4 -0
- package/dist/client/sharing/index.d.ts.map +1 -0
- package/dist/client/sharing/index.js +4 -0
- package/dist/client/sharing/index.js.map +1 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +27 -3
- package/dist/client/use-action.js.map +1 -1
- package/dist/db/migrations.d.ts +18 -3
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +25 -3
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/schema.d.ts +1 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +4 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/deploy/build.js +22 -3
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-core.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-client/config.d.ts +20 -1
- package/dist/mcp-client/config.d.ts.map +1 -1
- package/dist/mcp-client/config.js +28 -11
- package/dist/mcp-client/config.js.map +1 -1
- package/dist/mcp-client/hub-client.d.ts +38 -0
- package/dist/mcp-client/hub-client.d.ts.map +1 -0
- package/dist/mcp-client/hub-client.js +147 -0
- package/dist/mcp-client/hub-client.js.map +1 -0
- package/dist/mcp-client/hub-routes.d.ts +42 -0
- package/dist/mcp-client/hub-routes.d.ts.map +1 -0
- package/dist/mcp-client/hub-routes.js +114 -0
- package/dist/mcp-client/hub-routes.js.map +1 -0
- package/dist/mcp-client/index.d.ts +15 -0
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +35 -0
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/manager.d.ts +54 -8
- package/dist/mcp-client/manager.d.ts.map +1 -1
- package/dist/mcp-client/manager.js +276 -59
- package/dist/mcp-client/manager.js.map +1 -1
- package/dist/mcp-client/remote-store.d.ts +102 -0
- package/dist/mcp-client/remote-store.d.ts.map +1 -0
- package/dist/mcp-client/remote-store.js +200 -0
- package/dist/mcp-client/remote-store.js.map +1 -0
- package/dist/mcp-client/routes.d.ts +55 -0
- package/dist/mcp-client/routes.d.ts.map +1 -0
- package/dist/mcp-client/routes.js +384 -0
- package/dist/mcp-client/routes.js.map +1 -0
- package/dist/mcp-client/visibility.d.ts +16 -0
- package/dist/mcp-client/visibility.d.ts.map +1 -0
- package/dist/mcp-client/visibility.js +45 -0
- package/dist/mcp-client/visibility.js.map +1 -0
- package/dist/onboarding/default-steps.d.ts.map +1 -1
- package/dist/onboarding/default-steps.js +5 -0
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/org/accept-pending.d.ts +22 -0
- package/dist/org/accept-pending.d.ts.map +1 -0
- package/dist/org/accept-pending.js +75 -0
- package/dist/org/accept-pending.js.map +1 -0
- package/dist/org/context.js +2 -2
- package/dist/org/context.js.map +1 -1
- package/dist/org/handlers.d.ts +2 -0
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +54 -3
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/index.d.ts +2 -0
- package/dist/org/index.d.ts.map +1 -1
- package/dist/org/index.js +1 -0
- package/dist/org/index.js.map +1 -1
- package/dist/resources/handlers.d.ts.map +1 -1
- package/dist/resources/handlers.js +30 -0
- package/dist/resources/handlers.js.map +1 -1
- package/dist/secrets/index.d.ts +15 -0
- package/dist/secrets/index.d.ts.map +1 -0
- package/dist/secrets/index.js +15 -0
- package/dist/secrets/index.js.map +1 -0
- package/dist/secrets/onboarding.d.ts +18 -0
- package/dist/secrets/onboarding.d.ts.map +1 -0
- package/dist/secrets/onboarding.js +87 -0
- package/dist/secrets/onboarding.js.map +1 -0
- package/dist/secrets/register-framework-secrets.d.ts +13 -0
- package/dist/secrets/register-framework-secrets.d.ts.map +1 -0
- package/dist/secrets/register-framework-secrets.js +59 -0
- package/dist/secrets/register-framework-secrets.js.map +1 -0
- package/dist/secrets/register.d.ts +63 -0
- package/dist/secrets/register.d.ts.map +1 -0
- package/dist/secrets/register.js +62 -0
- package/dist/secrets/register.js.map +1 -0
- package/dist/secrets/routes.d.ts +67 -0
- package/dist/secrets/routes.d.ts.map +1 -0
- package/dist/secrets/routes.js +275 -0
- package/dist/secrets/routes.js.map +1 -0
- package/dist/secrets/schema.d.ts +154 -0
- package/dist/secrets/schema.d.ts.map +1 -0
- package/dist/secrets/schema.js +41 -0
- package/dist/secrets/schema.js.map +1 -0
- package/dist/secrets/storage.d.ts +54 -0
- package/dist/secrets/storage.d.ts.map +1 -0
- package/dist/secrets/storage.js +181 -0
- package/dist/secrets/storage.js.map +1 -0
- package/dist/server/action-discovery.d.ts +18 -0
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +95 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +22 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +16 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +120 -25
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/app-name.d.ts +13 -0
- package/dist/server/app-name.d.ts.map +1 -0
- package/dist/server/app-name.js +41 -0
- package/dist/server/app-name.js.map +1 -0
- package/dist/server/app-url.d.ts +24 -0
- package/dist/server/app-url.d.ts.map +1 -0
- package/dist/server/app-url.js +76 -0
- package/dist/server/app-url.js.map +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +50 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +137 -13
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +73 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/create-server.d.ts.map +1 -1
- package/dist/server/create-server.js +6 -0
- package/dist/server/create-server.js.map +1 -1
- package/dist/server/date-utils.d.ts +15 -0
- package/dist/server/date-utils.d.ts.map +1 -0
- package/dist/server/date-utils.js +41 -0
- package/dist/server/date-utils.js.map +1 -0
- package/dist/server/email-template.d.ts +51 -0
- package/dist/server/email-template.d.ts.map +1 -0
- package/dist/server/email-template.js +146 -0
- package/dist/server/email-template.js.map +1 -0
- package/dist/server/index.d.ts +6 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +6 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +3 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +13 -3
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/request-context.d.ts +9 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +10 -0
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +26 -0
- package/dist/server/transcribe-voice.d.ts.map +1 -0
- package/dist/server/transcribe-voice.js +143 -0
- package/dist/server/transcribe-voice.js.map +1 -0
- package/dist/sharing/access.d.ts +56 -0
- package/dist/sharing/access.d.ts.map +1 -0
- package/dist/sharing/access.js +149 -0
- package/dist/sharing/access.js.map +1 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +3 -0
- package/dist/sharing/actions/list-resource-shares.d.ts.map +1 -0
- package/dist/sharing/actions/list-resource-shares.js +38 -0
- package/dist/sharing/actions/list-resource-shares.js.map +1 -0
- package/dist/sharing/actions/set-resource-visibility.d.ts +3 -0
- package/dist/sharing/actions/set-resource-visibility.d.ts.map +1 -0
- package/dist/sharing/actions/set-resource-visibility.js +24 -0
- package/dist/sharing/actions/set-resource-visibility.js.map +1 -0
- package/dist/sharing/actions/share-resource.d.ts +3 -0
- package/dist/sharing/actions/share-resource.d.ts.map +1 -0
- package/dist/sharing/actions/share-resource.js +64 -0
- package/dist/sharing/actions/share-resource.js.map +1 -0
- package/dist/sharing/actions/unshare-resource.d.ts +3 -0
- package/dist/sharing/actions/unshare-resource.d.ts.map +1 -0
- package/dist/sharing/actions/unshare-resource.js +24 -0
- package/dist/sharing/actions/unshare-resource.js.map +1 -0
- package/dist/sharing/index.d.ts +11 -0
- package/dist/sharing/index.d.ts.map +1 -0
- package/dist/sharing/index.js +11 -0
- package/dist/sharing/index.js.map +1 -0
- package/dist/sharing/registry.d.ts +44 -0
- package/dist/sharing/registry.d.ts.map +1 -0
- package/dist/sharing/registry.js +54 -0
- package/dist/sharing/registry.js.map +1 -0
- package/dist/sharing/schema.d.ts +202 -0
- package/dist/sharing/schema.d.ts.map +1 -0
- package/dist/sharing/schema.js +88 -0
- package/dist/sharing/schema.js.map +1 -0
- package/dist/styles/agent-native.css +111 -0
- package/dist/tailwind.preset.d.ts +2 -2
- package/dist/tailwind.preset.d.ts.map +1 -1
- package/dist/tailwind.preset.js +27 -7
- package/dist/tailwind.preset.js.map +1 -1
- package/dist/templates/default/app/global.css +65 -68
- package/dist/templates/default/components.json +1 -1
- package/dist/templates/default/package.json +2 -4
- package/dist/templates/default/vite.config.ts +3 -0
- package/dist/templates/workspace-core/package.json +1 -4
- package/dist/templates/workspace-core/src/index.ts +1 -1
- package/dist/templates/workspace-core/styles/tokens.css +22 -0
- package/dist/templates/workspace-core/tsconfig.json +1 -1
- package/dist/vite/action-types-plugin.d.ts +5 -0
- package/dist/vite/action-types-plugin.d.ts.map +1 -1
- package/dist/vite/action-types-plugin.js +129 -28
- package/dist/vite/action-types-plugin.js.map +1 -1
- package/dist/vite/client.d.ts +6 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +18 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/actions.md +169 -74
- package/docs/content/agent-teams.md +139 -0
- package/docs/content/cloneable-saas.md +98 -0
- package/docs/content/creating-templates.md +9 -11
- package/docs/content/deployment.md +2 -9
- package/docs/content/drop-in-agent.md +200 -0
- package/docs/content/enterprise-workspace.md +22 -10
- package/docs/content/getting-started.md +34 -19
- package/docs/content/integrations.md +3 -3
- package/docs/content/key-concepts.md +50 -23
- package/docs/content/mcp-clients.md +71 -0
- package/docs/content/pure-agent-apps.md +69 -0
- package/docs/content/recurring-jobs.md +123 -0
- package/docs/content/skills-guide.md +8 -0
- package/docs/content/template-analytics.md +190 -0
- package/docs/content/template-calendar.md +151 -0
- package/docs/content/template-clips.md +55 -0
- package/docs/content/template-content.md +141 -0
- package/docs/content/template-dispatch.md +58 -0
- package/docs/content/template-forms.md +51 -0
- package/docs/content/template-mail.md +169 -0
- package/docs/content/template-slides.md +218 -0
- package/docs/content/template-starter.md +68 -0
- package/docs/content/template-video.md +162 -0
- package/docs/content/voice-input.md +59 -0
- package/docs/content/what-is-agent-native.md +142 -45
- package/docs/content/workspace-management.md +1 -0
- package/docs/content/{resources.md → workspace.md} +94 -42
- package/package.json +20 -19
- package/src/templates/default/app/global.css +65 -68
- package/src/templates/default/components.json +1 -1
- package/src/templates/default/package.json +2 -4
- package/src/templates/default/vite.config.ts +3 -0
- package/src/templates/workspace-core/package.json +1 -4
- package/src/templates/workspace-core/src/index.ts +1 -1
- package/src/templates/workspace-core/styles/tokens.css +22 -0
- package/src/templates/workspace-core/tsconfig.json +1 -1
- package/dist/templates/default/postcss.config.js +0 -6
- package/dist/templates/default/tailwind.config.ts +0 -7
- package/dist/templates/workspace-core/tailwind.preset.ts +0 -34
- package/src/templates/default/postcss.config.js +0 -6
- package/src/templates/default/tailwind.config.ts +0 -7
- package/src/templates/workspace-core/tailwind.preset.ts +0 -34
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent store for user-added remote MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* Servers added through the settings UI live in the framework's `settings`
|
|
5
|
+
* table, keyed by scope:
|
|
6
|
+
* - User scope: `u:<email>:mcp-servers-remote`
|
|
7
|
+
* - Org scope: `o:<orgId>:mcp-servers-remote`
|
|
8
|
+
*
|
|
9
|
+
* Both scopes store the same shape — a list of `StoredRemoteMcpServer`
|
|
10
|
+
* records. The running MCP manager merges this list with the file-based
|
|
11
|
+
* `mcp.config.json` on startup and after every mutation.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import { getUserSetting, putUserSetting, deleteUserSetting, } from "../settings/user-settings.js";
|
|
15
|
+
import { getOrgSetting, putOrgSetting, deleteOrgSetting, } from "../settings/org-settings.js";
|
|
16
|
+
const SETTINGS_KEY = "mcp-servers-remote";
|
|
17
|
+
/** Tiny nanoid — matches the inline helper used elsewhere in this package. */
|
|
18
|
+
function shortId() {
|
|
19
|
+
const rand = globalThis.crypto?.randomUUID?.().replace(/-/g, "") ??
|
|
20
|
+
Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
21
|
+
return rand.slice(0, 16);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validate a candidate MCP server name — used as a key in the merged config
|
|
25
|
+
* and as part of the prefixed tool name (`mcp__<merged-key>__<tool>`).
|
|
26
|
+
*
|
|
27
|
+
* Allowed: letters, digits, hyphen; 1–40 chars. Lowercased. Underscores are
|
|
28
|
+
* excluded on purpose — the merged-key format uses `_` as a separator between
|
|
29
|
+
* `<scope>`, `<owner>`, and `<name>`, so allowing `_` in names would make the
|
|
30
|
+
* parse ambiguous.
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeServerName(input) {
|
|
33
|
+
return input
|
|
34
|
+
.trim()
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
37
|
+
.slice(0, 40);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Short, deterministic, URL-safe hash of an email. Used as the owner
|
|
41
|
+
* discriminator in user-scope merged keys so two users with the same server
|
|
42
|
+
* name don't collide in the global MCP manager.
|
|
43
|
+
*/
|
|
44
|
+
export function hashEmail(email) {
|
|
45
|
+
return createHash("sha256")
|
|
46
|
+
.update(email.toLowerCase().trim())
|
|
47
|
+
.digest("hex")
|
|
48
|
+
.slice(0, 10);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Sanitise an org id to the character set allowed in merged keys.
|
|
52
|
+
* Org ids are already nanoid-style alphanumeric, but we normalise defensively.
|
|
53
|
+
*/
|
|
54
|
+
function sanitiseOrgId(orgId) {
|
|
55
|
+
return orgId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
56
|
+
}
|
|
57
|
+
/** Reject obviously-wrong URLs. Allows http only for localhost. */
|
|
58
|
+
export function validateRemoteUrl(raw) {
|
|
59
|
+
let url;
|
|
60
|
+
try {
|
|
61
|
+
url = new URL(raw);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return { ok: false, error: "Not a valid URL" };
|
|
65
|
+
}
|
|
66
|
+
if (url.protocol === "https:")
|
|
67
|
+
return { ok: true, url };
|
|
68
|
+
if (url.protocol === "http:") {
|
|
69
|
+
const host = url.hostname;
|
|
70
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "::1") {
|
|
71
|
+
return { ok: true, url };
|
|
72
|
+
}
|
|
73
|
+
return { ok: false, error: "Plain http is only allowed for localhost" };
|
|
74
|
+
}
|
|
75
|
+
return { ok: false, error: `Unsupported protocol: ${url.protocol}` };
|
|
76
|
+
}
|
|
77
|
+
async function readList(scope, scopeId) {
|
|
78
|
+
const raw = scope === "user"
|
|
79
|
+
? await getUserSetting(scopeId, SETTINGS_KEY)
|
|
80
|
+
: await getOrgSetting(scopeId, SETTINGS_KEY);
|
|
81
|
+
if (!raw || !Array.isArray(raw.servers))
|
|
82
|
+
return [];
|
|
83
|
+
return raw.servers.filter((s) => s && typeof s.id === "string" && typeof s.url === "string");
|
|
84
|
+
}
|
|
85
|
+
async function writeList(scope, scopeId, servers) {
|
|
86
|
+
if (scope === "user") {
|
|
87
|
+
await putUserSetting(scopeId, SETTINGS_KEY, { servers });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
await putOrgSetting(scopeId, SETTINGS_KEY, { servers });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export async function listRemoteServers(scope, scopeId) {
|
|
94
|
+
return readList(scope, scopeId);
|
|
95
|
+
}
|
|
96
|
+
export async function addRemoteServer(scope, scopeId, input) {
|
|
97
|
+
const name = normalizeServerName(input.name);
|
|
98
|
+
if (!name)
|
|
99
|
+
return { ok: false, error: "Name is required" };
|
|
100
|
+
const urlCheck = validateRemoteUrl(input.url);
|
|
101
|
+
if (!urlCheck.ok)
|
|
102
|
+
return { ok: false, error: urlCheck.error ?? "Bad URL" };
|
|
103
|
+
const existing = await readList(scope, scopeId);
|
|
104
|
+
if (existing.some((s) => s.name === name)) {
|
|
105
|
+
return { ok: false, error: `A server named "${name}" already exists` };
|
|
106
|
+
}
|
|
107
|
+
const server = {
|
|
108
|
+
id: `mcps_${shortId()}`,
|
|
109
|
+
name,
|
|
110
|
+
url: urlCheck.url.toString(),
|
|
111
|
+
headers: input.headers && Object.keys(input.headers).length > 0
|
|
112
|
+
? { ...input.headers }
|
|
113
|
+
: undefined,
|
|
114
|
+
description: input.description?.trim() || undefined,
|
|
115
|
+
createdAt: Date.now(),
|
|
116
|
+
};
|
|
117
|
+
await writeList(scope, scopeId, [...existing, server]);
|
|
118
|
+
return { ok: true, server };
|
|
119
|
+
}
|
|
120
|
+
export async function removeRemoteServer(scope, scopeId, id) {
|
|
121
|
+
const existing = await readList(scope, scopeId);
|
|
122
|
+
const next = existing.filter((s) => s.id !== id);
|
|
123
|
+
if (next.length === existing.length)
|
|
124
|
+
return false;
|
|
125
|
+
if (next.length === 0) {
|
|
126
|
+
if (scope === "user") {
|
|
127
|
+
await deleteUserSetting(scopeId, SETTINGS_KEY);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
await deleteOrgSetting(scopeId, SETTINGS_KEY);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
await writeList(scope, scopeId, next);
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Project a stored server into the runtime `McpHttpServerConfig` shape that
|
|
140
|
+
* `McpClientManager` consumes. The merged-config key is the scope + name
|
|
141
|
+
* so a user-scope and org-scope server can both share a readable name
|
|
142
|
+
* without clobbering each other.
|
|
143
|
+
*/
|
|
144
|
+
export function toHttpServerConfig(stored) {
|
|
145
|
+
return {
|
|
146
|
+
type: "http",
|
|
147
|
+
url: stored.url,
|
|
148
|
+
headers: stored.headers,
|
|
149
|
+
description: stored.description,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Build the merged-config key for a stored server.
|
|
154
|
+
*
|
|
155
|
+
* The key encodes the owning scope + owner identity so two users adding a
|
|
156
|
+
* server called `zapier` produce distinct ids (`user_ab12cd34ef_zapier` vs
|
|
157
|
+
* `user_99aa88bb77_zapier`) and Alice's tool calls never route through Bob's
|
|
158
|
+
* credentials in a shared-process deployment.
|
|
159
|
+
*
|
|
160
|
+
* - User scope: `user_<emailhash>_<name>`
|
|
161
|
+
* - Org scope: `org_<orgId>_<name>`
|
|
162
|
+
*
|
|
163
|
+
* `ownerId` is the raw email for user scope, and the org id for org scope.
|
|
164
|
+
*/
|
|
165
|
+
export function mergedConfigKey(scope, stored, ownerId) {
|
|
166
|
+
const owner = scope === "user" ? hashEmail(ownerId) : sanitiseOrgId(ownerId);
|
|
167
|
+
return `${scope}_${owner}_${stored.name}`;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Parse a merged key (or a full prefixed tool name like
|
|
171
|
+
* `mcp__user_abcd1234ef_zapier__run-task`) back into its scope + owner + name
|
|
172
|
+
* components. Returns null for non-merged keys (e.g. stdio file-config servers
|
|
173
|
+
* like `claude-in-chrome`) so callers can treat them as always-visible.
|
|
174
|
+
*
|
|
175
|
+
* `hub_<orgId>_<name>` entries (pulled from a remote hub via
|
|
176
|
+
* `hub-client.ts`) project to `scope: "org"` so they pass through the same
|
|
177
|
+
* per-request visibility gate as locally-stored org servers — the tool is
|
|
178
|
+
* only visible to requests whose active org matches the hub entry's org.
|
|
179
|
+
*/
|
|
180
|
+
export function parseMergedKey(keyOrToolName) {
|
|
181
|
+
let key = keyOrToolName;
|
|
182
|
+
if (key.startsWith("mcp__")) {
|
|
183
|
+
const rest = key.slice("mcp__".length);
|
|
184
|
+
const idx = rest.indexOf("__");
|
|
185
|
+
key = idx >= 0 ? rest.slice(0, idx) : rest;
|
|
186
|
+
}
|
|
187
|
+
const m = /^(user|org|hub)_([^_]+)_(.+)$/.exec(key);
|
|
188
|
+
if (!m)
|
|
189
|
+
return null;
|
|
190
|
+
const prefix = m[1];
|
|
191
|
+
// Hub-sourced servers are scoped to the org they came from — treat them
|
|
192
|
+
// as org-scope for visibility purposes (see isMcpToolAllowedForRequest).
|
|
193
|
+
const scope = prefix === "user" ? "user" : "org";
|
|
194
|
+
return {
|
|
195
|
+
scope,
|
|
196
|
+
owner: m[2],
|
|
197
|
+
name: m[3],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=remote-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-store.js","sourceRoot":"","sources":["../../src/mcp-client/remote-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP routes for user- and org-scope remote MCP server management.
|
|
3
|
+
*
|
|
4
|
+
* Mounted under `/_agent-native/mcp/servers` by `agent-chat-plugin` —
|
|
5
|
+
* requires a reference to the running `McpClientManager` so mutations can
|
|
6
|
+
* hot-reload the configured server set.
|
|
7
|
+
*
|
|
8
|
+
* GET /_agent-native/mcp/servers list user + org servers
|
|
9
|
+
* POST /_agent-native/mcp/servers add a server
|
|
10
|
+
* DELETE /_agent-native/mcp/servers/:id remove a server (scope via ?scope=)
|
|
11
|
+
* POST /_agent-native/mcp/servers/:id/test dry-run connect (no persist)
|
|
12
|
+
* POST /_agent-native/mcp/servers/test dry-run a URL before persisting
|
|
13
|
+
*/
|
|
14
|
+
import type { McpClientManager } from "./manager.js";
|
|
15
|
+
import type { McpConfig } from "./config.js";
|
|
16
|
+
import { type RemoteMcpScope } from "./remote-store.js";
|
|
17
|
+
export interface ClientServer {
|
|
18
|
+
id: string;
|
|
19
|
+
scope: RemoteMcpScope;
|
|
20
|
+
name: string;
|
|
21
|
+
url: string;
|
|
22
|
+
headers?: Record<string, {
|
|
23
|
+
set: true;
|
|
24
|
+
}>;
|
|
25
|
+
description?: string;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
/** The key under which this server is registered in the running MCP manager. */
|
|
28
|
+
mergedId: string;
|
|
29
|
+
status: ServerStatus;
|
|
30
|
+
}
|
|
31
|
+
type ServerStatus = {
|
|
32
|
+
state: "connected";
|
|
33
|
+
toolCount: number;
|
|
34
|
+
} | {
|
|
35
|
+
state: "error";
|
|
36
|
+
error: string;
|
|
37
|
+
} | {
|
|
38
|
+
state: "unknown";
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Build the merged MCP config the manager should run with: file/env config
|
|
42
|
+
* plus **every** user-scope and org-scope remote server persisted in the
|
|
43
|
+
* settings store. Scanning all scopes means a mutation from one user's
|
|
44
|
+
* session never drops another user's servers from the running manager.
|
|
45
|
+
*
|
|
46
|
+
* Each persisted server's merged key includes its owner discriminator
|
|
47
|
+
* (`user_<emailhash>_<name>` or `org_<orgId>_<name>`) so two users' servers
|
|
48
|
+
* with the same name coexist; the request-time gate in
|
|
49
|
+
* `isMcpToolAllowedForRequest` then scopes tool visibility back down to the
|
|
50
|
+
* calling user.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildMergedConfig(): Promise<McpConfig | null>;
|
|
53
|
+
export declare function mountMcpServersRoutes(nitroApp: any, manager: McpClientManager): void;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/mcp-client/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,aAAa,CAAC;AAE9D,OAAO,EAOL,KAAK,cAAc,EAEpB,MAAM,mBAAmB,CAAC;AAgC3B,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,cAAc,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,KAAK,YAAY,GACb;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AAiBzB;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CA4CnE;AAoCD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,GAAG,EACb,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAiDN"}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP routes for user- and org-scope remote MCP server management.
|
|
3
|
+
*
|
|
4
|
+
* Mounted under `/_agent-native/mcp/servers` by `agent-chat-plugin` —
|
|
5
|
+
* requires a reference to the running `McpClientManager` so mutations can
|
|
6
|
+
* hot-reload the configured server set.
|
|
7
|
+
*
|
|
8
|
+
* GET /_agent-native/mcp/servers list user + org servers
|
|
9
|
+
* POST /_agent-native/mcp/servers add a server
|
|
10
|
+
* DELETE /_agent-native/mcp/servers/:id remove a server (scope via ?scope=)
|
|
11
|
+
* POST /_agent-native/mcp/servers/:id/test dry-run connect (no persist)
|
|
12
|
+
* POST /_agent-native/mcp/servers/test dry-run a URL before persisting
|
|
13
|
+
*/
|
|
14
|
+
import { defineEventHandler, getMethod, getQuery, setResponseHeader, setResponseStatus, } from "h3";
|
|
15
|
+
import { getH3App } from "../server/framework-request-handler.js";
|
|
16
|
+
import { readBody } from "../server/h3-helpers.js";
|
|
17
|
+
import { getOrgContext } from "../org/context.js";
|
|
18
|
+
import { getSession } from "../server/auth.js";
|
|
19
|
+
import { getAllSettings } from "../settings/store.js";
|
|
20
|
+
import { loadMcpConfig, autoDetectMcpConfig } from "./config.js";
|
|
21
|
+
import { addRemoteServer, listRemoteServers, mergedConfigKey, removeRemoteServer, toHttpServerConfig, validateRemoteUrl, } from "./remote-store.js";
|
|
22
|
+
import { fetchHubServers } from "./hub-client.js";
|
|
23
|
+
/** Redact obvious auth header values before sending to the client. */
|
|
24
|
+
function redactHeaders(headers) {
|
|
25
|
+
if (!headers)
|
|
26
|
+
return undefined;
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const k of Object.keys(headers))
|
|
29
|
+
out[k] = { set: true };
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
function projectForClient(stored, scope, ownerId, status) {
|
|
33
|
+
return {
|
|
34
|
+
id: stored.id,
|
|
35
|
+
scope,
|
|
36
|
+
name: stored.name,
|
|
37
|
+
url: stored.url,
|
|
38
|
+
headers: redactHeaders(stored.headers),
|
|
39
|
+
description: stored.description,
|
|
40
|
+
createdAt: stored.createdAt,
|
|
41
|
+
mergedId: mergedConfigKey(scope, stored, ownerId),
|
|
42
|
+
status,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function statusFor(manager, mergedId) {
|
|
46
|
+
const snap = manager.getStatus();
|
|
47
|
+
if (snap.connectedServers.includes(mergedId)) {
|
|
48
|
+
const toolCount = snap.tools.filter((t) => t.source === mergedId).length;
|
|
49
|
+
return { state: "connected", toolCount };
|
|
50
|
+
}
|
|
51
|
+
if (snap.errors[mergedId]) {
|
|
52
|
+
return { state: "error", error: snap.errors[mergedId] };
|
|
53
|
+
}
|
|
54
|
+
if (snap.configuredServers.includes(mergedId)) {
|
|
55
|
+
return { state: "unknown" };
|
|
56
|
+
}
|
|
57
|
+
return { state: "unknown" };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build the merged MCP config the manager should run with: file/env config
|
|
61
|
+
* plus **every** user-scope and org-scope remote server persisted in the
|
|
62
|
+
* settings store. Scanning all scopes means a mutation from one user's
|
|
63
|
+
* session never drops another user's servers from the running manager.
|
|
64
|
+
*
|
|
65
|
+
* Each persisted server's merged key includes its owner discriminator
|
|
66
|
+
* (`user_<emailhash>_<name>` or `org_<orgId>_<name>`) so two users' servers
|
|
67
|
+
* with the same name coexist; the request-time gate in
|
|
68
|
+
* `isMcpToolAllowedForRequest` then scopes tool visibility back down to the
|
|
69
|
+
* calling user.
|
|
70
|
+
*/
|
|
71
|
+
export async function buildMergedConfig() {
|
|
72
|
+
const base = loadMcpConfig() ?? autoDetectMcpConfig();
|
|
73
|
+
const servers = { ...(base?.servers ?? {}) };
|
|
74
|
+
const all = await getAllSettings().catch(() => ({}));
|
|
75
|
+
for (const [fullKey, value] of Object.entries(all)) {
|
|
76
|
+
const userMatch = /^u:([^:]+):mcp-servers-remote$/.exec(fullKey);
|
|
77
|
+
const orgMatch = /^o:([^:]+):mcp-servers-remote$/.exec(fullKey);
|
|
78
|
+
let scope = null;
|
|
79
|
+
let ownerId = null;
|
|
80
|
+
if (userMatch) {
|
|
81
|
+
scope = "user";
|
|
82
|
+
ownerId = userMatch[1];
|
|
83
|
+
}
|
|
84
|
+
else if (orgMatch) {
|
|
85
|
+
scope = "org";
|
|
86
|
+
ownerId = orgMatch[1];
|
|
87
|
+
}
|
|
88
|
+
if (!scope || !ownerId)
|
|
89
|
+
continue;
|
|
90
|
+
const list = value.servers;
|
|
91
|
+
if (!Array.isArray(list))
|
|
92
|
+
continue;
|
|
93
|
+
for (const stored of list) {
|
|
94
|
+
if (!stored || typeof stored.url !== "string" || !stored.name)
|
|
95
|
+
continue;
|
|
96
|
+
servers[mergedConfigKey(scope, stored, ownerId)] =
|
|
97
|
+
toHttpServerConfig(stored);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Hub-consume: if this app is configured to consume from a remote hub
|
|
101
|
+
// (AGENT_NATIVE_MCP_HUB_URL + AGENT_NATIVE_MCP_HUB_TOKEN), pull its
|
|
102
|
+
// org-scope servers and merge. Hub entries use `hub_<orgId>_<name>` so
|
|
103
|
+
// they never collide with local `org_<orgId>_<name>` rows.
|
|
104
|
+
try {
|
|
105
|
+
const hubServers = await fetchHubServers();
|
|
106
|
+
for (const [mergedKey, cfg] of Object.entries(hubServers)) {
|
|
107
|
+
servers[mergedKey] = cfg;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
console.warn(`[mcp-client] hub merge failed: ${err?.message ?? err}. Continuing with local config.`);
|
|
112
|
+
}
|
|
113
|
+
if (Object.keys(servers).length === 0)
|
|
114
|
+
return null;
|
|
115
|
+
return { servers, source: base?.source ?? "merged" };
|
|
116
|
+
}
|
|
117
|
+
async function resolveContextForRequest(event) {
|
|
118
|
+
let email = null;
|
|
119
|
+
try {
|
|
120
|
+
const session = await getSession(event);
|
|
121
|
+
email = session?.email ?? null;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
email = null;
|
|
125
|
+
}
|
|
126
|
+
let orgId = null;
|
|
127
|
+
let role = null;
|
|
128
|
+
try {
|
|
129
|
+
const ctx = await getOrgContext(event);
|
|
130
|
+
orgId = ctx.orgId;
|
|
131
|
+
role = ctx.role;
|
|
132
|
+
if (!email)
|
|
133
|
+
email = ctx.email;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// ignore — no org context
|
|
137
|
+
}
|
|
138
|
+
// In solo/dev mode `getSession` can return nothing but getOrgContext still
|
|
139
|
+
// yields the fallback email. Keep that as the user-scope id so single-user
|
|
140
|
+
// desktop installs work without a login flow.
|
|
141
|
+
if (!email)
|
|
142
|
+
email = "local@localhost";
|
|
143
|
+
return { email, orgId, role };
|
|
144
|
+
}
|
|
145
|
+
async function reconfigureManager(manager) {
|
|
146
|
+
const merged = await buildMergedConfig();
|
|
147
|
+
await manager.reconfigure(merged);
|
|
148
|
+
}
|
|
149
|
+
export function mountMcpServersRoutes(nitroApp, manager) {
|
|
150
|
+
if (globalThis.__agentNativeMcpServersMounted)
|
|
151
|
+
return;
|
|
152
|
+
globalThis.__agentNativeMcpServersMounted = true;
|
|
153
|
+
try {
|
|
154
|
+
getH3App(nitroApp).use("/_agent-native/mcp/servers", defineEventHandler(async (event) => {
|
|
155
|
+
const method = getMethod(event);
|
|
156
|
+
const pathname = (event.url?.pathname || "")
|
|
157
|
+
.replace(/^\/+/, "")
|
|
158
|
+
.replace(/\/+$/, "");
|
|
159
|
+
const parts = pathname ? pathname.split("/") : [];
|
|
160
|
+
setResponseHeader(event, "Content-Type", "application/json");
|
|
161
|
+
// POST /servers/test — dry-run a URL+headers before persisting
|
|
162
|
+
if (method === "POST" && parts.length === 1 && parts[0] === "test") {
|
|
163
|
+
return handleTestUrl(event);
|
|
164
|
+
}
|
|
165
|
+
// Collection root
|
|
166
|
+
if (parts.length === 0) {
|
|
167
|
+
if (method === "GET")
|
|
168
|
+
return handleList(event, manager);
|
|
169
|
+
if (method === "POST")
|
|
170
|
+
return handleAdd(event, manager);
|
|
171
|
+
setResponseStatus(event, 405);
|
|
172
|
+
return { error: "Method not allowed" };
|
|
173
|
+
}
|
|
174
|
+
// /:id / /:id/test
|
|
175
|
+
if (parts.length === 1 || parts.length === 2) {
|
|
176
|
+
const id = parts[0];
|
|
177
|
+
if (parts.length === 2 && parts[1] === "test" && method === "POST") {
|
|
178
|
+
return handleTestExisting(event, manager, id);
|
|
179
|
+
}
|
|
180
|
+
if (parts.length === 1 && method === "DELETE") {
|
|
181
|
+
return handleDelete(event, manager, id);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
setResponseStatus(event, 404);
|
|
185
|
+
return { error: "Not found" };
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
console.warn(`[mcp-client] Failed to mount /_agent-native/mcp/servers: ${err?.message ?? err}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function handleList(event, manager) {
|
|
193
|
+
const { email, orgId, role } = await resolveContextForRequest(event);
|
|
194
|
+
const userServers = email ? await listRemoteServers("user", email) : [];
|
|
195
|
+
const orgServers = orgId ? await listRemoteServers("org", orgId) : [];
|
|
196
|
+
return {
|
|
197
|
+
user: userServers.map((s) => projectForClient(s, "user", email ?? "", statusFor(manager, mergedConfigKey("user", s, email ?? "")))),
|
|
198
|
+
org: orgServers.map((s) => projectForClient(s, "org", orgId ?? "", statusFor(manager, mergedConfigKey("org", s, orgId ?? "")))),
|
|
199
|
+
orgId,
|
|
200
|
+
role,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
async function handleAdd(event, manager) {
|
|
204
|
+
const body = (await readBody(event).catch(() => ({})));
|
|
205
|
+
const scope = body.scope === "org" ? "org" : body.scope === "user" ? "user" : null;
|
|
206
|
+
if (!scope) {
|
|
207
|
+
setResponseStatus(event, 400);
|
|
208
|
+
return { error: 'scope must be "user" or "org"' };
|
|
209
|
+
}
|
|
210
|
+
const name = typeof body.name === "string" ? body.name : "";
|
|
211
|
+
const url = typeof body.url === "string" ? body.url : "";
|
|
212
|
+
if (!name.trim() || !url.trim()) {
|
|
213
|
+
setResponseStatus(event, 400);
|
|
214
|
+
return { error: "name and url are required" };
|
|
215
|
+
}
|
|
216
|
+
const headers = normalizeHeaders(body.headers);
|
|
217
|
+
const description = typeof body.description === "string" ? body.description : undefined;
|
|
218
|
+
const { email, orgId, role } = await resolveContextForRequest(event);
|
|
219
|
+
let scopeId = null;
|
|
220
|
+
if (scope === "user") {
|
|
221
|
+
scopeId = email;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
if (!orgId) {
|
|
225
|
+
setResponseStatus(event, 400);
|
|
226
|
+
return {
|
|
227
|
+
error: "You must belong to an organization to add an org-scope server",
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (role !== "owner" && role !== "admin") {
|
|
231
|
+
setResponseStatus(event, 403);
|
|
232
|
+
return { error: "Only owners and admins can add org-scope MCP servers" };
|
|
233
|
+
}
|
|
234
|
+
scopeId = orgId;
|
|
235
|
+
}
|
|
236
|
+
if (!scopeId) {
|
|
237
|
+
setResponseStatus(event, 401);
|
|
238
|
+
return { error: "Authentication required" };
|
|
239
|
+
}
|
|
240
|
+
const result = await addRemoteServer(scope, scopeId, {
|
|
241
|
+
name,
|
|
242
|
+
url,
|
|
243
|
+
headers,
|
|
244
|
+
description,
|
|
245
|
+
});
|
|
246
|
+
if (result.ok !== true) {
|
|
247
|
+
setResponseStatus(event, 400);
|
|
248
|
+
return { error: result.error };
|
|
249
|
+
}
|
|
250
|
+
await reconfigureManager(manager);
|
|
251
|
+
const mergedId = mergedConfigKey(scope, result.server, scopeId);
|
|
252
|
+
return {
|
|
253
|
+
ok: true,
|
|
254
|
+
server: projectForClient(result.server, scope, scopeId, statusFor(manager, mergedId)),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
async function handleDelete(event, manager, id) {
|
|
258
|
+
const scope = getQuery(event).scope;
|
|
259
|
+
const parsedScope = scope === "org" ? "org" : scope === "user" ? "user" : null;
|
|
260
|
+
if (!parsedScope) {
|
|
261
|
+
setResponseStatus(event, 400);
|
|
262
|
+
return { error: 'scope query param must be "user" or "org"' };
|
|
263
|
+
}
|
|
264
|
+
const { email, orgId, role } = await resolveContextForRequest(event);
|
|
265
|
+
let scopeId = null;
|
|
266
|
+
if (parsedScope === "user") {
|
|
267
|
+
scopeId = email;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
if (!orgId) {
|
|
271
|
+
setResponseStatus(event, 400);
|
|
272
|
+
return { error: "No active organization" };
|
|
273
|
+
}
|
|
274
|
+
if (role !== "owner" && role !== "admin") {
|
|
275
|
+
setResponseStatus(event, 403);
|
|
276
|
+
return {
|
|
277
|
+
error: "Only owners and admins can remove org-scope MCP servers",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
scopeId = orgId;
|
|
281
|
+
}
|
|
282
|
+
if (!scopeId) {
|
|
283
|
+
setResponseStatus(event, 401);
|
|
284
|
+
return { error: "Authentication required" };
|
|
285
|
+
}
|
|
286
|
+
const removed = await removeRemoteServer(parsedScope, scopeId, id);
|
|
287
|
+
if (!removed) {
|
|
288
|
+
setResponseStatus(event, 404);
|
|
289
|
+
return { error: "Server not found" };
|
|
290
|
+
}
|
|
291
|
+
await reconfigureManager(manager);
|
|
292
|
+
return { ok: true };
|
|
293
|
+
}
|
|
294
|
+
async function handleTestUrl(event) {
|
|
295
|
+
const body = (await readBody(event).catch(() => ({})));
|
|
296
|
+
const url = typeof body.url === "string" ? body.url : "";
|
|
297
|
+
const check = validateRemoteUrl(url);
|
|
298
|
+
if (!check.ok) {
|
|
299
|
+
setResponseStatus(event, 400);
|
|
300
|
+
return { ok: false, error: check.error };
|
|
301
|
+
}
|
|
302
|
+
const headers = normalizeHeaders(body.headers);
|
|
303
|
+
const result = await tryConnect(check.url.toString(), headers);
|
|
304
|
+
if (result.ok !== true) {
|
|
305
|
+
setResponseStatus(event, 400);
|
|
306
|
+
return { ok: false, error: result.error };
|
|
307
|
+
}
|
|
308
|
+
return { ok: true, toolCount: result.toolCount, tools: result.tools };
|
|
309
|
+
}
|
|
310
|
+
async function handleTestExisting(event, manager, id) {
|
|
311
|
+
const scope = getQuery(event).scope;
|
|
312
|
+
const parsedScope = scope === "org" ? "org" : scope === "user" ? "user" : null;
|
|
313
|
+
if (!parsedScope) {
|
|
314
|
+
setResponseStatus(event, 400);
|
|
315
|
+
return { error: 'scope query param must be "user" or "org"' };
|
|
316
|
+
}
|
|
317
|
+
const { email, orgId } = await resolveContextForRequest(event);
|
|
318
|
+
const scopeId = parsedScope === "user" ? email : orgId;
|
|
319
|
+
if (!scopeId) {
|
|
320
|
+
setResponseStatus(event, 401);
|
|
321
|
+
return { error: "Authentication required" };
|
|
322
|
+
}
|
|
323
|
+
const list = await listRemoteServers(parsedScope, scopeId);
|
|
324
|
+
const server = list.find((s) => s.id === id);
|
|
325
|
+
if (!server) {
|
|
326
|
+
setResponseStatus(event, 404);
|
|
327
|
+
return { error: "Server not found" };
|
|
328
|
+
}
|
|
329
|
+
const result = await tryConnect(server.url, server.headers);
|
|
330
|
+
if (result.ok !== true) {
|
|
331
|
+
setResponseStatus(event, 400);
|
|
332
|
+
return { ok: false, error: result.error };
|
|
333
|
+
}
|
|
334
|
+
return { ok: true, toolCount: result.toolCount, tools: result.tools };
|
|
335
|
+
}
|
|
336
|
+
async function tryConnect(url, headers) {
|
|
337
|
+
try {
|
|
338
|
+
const [{ Client }, { StreamableHTTPClientTransport }] = await Promise.all([
|
|
339
|
+
import("@modelcontextprotocol/sdk/client/index.js"),
|
|
340
|
+
import("@modelcontextprotocol/sdk/client/streamableHttp.js"),
|
|
341
|
+
]);
|
|
342
|
+
const requestInit = {};
|
|
343
|
+
if (headers && Object.keys(headers).length > 0) {
|
|
344
|
+
requestInit.headers = headers;
|
|
345
|
+
}
|
|
346
|
+
const transport = new StreamableHTTPClientTransport(new URL(url), {
|
|
347
|
+
requestInit,
|
|
348
|
+
});
|
|
349
|
+
const client = new Client({ name: "agent-native-mcp-client-test", version: "1.0.0" }, { capabilities: {} });
|
|
350
|
+
try {
|
|
351
|
+
await client.connect(transport);
|
|
352
|
+
const listed = await client.listTools();
|
|
353
|
+
const names = (listed?.tools ?? []).map((t) => t.name);
|
|
354
|
+
return { ok: true, toolCount: names.length, tools: names };
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
try {
|
|
358
|
+
await client.close();
|
|
359
|
+
}
|
|
360
|
+
catch { }
|
|
361
|
+
try {
|
|
362
|
+
await transport.close();
|
|
363
|
+
}
|
|
364
|
+
catch { }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
return { ok: false, error: err?.message ?? String(err) };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function normalizeHeaders(input) {
|
|
372
|
+
if (!input || typeof input !== "object")
|
|
373
|
+
return undefined;
|
|
374
|
+
const out = {};
|
|
375
|
+
for (const [k, v] of Object.entries(input)) {
|
|
376
|
+
if (typeof k !== "string" || !k.trim())
|
|
377
|
+
continue;
|
|
378
|
+
if (typeof v !== "string")
|
|
379
|
+
continue;
|
|
380
|
+
out[k.trim()] = v;
|
|
381
|
+
}
|
|
382
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
383
|
+
}
|
|
384
|
+
//# sourceMappingURL=routes.js.map
|