@agent-native/core 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/action.d.ts +27 -0
  2. package/dist/action.d.ts.map +1 -1
  3. package/dist/action.js +2 -0
  4. package/dist/action.js.map +1 -1
  5. package/dist/agent/production-agent.d.ts +4 -0
  6. package/dist/agent/production-agent.d.ts.map +1 -1
  7. package/dist/agent/production-agent.js.map +1 -1
  8. package/dist/cli/index.js +16 -0
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/mcp.d.ts +16 -0
  11. package/dist/cli/mcp.d.ts.map +1 -0
  12. package/dist/cli/mcp.js +583 -0
  13. package/dist/cli/mcp.js.map +1 -0
  14. package/dist/db/client.d.ts +27 -0
  15. package/dist/db/client.d.ts.map +1 -1
  16. package/dist/db/client.js +62 -19
  17. package/dist/db/client.js.map +1 -1
  18. package/dist/db/create-get-db.d.ts.map +1 -1
  19. package/dist/db/create-get-db.js +6 -9
  20. package/dist/db/create-get-db.js.map +1 -1
  21. package/dist/db/index.d.ts.map +1 -1
  22. package/dist/db/index.js +2 -1
  23. package/dist/db/index.js.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/mcp/build-server.d.ts +135 -0
  28. package/dist/mcp/build-server.d.ts.map +1 -0
  29. package/dist/mcp/build-server.js +274 -0
  30. package/dist/mcp/build-server.js.map +1 -0
  31. package/dist/mcp/builtin-tools.d.ts +32 -0
  32. package/dist/mcp/builtin-tools.d.ts.map +1 -0
  33. package/dist/mcp/builtin-tools.js +299 -0
  34. package/dist/mcp/builtin-tools.js.map +1 -0
  35. package/dist/mcp/index.d.ts +7 -0
  36. package/dist/mcp/index.d.ts.map +1 -1
  37. package/dist/mcp/index.js +8 -0
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/mcp/server.d.ts +3 -13
  40. package/dist/mcp/server.d.ts.map +1 -1
  41. package/dist/mcp/server.js +21 -175
  42. package/dist/mcp/server.js.map +1 -1
  43. package/dist/mcp/stdio.d.ts +44 -0
  44. package/dist/mcp/stdio.d.ts.map +1 -0
  45. package/dist/mcp/stdio.js +208 -0
  46. package/dist/mcp/stdio.js.map +1 -0
  47. package/dist/mcp/workspace-resolve.d.ts +68 -0
  48. package/dist/mcp/workspace-resolve.d.ts.map +1 -0
  49. package/dist/mcp/workspace-resolve.js +205 -0
  50. package/dist/mcp/workspace-resolve.js.map +1 -0
  51. package/dist/server/action-discovery.d.ts.map +1 -1
  52. package/dist/server/action-discovery.js +3 -0
  53. package/dist/server/action-discovery.js.map +1 -1
  54. package/dist/server/auth.d.ts +9 -0
  55. package/dist/server/auth.d.ts.map +1 -1
  56. package/dist/server/auth.js +25 -0
  57. package/dist/server/auth.js.map +1 -1
  58. package/dist/server/better-auth-instance.d.ts.map +1 -1
  59. package/dist/server/better-auth-instance.js +15 -10
  60. package/dist/server/better-auth-instance.js.map +1 -1
  61. package/dist/server/core-routes-plugin.d.ts +5 -0
  62. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  63. package/dist/server/core-routes-plugin.js +9 -0
  64. package/dist/server/core-routes-plugin.js.map +1 -1
  65. package/dist/server/deep-link.d.ts +55 -0
  66. package/dist/server/deep-link.d.ts.map +1 -0
  67. package/dist/server/deep-link.js +69 -0
  68. package/dist/server/deep-link.js.map +1 -0
  69. package/dist/server/index.d.ts +2 -0
  70. package/dist/server/index.d.ts.map +1 -1
  71. package/dist/server/index.js +2 -0
  72. package/dist/server/index.js.map +1 -1
  73. package/dist/server/open-route.d.ts +12 -0
  74. package/dist/server/open-route.d.ts.map +1 -0
  75. package/dist/server/open-route.js +128 -0
  76. package/dist/server/open-route.js.map +1 -0
  77. package/docs/content/external-agents.md +177 -0
  78. package/package.json +1 -1
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Generic cross-app MCP tools — a stable verb set every external agent gets
3
+ * regardless of which template it is talking to.
4
+ *
5
+ * These are merged into the MCP action registry by
6
+ * `createMCPServerForRequest` (see `build-server.ts`). **Precedence: template
7
+ * actions win.** If a template defines an action named `list_apps` /
8
+ * `open_app` / `ask_app` / `create_workspace_app` / `list_templates`, the
9
+ * template's `ActionEntry` overwrites the builtin of the same name. This is
10
+ * the same template-over-framework precedence `autoDiscoverActions` uses.
11
+ *
12
+ * | Tool | Side effects | Returns |
13
+ * | --------------------- | ------------ | ---------------------------------------- |
14
+ * | `list_apps` | none | `{ apps: [{ id, url, running }] }` |
15
+ * | `open_app` | none | `{ url }` (+ deep-link `link`) |
16
+ * | `ask_app` | agent loop | `{ app, response }` |
17
+ * | `create_workspace_app`| scaffolds | `{ name, url, port, deepLink }` (+ link) |
18
+ * | `list_templates` | none | `{ templates: [...] }` (allow-list only) |
19
+ *
20
+ * Node-only at call time (workspace resolution + scaffolding use `fs`), but
21
+ * the module has no top-level Node imports so it bundles fine alongside
22
+ * `mountMCP` — the Node bits are dynamically imported inside `run()`.
23
+ */
24
+ import { buildDeepLink } from "../server/deep-link.js";
25
+ /**
26
+ * Build an `ActionTool`. `parameters` is wrapped in the
27
+ * `{ type:"object", properties, required }` shape `createMCPServerForRequest`
28
+ * forwards verbatim as the MCP tool `inputSchema`.
29
+ */
30
+ function tool(description, parameters, required) {
31
+ if (!parameters)
32
+ return { description };
33
+ return {
34
+ description,
35
+ parameters: {
36
+ type: "object",
37
+ properties: parameters,
38
+ ...(required && required.length ? { required } : {}),
39
+ },
40
+ };
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // list_apps
44
+ // ---------------------------------------------------------------------------
45
+ function listAppsTool() {
46
+ return {
47
+ tool: tool("List the workspace apps and their local dev URLs/ports. Use this to " +
48
+ "discover which apps exist before opening or asking one. In a single-" +
49
+ "app project this returns just that app."),
50
+ readOnly: true,
51
+ parallelSafe: true,
52
+ run: async () => {
53
+ const { resolveWorkspace } = await import("./workspace-resolve.js");
54
+ const ws = await resolveWorkspace();
55
+ return {
56
+ workspace: ws.isWorkspace,
57
+ gatewayUrl: ws.gatewayUrl,
58
+ apps: ws.apps.map((a) => ({
59
+ id: a.id,
60
+ url: a.url,
61
+ port: a.port,
62
+ running: a.running,
63
+ })),
64
+ };
65
+ },
66
+ };
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // open_app
70
+ // ---------------------------------------------------------------------------
71
+ function openAppTool() {
72
+ return {
73
+ tool: tool("Build a deep link that opens an app at a specific view/record. No side " +
74
+ "effects — returns a URL the user can click to land in the running UI. " +
75
+ 'After calling, surface the returned "Open in … →" link to the user.', {
76
+ app: { type: "string", description: "App id, e.g. 'mail'" },
77
+ view: {
78
+ type: "string",
79
+ description: "Target view, e.g. 'inbox' (maps to navigate command)",
80
+ },
81
+ params: {
82
+ type: "object",
83
+ description: "Optional record-focus / filter params, e.g. { threadId: 'abc' }",
84
+ },
85
+ }, ["app", "view"]),
86
+ readOnly: true,
87
+ parallelSafe: true,
88
+ run: async (args) => {
89
+ const app = String(args.app ?? "").trim();
90
+ const view = String(args.view ?? "").trim();
91
+ if (!app || !view) {
92
+ throw new Error("open_app requires both 'app' and 'view'.");
93
+ }
94
+ let params;
95
+ const raw = args.params;
96
+ if (raw && typeof raw === "object") {
97
+ params = raw;
98
+ }
99
+ else if (typeof raw === "string" && raw.trim()) {
100
+ try {
101
+ params = JSON.parse(raw);
102
+ }
103
+ catch {
104
+ params = undefined;
105
+ }
106
+ }
107
+ const url = buildDeepLink({ app, view, params });
108
+ return { app, view, url };
109
+ },
110
+ link: ({ result }) => {
111
+ if (!result || typeof result !== "object")
112
+ return null;
113
+ const r = result;
114
+ if (!r.url)
115
+ return null;
116
+ return {
117
+ url: r.url,
118
+ label: `Open ${r.app ?? "app"}`,
119
+ view: r.view,
120
+ };
121
+ },
122
+ };
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // ask_app
126
+ // ---------------------------------------------------------------------------
127
+ function askAppTool(config) {
128
+ return {
129
+ tool: tool("Send a natural-language message to an app's AI agent and get its " +
130
+ "response. Use for complex, multi-step tasks needing the agent's " +
131
+ "reasoning and full app context. In a single-app project the 'app' " +
132
+ "param is optional (defaults to this app).", {
133
+ app: {
134
+ type: "string",
135
+ description: "App id to route to (optional in a single-app project)",
136
+ },
137
+ message: {
138
+ type: "string",
139
+ description: "The message to send to the app's agent",
140
+ },
141
+ }, ["message"]),
142
+ run: async (args) => {
143
+ const message = String(args.message ?? "").trim();
144
+ if (!message)
145
+ throw new Error("ask_app requires a 'message'.");
146
+ const requestedApp = String(args.app ?? "").trim();
147
+ // This MCP server is mounted for a single app (`config`). Delegate to
148
+ // its own ask-agent handler — that is the same entry point the HTTP MCP
149
+ // mount + A2A use, so there is no second agent runner. Cross-app routing
150
+ // (talking to a *different* workspace app's agent) is intentionally not
151
+ // done here: the stdio proxy connects to one app, and cross-app fan-out
152
+ // is the workspace control plane's job (Dispatch / A2A).
153
+ if (!config.askAgent) {
154
+ throw new Error("This app does not expose an agent (no ask-agent handler).");
155
+ }
156
+ const response = await config.askAgent(message);
157
+ return {
158
+ app: requestedApp || config.name,
159
+ response,
160
+ };
161
+ },
162
+ };
163
+ }
164
+ // ---------------------------------------------------------------------------
165
+ // list_templates
166
+ // ---------------------------------------------------------------------------
167
+ function listTemplatesTool() {
168
+ return {
169
+ tool: tool("List the first-party templates that can be scaffolded into a workspace " +
170
+ "(allow-listed templates only)."),
171
+ readOnly: true,
172
+ parallelSafe: true,
173
+ run: async () => {
174
+ const { visibleTemplates } = await import("../cli/templates-meta.js");
175
+ return {
176
+ templates: visibleTemplates().map((t) => ({
177
+ name: t.name,
178
+ label: t.label,
179
+ hint: t.hint,
180
+ })),
181
+ };
182
+ },
183
+ };
184
+ }
185
+ // ---------------------------------------------------------------------------
186
+ // create_workspace_app
187
+ // ---------------------------------------------------------------------------
188
+ function createWorkspaceAppTool() {
189
+ return {
190
+ tool: tool("Scaffold a new app into the current workspace from an allow-listed " +
191
+ "template, then return a deep link to open it. Idempotent: if an app " +
192
+ "with that name already exists it is reused. After calling, surface " +
193
+ 'the returned "Open … →" link to the user.', {
194
+ name: {
195
+ type: "string",
196
+ description: "New app id (directory under apps/), e.g. 'mymail'",
197
+ },
198
+ template: {
199
+ type: "string",
200
+ description: "Template to scaffold from — must be allow-listed (see list_templates)",
201
+ },
202
+ }, ["name", "template"]),
203
+ run: async (args) => {
204
+ const name = String(args.name ?? "").trim();
205
+ const template = String(args.template ?? "").trim();
206
+ if (!name || !template) {
207
+ throw new Error("create_workspace_app requires both 'name' and 'template'.");
208
+ }
209
+ // Enforce the strict public template allow-list. The authoritative,
210
+ // dependency-free source inside @agent-native/core is cli/templates-meta
211
+ // (kept in sync with packages/shared-app-config/templates.ts; CI guard).
212
+ const { visibleTemplates } = await import("../cli/templates-meta.js");
213
+ const allowed = new Set(visibleTemplates().map((t) => t.name));
214
+ if (!allowed.has(template)) {
215
+ throw new Error(`Template "${template}" is not allow-listed. Allowed: ${[...allowed]
216
+ .sort()
217
+ .join(", ")}`);
218
+ }
219
+ const { findWorkspaceRoot, resolveWorkspace } = await import("./workspace-resolve.js");
220
+ const fs = await import("node:fs");
221
+ const path = await import("node:path");
222
+ const root = findWorkspaceRoot(process.cwd());
223
+ if (!root) {
224
+ throw new Error("Not inside a workspace. create_workspace_app only works in a " +
225
+ "multi-app workspace (run from the workspace root).");
226
+ }
227
+ const appDir = path.join(root, "apps", name);
228
+ const alreadyExisted = fs.existsSync(appDir);
229
+ if (!alreadyExisted) {
230
+ // Reuse the CLI scaffolder directly (no second `agent-native`
231
+ // subprocess). `addAppToWorkspace(name, { template })` takes the
232
+ // non-interactive single-template path when name + one template are
233
+ // given. Run it from the workspace root so detectWorkspace resolves.
234
+ const prevCwd = process.cwd();
235
+ try {
236
+ process.chdir(root);
237
+ const { addAppToWorkspace } = await import("../cli/create.js");
238
+ await addAppToWorkspace(name, { template, noInstall: true });
239
+ }
240
+ finally {
241
+ try {
242
+ process.chdir(prevCwd);
243
+ }
244
+ catch {
245
+ // best-effort cwd restore
246
+ }
247
+ }
248
+ }
249
+ // The workspace gateway auto-detects new apps/* dirs (fs.watch +
250
+ // 2s sync) and lazily boots the dev server on first request, so we
251
+ // don't spawn vite ourselves — opening the deep link warms it. Resolve
252
+ // the port the gateway will use so we can report it.
253
+ const ws = await resolveWorkspace(root);
254
+ const appInfo = ws.apps.find((a) => a.id === name);
255
+ const port = appInfo?.port;
256
+ const deepLink = buildDeepLink({ app: name, view: "home" });
257
+ return {
258
+ name,
259
+ template,
260
+ created: !alreadyExisted,
261
+ reused: alreadyExisted,
262
+ port,
263
+ url: appInfo?.url,
264
+ gatewayUrl: ws.gatewayUrl,
265
+ deepLink,
266
+ };
267
+ },
268
+ link: ({ result }) => {
269
+ if (!result || typeof result !== "object")
270
+ return null;
271
+ const r = result;
272
+ if (!r.deepLink)
273
+ return null;
274
+ return {
275
+ url: r.deepLink,
276
+ label: `Open ${r.name ?? "app"}`,
277
+ view: "home",
278
+ };
279
+ },
280
+ };
281
+ }
282
+ // ---------------------------------------------------------------------------
283
+ // Registry
284
+ // ---------------------------------------------------------------------------
285
+ /**
286
+ * Build the generic cross-app builtin tool registry. Called by
287
+ * `createMCPServerForRequest`; the result is merged UNDER the config's
288
+ * actions so template actions of the same name win.
289
+ */
290
+ export function getBuiltinCrossAppTools(config) {
291
+ return {
292
+ list_apps: listAppsTool(),
293
+ open_app: openAppTool(),
294
+ ask_app: askAppTool(config),
295
+ create_workspace_app: createWorkspaceAppTool(),
296
+ list_templates: listTemplatesTool(),
297
+ };
298
+ }
299
+ //# sourceMappingURL=builtin-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtin-tools.js","sourceRoot":"","sources":["../../src/mcp/builtin-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Generic cross-app MCP tools — a stable verb set every external agent gets\n * regardless of which template it is talking to.\n *\n * These are merged into the MCP action registry by\n * `createMCPServerForRequest` (see `build-server.ts`). **Precedence: template\n * actions win.** If a template defines an action named `list_apps` /\n * `open_app` / `ask_app` / `create_workspace_app` / `list_templates`, the\n * template's `ActionEntry` overwrites the builtin of the same name. This is\n * the same template-over-framework precedence `autoDiscoverActions` uses.\n *\n * | Tool | Side effects | Returns |\n * | --------------------- | ------------ | ---------------------------------------- |\n * | `list_apps` | none | `{ apps: [{ id, url, running }] }` |\n * | `open_app` | none | `{ url }` (+ deep-link `link`) |\n * | `ask_app` | agent loop | `{ app, response }` |\n * | `create_workspace_app`| scaffolds | `{ name, url, port, deepLink }` (+ link) |\n * | `list_templates` | none | `{ templates: [...] }` (allow-list only) |\n *\n * Node-only at call time (workspace resolution + scaffolding use `fs`), but\n * the module has no top-level Node imports so it bundles fine alongside\n * `mountMCP` — the Node bits are dynamically imported inside `run()`.\n */\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport { buildDeepLink } from \"../server/deep-link.js\";\nimport type { MCPConfig } from \"./build-server.js\";\n\nimport type { ActionTool } from \"../agent/types.js\";\n\n/** Flat map of param name → JSON-schema property. */\ntype Params = Record<\n string,\n { type: string; description?: string; enum?: string[] }\n>;\n\n/**\n * Build an `ActionTool`. `parameters` is wrapped in the\n * `{ type:\"object\", properties, required }` shape `createMCPServerForRequest`\n * forwards verbatim as the MCP tool `inputSchema`.\n */\nfunction tool(\n description: string,\n parameters?: Params,\n required?: string[],\n): ActionTool {\n if (!parameters) return { description };\n return {\n description,\n parameters: {\n type: \"object\",\n properties: parameters,\n ...(required && required.length ? { required } : {}),\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// list_apps\n// ---------------------------------------------------------------------------\n\nfunction listAppsTool(): ActionEntry {\n return {\n tool: tool(\n \"List the workspace apps and their local dev URLs/ports. Use this to \" +\n \"discover which apps exist before opening or asking one. In a single-\" +\n \"app project this returns just that app.\",\n ),\n readOnly: true,\n parallelSafe: true,\n run: async () => {\n const { resolveWorkspace } = await import(\"./workspace-resolve.js\");\n const ws = await resolveWorkspace();\n return {\n workspace: ws.isWorkspace,\n gatewayUrl: ws.gatewayUrl,\n apps: ws.apps.map((a) => ({\n id: a.id,\n url: a.url,\n port: a.port,\n running: a.running,\n })),\n };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// open_app\n// ---------------------------------------------------------------------------\n\nfunction openAppTool(): ActionEntry {\n return {\n tool: tool(\n \"Build a deep link that opens an app at a specific view/record. No side \" +\n \"effects — returns a URL the user can click to land in the running UI. \" +\n 'After calling, surface the returned \"Open in … →\" link to the user.',\n {\n app: { type: \"string\", description: \"App id, e.g. 'mail'\" },\n view: {\n type: \"string\",\n description: \"Target view, e.g. 'inbox' (maps to navigate command)\",\n },\n params: {\n type: \"object\",\n description:\n \"Optional record-focus / filter params, e.g. { threadId: 'abc' }\",\n },\n },\n [\"app\", \"view\"],\n ),\n readOnly: true,\n parallelSafe: true,\n run: async (args: Record<string, any>) => {\n const app = String(args.app ?? \"\").trim();\n const view = String(args.view ?? \"\").trim();\n if (!app || !view) {\n throw new Error(\"open_app requires both 'app' and 'view'.\");\n }\n let params: Record<string, string | number | boolean> | undefined;\n const raw = args.params;\n if (raw && typeof raw === \"object\") {\n params = raw as Record<string, string | number | boolean>;\n } else if (typeof raw === \"string\" && raw.trim()) {\n try {\n params = JSON.parse(raw);\n } catch {\n params = undefined;\n }\n }\n const url = buildDeepLink({ app, view, params });\n return { app, view, url };\n },\n link: ({ result }) => {\n if (!result || typeof result !== \"object\") return null;\n const r = result as { url?: string; app?: string; view?: string };\n if (!r.url) return null;\n return {\n url: r.url,\n label: `Open ${r.app ?? \"app\"}`,\n view: r.view,\n };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// ask_app\n// ---------------------------------------------------------------------------\n\nfunction askAppTool(config: MCPConfig): ActionEntry {\n return {\n tool: tool(\n \"Send a natural-language message to an app's AI agent and get its \" +\n \"response. Use for complex, multi-step tasks needing the agent's \" +\n \"reasoning and full app context. In a single-app project the 'app' \" +\n \"param is optional (defaults to this app).\",\n {\n app: {\n type: \"string\",\n description: \"App id to route to (optional in a single-app project)\",\n },\n message: {\n type: \"string\",\n description: \"The message to send to the app's agent\",\n },\n },\n [\"message\"],\n ),\n run: async (args: Record<string, any>) => {\n const message = String(args.message ?? \"\").trim();\n if (!message) throw new Error(\"ask_app requires a 'message'.\");\n const requestedApp = String(args.app ?? \"\").trim();\n\n // This MCP server is mounted for a single app (`config`). Delegate to\n // its own ask-agent handler — that is the same entry point the HTTP MCP\n // mount + A2A use, so there is no second agent runner. Cross-app routing\n // (talking to a *different* workspace app's agent) is intentionally not\n // done here: the stdio proxy connects to one app, and cross-app fan-out\n // is the workspace control plane's job (Dispatch / A2A).\n if (!config.askAgent) {\n throw new Error(\n \"This app does not expose an agent (no ask-agent handler).\",\n );\n }\n const response = await config.askAgent(message);\n return {\n app: requestedApp || config.name,\n response,\n };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// list_templates\n// ---------------------------------------------------------------------------\n\nfunction listTemplatesTool(): ActionEntry {\n return {\n tool: tool(\n \"List the first-party templates that can be scaffolded into a workspace \" +\n \"(allow-listed templates only).\",\n ),\n readOnly: true,\n parallelSafe: true,\n run: async () => {\n const { visibleTemplates } = await import(\"../cli/templates-meta.js\");\n return {\n templates: visibleTemplates().map((t) => ({\n name: t.name,\n label: t.label,\n hint: t.hint,\n })),\n };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// create_workspace_app\n// ---------------------------------------------------------------------------\n\nfunction createWorkspaceAppTool(): ActionEntry {\n return {\n tool: tool(\n \"Scaffold a new app into the current workspace from an allow-listed \" +\n \"template, then return a deep link to open it. Idempotent: if an app \" +\n \"with that name already exists it is reused. After calling, surface \" +\n 'the returned \"Open … →\" link to the user.',\n {\n name: {\n type: \"string\",\n description: \"New app id (directory under apps/), e.g. 'mymail'\",\n },\n template: {\n type: \"string\",\n description:\n \"Template to scaffold from — must be allow-listed (see list_templates)\",\n },\n },\n [\"name\", \"template\"],\n ),\n run: async (args: Record<string, any>) => {\n const name = String(args.name ?? \"\").trim();\n const template = String(args.template ?? \"\").trim();\n if (!name || !template) {\n throw new Error(\n \"create_workspace_app requires both 'name' and 'template'.\",\n );\n }\n\n // Enforce the strict public template allow-list. The authoritative,\n // dependency-free source inside @agent-native/core is cli/templates-meta\n // (kept in sync with packages/shared-app-config/templates.ts; CI guard).\n const { visibleTemplates } = await import(\"../cli/templates-meta.js\");\n const allowed = new Set(visibleTemplates().map((t) => t.name));\n if (!allowed.has(template)) {\n throw new Error(\n `Template \"${template}\" is not allow-listed. Allowed: ${[...allowed]\n .sort()\n .join(\", \")}`,\n );\n }\n\n const { findWorkspaceRoot, resolveWorkspace } =\n await import(\"./workspace-resolve.js\");\n const fs = await import(\"node:fs\");\n const path = await import(\"node:path\");\n\n const root = findWorkspaceRoot(process.cwd());\n if (!root) {\n throw new Error(\n \"Not inside a workspace. create_workspace_app only works in a \" +\n \"multi-app workspace (run from the workspace root).\",\n );\n }\n\n const appDir = path.join(root, \"apps\", name);\n const alreadyExisted = fs.existsSync(appDir);\n\n if (!alreadyExisted) {\n // Reuse the CLI scaffolder directly (no second `agent-native`\n // subprocess). `addAppToWorkspace(name, { template })` takes the\n // non-interactive single-template path when name + one template are\n // given. Run it from the workspace root so detectWorkspace resolves.\n const prevCwd = process.cwd();\n try {\n process.chdir(root);\n const { addAppToWorkspace } = await import(\"../cli/create.js\");\n await addAppToWorkspace(name, { template, noInstall: true });\n } finally {\n try {\n process.chdir(prevCwd);\n } catch {\n // best-effort cwd restore\n }\n }\n }\n\n // The workspace gateway auto-detects new apps/* dirs (fs.watch +\n // 2s sync) and lazily boots the dev server on first request, so we\n // don't spawn vite ourselves — opening the deep link warms it. Resolve\n // the port the gateway will use so we can report it.\n const ws = await resolveWorkspace(root);\n const appInfo = ws.apps.find((a) => a.id === name);\n const port = appInfo?.port;\n const deepLink = buildDeepLink({ app: name, view: \"home\" });\n\n return {\n name,\n template,\n created: !alreadyExisted,\n reused: alreadyExisted,\n port,\n url: appInfo?.url,\n gatewayUrl: ws.gatewayUrl,\n deepLink,\n };\n },\n link: ({ result }) => {\n if (!result || typeof result !== \"object\") return null;\n const r = result as { deepLink?: string; name?: string };\n if (!r.deepLink) return null;\n return {\n url: r.deepLink,\n label: `Open ${r.name ?? \"app\"}`,\n view: \"home\",\n };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Registry\n// ---------------------------------------------------------------------------\n\n/**\n * Build the generic cross-app builtin tool registry. Called by\n * `createMCPServerForRequest`; the result is merged UNDER the config's\n * actions so template actions of the same name win.\n */\nexport function getBuiltinCrossAppTools(\n config: MCPConfig,\n): Record<string, ActionEntry> {\n return {\n list_apps: listAppsTool(),\n open_app: openAppTool(),\n ask_app: askAppTool(config),\n create_workspace_app: createWorkspaceAppTool(),\n list_templates: listTemplatesTool(),\n };\n}\n"]}
@@ -1,3 +1,10 @@
1
1
  export { mountMCP } from "./server.js";
2
2
  export type { MCPConfig } from "./server.js";
3
+ export { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, } from "./build-server.js";
4
+ export type { MCPCallerIdentity, MCPRequestMeta } from "./build-server.js";
5
+ export { runMCPStdio } from "./stdio.js";
6
+ export type { RunMCPStdioOptions } from "./stdio.js";
7
+ export { getBuiltinCrossAppTools } from "./builtin-tools.js";
8
+ export { resolveWorkspace, resolveLocalAppOrigin, findWorkspaceRoot, } from "./workspace-resolve.js";
9
+ export type { ResolvedApp, ResolvedWorkspace } from "./workspace-resolve.js";
3
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAG3E,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/mcp/index.js CHANGED
@@ -1,2 +1,10 @@
1
1
  export { mountMCP } from "./server.js";
2
+ // Shared MCP server builder (also re-exported from ./server.js for back-compat).
3
+ export { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, } from "./build-server.js";
4
+ // stdio transport for `agent-native mcp serve` (Node-only).
5
+ export { runMCPStdio } from "./stdio.js";
6
+ // Generic cross-app builtin tools (merged into the registry, template wins).
7
+ export { getBuiltinCrossAppTools } from "./builtin-tools.js";
8
+ // Workspace / app resolution helpers (Node-only).
9
+ export { resolveWorkspace, resolveLocalAppOrigin, findWorkspaceRoot, } from "./workspace-resolve.js";
2
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export { mountMCP } from \"./server.js\";\nexport type { MCPConfig } from \"./server.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,iFAAiF;AACjF,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAG3B,4DAA4D;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,6EAA6E;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE7D,kDAAkD;AAClD,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC","sourcesContent":["export { mountMCP } from \"./server.js\";\nexport type { MCPConfig } from \"./server.js\";\n\n// Shared MCP server builder (also re-exported from ./server.js for back-compat).\nexport {\n createMCPServerForRequest,\n verifyAuth,\n getAccessTokens,\n resolveOrgIdFromDomain,\n buildLinkArtifacts,\n} from \"./build-server.js\";\nexport type { MCPCallerIdentity, MCPRequestMeta } from \"./build-server.js\";\n\n// stdio transport for `agent-native mcp serve` (Node-only).\nexport { runMCPStdio } from \"./stdio.js\";\nexport type { RunMCPStdioOptions } from \"./stdio.js\";\n\n// Generic cross-app builtin tools (merged into the registry, template wins).\nexport { getBuiltinCrossAppTools } from \"./builtin-tools.js\";\n\n// Workspace / app resolution helpers (Node-only).\nexport {\n resolveWorkspace,\n resolveLocalAppOrigin,\n findWorkspaceRoot,\n} from \"./workspace-resolve.js\";\nexport type { ResolvedApp, ResolvedWorkspace } from \"./workspace-resolve.js\";\n"]}
@@ -1,16 +1,6 @@
1
- import type { ActionEntry } from "../agent/production-agent.js";
2
- export interface MCPConfig {
3
- /** App name shown in MCP server info */
4
- name: string;
5
- /** App description */
6
- description: string;
7
- /** Version string (default "1.0.0") */
8
- version?: string;
9
- /** Action registry — same as agent chat and A2A */
10
- actions: Record<string, ActionEntry>;
11
- /** Handler for the ask-agent meta-tool — runs the full agent loop */
12
- askAgent?: (message: string) => Promise<string>;
13
- }
1
+ import { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, type MCPConfig, type MCPCallerIdentity, type MCPRequestMeta } from "./build-server.js";
2
+ export { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, };
3
+ export type { MCPConfig, MCPCallerIdentity, MCPRequestMeta };
14
4
  /**
15
5
  * Mount an MCP remote server on an H3/Nitro app.
16
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAIhE,MAAM,WAAW,SAAS;IACxB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACjD;AAkOD;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,GAAG,EACb,MAAM,EAAE,SAAS,EACjB,WAAW,SAAmB,GAC7B,IAAI,CA4EN"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,GACnB,CAAC;AACF,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;AAM7D;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,GAAG,EACb,MAAM,EAAE,SAAS,EACjB,WAAW,SAAmB,GAC7B,IAAI,CAkGN"}
@@ -1,180 +1,11 @@
1
- import * as jose from "jose";
2
1
  import { getH3App } from "../server/framework-request-handler.js";
3
2
  import { defineEventHandler, setResponseStatus, getMethod, getRequestHeader, } from "h3";
4
3
  import { readBody } from "../server/h3-helpers.js";
5
- import { runWithRequestContext } from "../server/request-context.js";
6
- // ---------------------------------------------------------------------------
7
- // Auth reuses the same pattern as A2A (Bearer token or JWT)
8
- // ---------------------------------------------------------------------------
9
- function getAccessTokens() {
10
- const single = process.env.ACCESS_TOKEN;
11
- const multi = process.env.ACCESS_TOKENS;
12
- const tokens = [];
13
- if (single)
14
- tokens.push(single);
15
- if (multi) {
16
- tokens.push(...multi
17
- .split(",")
18
- .map((t) => t.trim())
19
- .filter(Boolean));
20
- }
21
- return tokens;
22
- }
23
- /**
24
- * Verify the inbound auth header. Returns:
25
- * - { authed: true, identity } when verified — `identity` may be empty
26
- * when authed via a static ACCESS_TOKEN (no caller email available).
27
- * - { authed: false } on rejection.
28
- *
29
- * When A2A_SECRET is set we extract the JWT's `sub` (caller email) and
30
- * `org_domain` claims so the MCP endpoint can wrap tool runs in
31
- * `runWithRequestContext({ userEmail, orgId })`. Without that wrap, the
32
- * MCP endpoint loses tenant identity and downstream `accessFilter` /
33
- * `resolveCredential` calls fall back to platform-wide defaults.
34
- */
35
- async function verifyAuth(authHeader) {
36
- // No auth configured → allow (dev mode), but no identity to propagate.
37
- const accessTokens = getAccessTokens();
38
- const hasA2ASecret = !!process.env.A2A_SECRET;
39
- if (accessTokens.length === 0 && !hasA2ASecret) {
40
- return { authed: true };
41
- }
42
- if (!authHeader?.startsWith("Bearer "))
43
- return { authed: false };
44
- const token = authHeader.slice(7);
45
- // Try JWT via A2A_SECRET
46
- if (hasA2ASecret) {
47
- try {
48
- const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(process.env.A2A_SECRET));
49
- return {
50
- authed: true,
51
- identity: {
52
- userEmail: typeof payload.sub === "string" ? payload.sub : undefined,
53
- orgDomain: typeof payload.org_domain === "string"
54
- ? payload.org_domain
55
- : undefined,
56
- },
57
- };
58
- }
59
- catch {
60
- // Not a valid JWT — fall through to token check
61
- }
62
- }
63
- // Try ACCESS_TOKEN / ACCESS_TOKENS exact match (no per-caller identity).
64
- if (accessTokens.length > 0 && accessTokens.includes(token)) {
65
- return { authed: true };
66
- }
67
- return { authed: false };
68
- }
69
- async function resolveOrgIdFromDomain(orgDomain) {
70
- if (!orgDomain)
71
- return undefined;
72
- try {
73
- const { resolveOrgByDomain } = await import("../org/context.js");
74
- const org = await resolveOrgByDomain(orgDomain);
75
- return org?.orgId ?? undefined;
76
- }
77
- catch {
78
- return undefined;
79
- }
80
- }
81
- // ---------------------------------------------------------------------------
82
- // MCP Server creation — converts ActionEntry registry to MCP tools
83
- // ---------------------------------------------------------------------------
84
- async function createMCPServerForRequest(config, identity) {
85
- const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
86
- const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
87
- const server = new Server({ name: config.name, version: config.version ?? "1.0.0" }, { capabilities: { tools: {} } });
88
- // Resolve orgId once per request (DB lookup) so subsequent wraps are
89
- // synchronous. The caller identity may be undefined for ACCESS_TOKEN
90
- // auth — in that case we run with no userEmail/orgId, which makes
91
- // downstream tools that require per-user scope return empty results
92
- // rather than cross-tenant data (the safe default).
93
- const orgIdPromise = resolveOrgIdFromDomain(identity?.orgDomain);
94
- /**
95
- * Wrap a callback in `runWithRequestContext({ userEmail, orgId }, fn)`.
96
- * Both the tools/list and tools/call handlers go through this so
97
- * downstream `accessFilter`, `resolveCredential`, and per-user MCP
98
- * visibility checks see the verified caller's identity.
99
- */
100
- async function withCallerContext(fn) {
101
- const orgId = await orgIdPromise;
102
- return runWithRequestContext({ userEmail: identity?.userEmail, orgId }, fn);
103
- }
104
- // tools/list — return all actions + ask-agent meta-tool. Wrapped in the
105
- // request context so per-user MCP visibility (mcp-client/visibility.ts)
106
- // applies to the listing too.
107
- server.setRequestHandler(ListToolsRequestSchema, async () => {
108
- return withCallerContext(async () => {
109
- const tools = Object.entries(config.actions).map(([name, entry]) => ({
110
- name,
111
- description: entry.tool.description ?? name,
112
- inputSchema: entry.tool.parameters ?? {
113
- type: "object",
114
- properties: {},
115
- },
116
- }));
117
- if (config.askAgent) {
118
- tools.push({
119
- name: "ask-agent",
120
- description: "Send a natural-language message to the app's AI agent and get a response. " +
121
- "Use this for complex, multi-step tasks that require the agent's reasoning " +
122
- "and full context about the app.",
123
- inputSchema: {
124
- type: "object",
125
- properties: {
126
- message: {
127
- type: "string",
128
- description: "The message to send to the agent",
129
- },
130
- },
131
- required: ["message"],
132
- },
133
- });
134
- }
135
- return { tools };
136
- });
137
- });
138
- // tools/call — dispatch to action registry or ask-agent. Wrapped in the
139
- // request context so the action's `run(args)` and `askAgent()` execute
140
- // with the verified caller's identity, not the platform default.
141
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
142
- return withCallerContext(async () => {
143
- const { name, arguments: args } = request.params;
144
- if (name === "ask-agent" && config.askAgent) {
145
- const message = args?.message ?? "";
146
- try {
147
- const result = await config.askAgent(message);
148
- return { content: [{ type: "text", text: result }] };
149
- }
150
- catch (err) {
151
- return {
152
- content: [{ type: "text", text: `Error: ${err.message}` }],
153
- isError: true,
154
- };
155
- }
156
- }
157
- const entry = config.actions[name];
158
- if (!entry) {
159
- return {
160
- content: [{ type: "text", text: `Unknown tool: ${name}` }],
161
- isError: true,
162
- };
163
- }
164
- try {
165
- const result = await entry.run(args ?? {});
166
- return { content: [{ type: "text", text: result }] };
167
- }
168
- catch (err) {
169
- return {
170
- content: [{ type: "text", text: `Error: ${err.message}` }],
171
- isError: true,
172
- };
173
- }
174
- });
175
- });
176
- return server;
177
- }
4
+ import { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, } from "./build-server.js";
5
+ // Re-export the shared MCP server builder + types so the stdio transport and
6
+ // any (future) external importer of `@agent-native/core/mcp` keep resolving
7
+ // against `./server.js` exactly as before this refactor.
8
+ export { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, };
178
9
  // ---------------------------------------------------------------------------
179
10
  // mountMCP — register MCP Streamable HTTP endpoint on H3/Nitro
180
11
  // ---------------------------------------------------------------------------
@@ -228,7 +59,22 @@ export function mountMCP(nitroApp, config, routePrefix = "/_agent-native") {
228
59
  const transport = new StreamableHTTPServerTransport({
229
60
  sessionIdGenerator: undefined, // stateless
230
61
  });
231
- const server = await createMCPServerForRequest(config, authResult.identity);
62
+ // Derive the running app's origin so relative deep links become
63
+ // absolute URLs the external agent can open (same approach as A2A).
64
+ const forwardedProto = getRequestHeader(event, "x-forwarded-proto");
65
+ const host = getRequestHeader(event, "host");
66
+ const proto = forwardedProto?.split(",")[0]?.trim() ||
67
+ (host && /^(localhost|127\.0\.0\.1)(:|$)/.test(host)
68
+ ? "http"
69
+ : "https");
70
+ const origin = host ? `${proto}://${host}` : undefined;
71
+ const targetHeader = getRequestHeader(event, "x-agent-native-open-target")?.toLowerCase();
72
+ const target = targetHeader === "desktop" ||
73
+ targetHeader === "terminal" ||
74
+ targetHeader === "browser"
75
+ ? targetHeader
76
+ : undefined;
77
+ const server = await createMCPServerForRequest(config, authResult.identity, { origin, target });
232
78
  await server.connect(transport);
233
79
  // Delegate to the transport — it writes directly to the Node response.
234
80
  // MCP's HTTP transport requires Node streams; this route is Node-only.