@agent-native/core 0.7.4 → 0.7.7

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.
Files changed (250) hide show
  1. package/README.md +6 -5
  2. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  3. package/dist/agent/engine/anthropic-engine.js +8 -4
  4. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  5. package/dist/agent/engine/types.d.ts +1 -1
  6. package/dist/agent/engine/types.d.ts.map +1 -1
  7. package/dist/agent/production-agent.d.ts +7 -0
  8. package/dist/agent/production-agent.d.ts.map +1 -1
  9. package/dist/agent/production-agent.js +153 -118
  10. package/dist/agent/production-agent.js.map +1 -1
  11. package/dist/agent/run-manager.d.ts +4 -0
  12. package/dist/agent/run-manager.d.ts.map +1 -1
  13. package/dist/agent/run-manager.js +46 -25
  14. package/dist/agent/run-manager.js.map +1 -1
  15. package/dist/agent/run-store.d.ts +12 -3
  16. package/dist/agent/run-store.d.ts.map +1 -1
  17. package/dist/agent/run-store.js +25 -4
  18. package/dist/agent/run-store.js.map +1 -1
  19. package/dist/chat-threads/store.d.ts +13 -0
  20. package/dist/chat-threads/store.d.ts.map +1 -1
  21. package/dist/chat-threads/store.js +66 -10
  22. package/dist/chat-threads/store.js.map +1 -1
  23. package/dist/cli/create.d.ts.map +1 -1
  24. package/dist/cli/create.js +8 -1
  25. package/dist/cli/create.js.map +1 -1
  26. package/dist/cli/index.js +8 -0
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/cli/info.d.ts +2 -0
  29. package/dist/cli/info.d.ts.map +1 -0
  30. package/dist/cli/info.js +103 -0
  31. package/dist/cli/info.js.map +1 -0
  32. package/dist/client/AssistantChat.d.ts.map +1 -1
  33. package/dist/client/AssistantChat.js +249 -85
  34. package/dist/client/AssistantChat.js.map +1 -1
  35. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  36. package/dist/client/agent-chat-adapter.js +12 -1
  37. package/dist/client/agent-chat-adapter.js.map +1 -1
  38. package/dist/client/composer/TiptapComposer.d.ts +3 -1
  39. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  40. package/dist/client/composer/TiptapComposer.js +46 -2
  41. package/dist/client/composer/TiptapComposer.js.map +1 -1
  42. package/dist/client/composer/VoiceButton.d.ts +21 -0
  43. package/dist/client/composer/VoiceButton.d.ts.map +1 -0
  44. package/dist/client/composer/VoiceButton.js +51 -0
  45. package/dist/client/composer/VoiceButton.js.map +1 -0
  46. package/dist/client/composer/useVoiceDictation.d.ts +38 -0
  47. package/dist/client/composer/useVoiceDictation.d.ts.map +1 -0
  48. package/dist/client/composer/useVoiceDictation.js +398 -0
  49. package/dist/client/composer/useVoiceDictation.js.map +1 -0
  50. package/dist/client/onboarding/OnboardingPanel.js +2 -2
  51. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  52. package/dist/client/org/OrgSwitcher.d.ts +5 -4
  53. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  54. package/dist/client/org/OrgSwitcher.js +90 -24
  55. package/dist/client/org/OrgSwitcher.js.map +1 -1
  56. package/dist/client/resources/McpServerDetail.d.ts +15 -0
  57. package/dist/client/resources/McpServerDetail.d.ts.map +1 -0
  58. package/dist/client/resources/McpServerDetail.js +65 -0
  59. package/dist/client/resources/McpServerDetail.js.map +1 -0
  60. package/dist/client/resources/ResourceEditor.js +1 -1
  61. package/dist/client/resources/ResourceEditor.js.map +1 -1
  62. package/dist/client/resources/ResourceTree.d.ts +6 -1
  63. package/dist/client/resources/ResourceTree.d.ts.map +1 -1
  64. package/dist/client/resources/ResourceTree.js +18 -7
  65. package/dist/client/resources/ResourceTree.js.map +1 -1
  66. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  67. package/dist/client/resources/ResourcesPanel.js +191 -20
  68. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  69. package/dist/client/resources/use-mcp-servers.d.ts +68 -0
  70. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -0
  71. package/dist/client/resources/use-mcp-servers.js +83 -0
  72. package/dist/client/resources/use-mcp-servers.js.map +1 -0
  73. package/dist/client/resources/use-resources.d.ts +39 -1
  74. package/dist/client/resources/use-resources.d.ts.map +1 -1
  75. package/dist/client/resources/use-resources.js +102 -0
  76. package/dist/client/resources/use-resources.js.map +1 -1
  77. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  78. package/dist/client/settings/SettingsPanel.js +3 -2
  79. package/dist/client/settings/SettingsPanel.js.map +1 -1
  80. package/dist/client/settings/VoiceTranscriptionSection.d.ts +14 -0
  81. package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -0
  82. package/dist/client/settings/VoiceTranscriptionSection.js +111 -0
  83. package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -0
  84. package/dist/client/sharing/ShareButton.d.ts +6 -4
  85. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  86. package/dist/client/sharing/ShareButton.js +299 -34
  87. package/dist/client/sharing/ShareButton.js.map +1 -1
  88. package/dist/client/sharing/ShareDialog.d.ts +22 -4
  89. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  90. package/dist/client/sharing/ShareDialog.js +170 -148
  91. package/dist/client/sharing/ShareDialog.js.map +1 -1
  92. package/dist/client/sharing/VisibilityBadge.d.ts.map +1 -1
  93. package/dist/client/sharing/VisibilityBadge.js +1 -2
  94. package/dist/client/sharing/VisibilityBadge.js.map +1 -1
  95. package/dist/client/use-action.d.ts.map +1 -1
  96. package/dist/client/use-action.js +20 -1
  97. package/dist/client/use-action.js.map +1 -1
  98. package/dist/db/migrations.d.ts +18 -3
  99. package/dist/db/migrations.d.ts.map +1 -1
  100. package/dist/db/migrations.js +25 -3
  101. package/dist/db/migrations.js.map +1 -1
  102. package/dist/deploy/workspace-core.js +2 -2
  103. package/dist/mcp-client/config.d.ts +20 -1
  104. package/dist/mcp-client/config.d.ts.map +1 -1
  105. package/dist/mcp-client/config.js +28 -11
  106. package/dist/mcp-client/config.js.map +1 -1
  107. package/dist/mcp-client/hub-client.d.ts +38 -0
  108. package/dist/mcp-client/hub-client.d.ts.map +1 -0
  109. package/dist/mcp-client/hub-client.js +147 -0
  110. package/dist/mcp-client/hub-client.js.map +1 -0
  111. package/dist/mcp-client/hub-routes.d.ts +42 -0
  112. package/dist/mcp-client/hub-routes.d.ts.map +1 -0
  113. package/dist/mcp-client/hub-routes.js +114 -0
  114. package/dist/mcp-client/hub-routes.js.map +1 -0
  115. package/dist/mcp-client/index.d.ts +15 -0
  116. package/dist/mcp-client/index.d.ts.map +1 -1
  117. package/dist/mcp-client/index.js +35 -0
  118. package/dist/mcp-client/index.js.map +1 -1
  119. package/dist/mcp-client/manager.d.ts +54 -8
  120. package/dist/mcp-client/manager.d.ts.map +1 -1
  121. package/dist/mcp-client/manager.js +276 -59
  122. package/dist/mcp-client/manager.js.map +1 -1
  123. package/dist/mcp-client/remote-store.d.ts +102 -0
  124. package/dist/mcp-client/remote-store.d.ts.map +1 -0
  125. package/dist/mcp-client/remote-store.js +200 -0
  126. package/dist/mcp-client/remote-store.js.map +1 -0
  127. package/dist/mcp-client/routes.d.ts +55 -0
  128. package/dist/mcp-client/routes.d.ts.map +1 -0
  129. package/dist/mcp-client/routes.js +384 -0
  130. package/dist/mcp-client/routes.js.map +1 -0
  131. package/dist/mcp-client/visibility.d.ts +16 -0
  132. package/dist/mcp-client/visibility.d.ts.map +1 -0
  133. package/dist/mcp-client/visibility.js +45 -0
  134. package/dist/mcp-client/visibility.js.map +1 -0
  135. package/dist/org/context.js +2 -2
  136. package/dist/org/context.js.map +1 -1
  137. package/dist/org/handlers.js +2 -2
  138. package/dist/org/handlers.js.map +1 -1
  139. package/dist/resources/handlers.d.ts.map +1 -1
  140. package/dist/resources/handlers.js +30 -0
  141. package/dist/resources/handlers.js.map +1 -1
  142. package/dist/secrets/register-framework-secrets.d.ts +13 -0
  143. package/dist/secrets/register-framework-secrets.d.ts.map +1 -0
  144. package/dist/secrets/register-framework-secrets.js +59 -0
  145. package/dist/secrets/register-framework-secrets.js.map +1 -0
  146. package/dist/secrets/register.d.ts.map +1 -1
  147. package/dist/secrets/register.js +8 -1
  148. package/dist/secrets/register.js.map +1 -1
  149. package/dist/server/action-routes.d.ts.map +1 -1
  150. package/dist/server/action-routes.js +22 -2
  151. package/dist/server/action-routes.js.map +1 -1
  152. package/dist/server/agent-chat-plugin.d.ts +16 -0
  153. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  154. package/dist/server/agent-chat-plugin.js +237 -70
  155. package/dist/server/agent-chat-plugin.js.map +1 -1
  156. package/dist/server/app-url.d.ts.map +1 -1
  157. package/dist/server/app-url.js +11 -3
  158. package/dist/server/app-url.js.map +1 -1
  159. package/dist/server/auth.d.ts.map +1 -1
  160. package/dist/server/auth.js +50 -0
  161. package/dist/server/auth.js.map +1 -1
  162. package/dist/server/better-auth-instance.d.ts.map +1 -1
  163. package/dist/server/better-auth-instance.js +99 -4
  164. package/dist/server/better-auth-instance.js.map +1 -1
  165. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  166. package/dist/server/core-routes-plugin.js +44 -0
  167. package/dist/server/core-routes-plugin.js.map +1 -1
  168. package/dist/server/create-server.d.ts.map +1 -1
  169. package/dist/server/create-server.js +6 -0
  170. package/dist/server/create-server.js.map +1 -1
  171. package/dist/server/date-utils.d.ts +15 -0
  172. package/dist/server/date-utils.d.ts.map +1 -0
  173. package/dist/server/date-utils.js +41 -0
  174. package/dist/server/date-utils.js.map +1 -0
  175. package/dist/server/index.d.ts +2 -1
  176. package/dist/server/index.d.ts.map +1 -1
  177. package/dist/server/index.js +2 -1
  178. package/dist/server/index.js.map +1 -1
  179. package/dist/server/onboarding-html.d.ts +3 -0
  180. package/dist/server/onboarding-html.d.ts.map +1 -1
  181. package/dist/server/onboarding-html.js +13 -3
  182. package/dist/server/onboarding-html.js.map +1 -1
  183. package/dist/server/request-context.d.ts +9 -0
  184. package/dist/server/request-context.d.ts.map +1 -1
  185. package/dist/server/request-context.js +10 -0
  186. package/dist/server/request-context.js.map +1 -1
  187. package/dist/server/transcribe-voice.d.ts +26 -0
  188. package/dist/server/transcribe-voice.d.ts.map +1 -0
  189. package/dist/server/transcribe-voice.js +143 -0
  190. package/dist/server/transcribe-voice.js.map +1 -0
  191. package/dist/styles/agent-native.css +111 -0
  192. package/dist/tailwind.preset.d.ts +2 -2
  193. package/dist/tailwind.preset.d.ts.map +1 -1
  194. package/dist/tailwind.preset.js +27 -7
  195. package/dist/tailwind.preset.js.map +1 -1
  196. package/dist/templates/default/app/global.css +65 -68
  197. package/dist/templates/default/components.json +1 -1
  198. package/dist/templates/default/package.json +2 -4
  199. package/dist/templates/default/vite.config.ts +3 -0
  200. package/dist/templates/workspace-core/package.json +1 -4
  201. package/dist/templates/workspace-core/src/index.ts +1 -1
  202. package/dist/templates/workspace-core/styles/tokens.css +22 -0
  203. package/dist/templates/workspace-core/tsconfig.json +1 -1
  204. package/dist/vite/client.d.ts +6 -0
  205. package/dist/vite/client.d.ts.map +1 -1
  206. package/dist/vite/client.js +18 -1
  207. package/dist/vite/client.js.map +1 -1
  208. package/docs/content/actions.md +169 -74
  209. package/docs/content/agent-teams.md +139 -0
  210. package/docs/content/cloneable-saas.md +98 -0
  211. package/docs/content/creating-templates.md +9 -11
  212. package/docs/content/deployment.md +2 -9
  213. package/docs/content/drop-in-agent.md +200 -0
  214. package/docs/content/enterprise-workspace.md +22 -10
  215. package/docs/content/getting-started.md +34 -19
  216. package/docs/content/integrations.md +3 -3
  217. package/docs/content/key-concepts.md +50 -23
  218. package/docs/content/mcp-clients.md +71 -0
  219. package/docs/content/pure-agent-apps.md +69 -0
  220. package/docs/content/recurring-jobs.md +123 -0
  221. package/docs/content/skills-guide.md +8 -0
  222. package/docs/content/template-analytics.md +190 -0
  223. package/docs/content/template-calendar.md +151 -0
  224. package/docs/content/template-clips.md +55 -0
  225. package/docs/content/template-content.md +141 -0
  226. package/docs/content/template-dispatch.md +58 -0
  227. package/docs/content/template-forms.md +51 -0
  228. package/docs/content/template-mail.md +169 -0
  229. package/docs/content/template-slides.md +218 -0
  230. package/docs/content/template-starter.md +68 -0
  231. package/docs/content/template-video.md +162 -0
  232. package/docs/content/voice-input.md +59 -0
  233. package/docs/content/what-is-agent-native.md +142 -45
  234. package/docs/content/workspace-management.md +1 -0
  235. package/docs/content/{resources.md → workspace.md} +94 -42
  236. package/package.json +9 -16
  237. package/src/templates/default/app/global.css +65 -68
  238. package/src/templates/default/components.json +1 -1
  239. package/src/templates/default/package.json +2 -4
  240. package/src/templates/default/vite.config.ts +3 -0
  241. package/src/templates/workspace-core/package.json +1 -4
  242. package/src/templates/workspace-core/src/index.ts +1 -1
  243. package/src/templates/workspace-core/styles/tokens.css +22 -0
  244. package/src/templates/workspace-core/tsconfig.json +1 -1
  245. package/dist/templates/default/postcss.config.js +0 -6
  246. package/dist/templates/default/tailwind.config.ts +0 -7
  247. package/dist/templates/workspace-core/tailwind.preset.ts +0 -34
  248. package/src/templates/default/postcss.config.js +0 -6
  249. package/src/templates/default/tailwind.config.ts +0 -7
  250. 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