@agent-native/core 0.17.2 → 0.18.1

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 (80) 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 +17 -14
  15. package/dist/db/client.d.ts.map +1 -1
  16. package/dist/db/client.js +31 -27
  17. package/dist/db/client.js.map +1 -1
  18. package/dist/db/create-get-db.js +2 -2
  19. package/dist/db/create-get-db.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp/build-server.d.ts +152 -0
  24. package/dist/mcp/build-server.d.ts.map +1 -0
  25. package/dist/mcp/build-server.js +349 -0
  26. package/dist/mcp/build-server.js.map +1 -0
  27. package/dist/mcp/builtin-tools.d.ts +39 -0
  28. package/dist/mcp/builtin-tools.d.ts.map +1 -0
  29. package/dist/mcp/builtin-tools.js +401 -0
  30. package/dist/mcp/builtin-tools.js.map +1 -0
  31. package/dist/mcp/index.d.ts +7 -0
  32. package/dist/mcp/index.d.ts.map +1 -1
  33. package/dist/mcp/index.js +8 -0
  34. package/dist/mcp/index.js.map +1 -1
  35. package/dist/mcp/server.d.ts +3 -13
  36. package/dist/mcp/server.d.ts.map +1 -1
  37. package/dist/mcp/server.js +44 -179
  38. package/dist/mcp/server.js.map +1 -1
  39. package/dist/mcp/stdio.d.ts +44 -0
  40. package/dist/mcp/stdio.d.ts.map +1 -0
  41. package/dist/mcp/stdio.js +209 -0
  42. package/dist/mcp/stdio.js.map +1 -0
  43. package/dist/mcp/workspace-resolve.d.ts +68 -0
  44. package/dist/mcp/workspace-resolve.d.ts.map +1 -0
  45. package/dist/mcp/workspace-resolve.js +205 -0
  46. package/dist/mcp/workspace-resolve.js.map +1 -0
  47. package/dist/server/action-discovery.d.ts.map +1 -1
  48. package/dist/server/action-discovery.js +3 -0
  49. package/dist/server/action-discovery.js.map +1 -1
  50. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  51. package/dist/server/agent-chat-plugin.js +1 -0
  52. package/dist/server/agent-chat-plugin.js.map +1 -1
  53. package/dist/server/auth.d.ts +9 -0
  54. package/dist/server/auth.d.ts.map +1 -1
  55. package/dist/server/auth.js +71 -19
  56. package/dist/server/auth.js.map +1 -1
  57. package/dist/server/better-auth-instance.d.ts.map +1 -1
  58. package/dist/server/better-auth-instance.js +15 -10
  59. package/dist/server/better-auth-instance.js.map +1 -1
  60. package/dist/server/core-routes-plugin.d.ts +5 -0
  61. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  62. package/dist/server/core-routes-plugin.js +9 -0
  63. package/dist/server/core-routes-plugin.js.map +1 -1
  64. package/dist/server/deep-link.d.ts +55 -0
  65. package/dist/server/deep-link.d.ts.map +1 -0
  66. package/dist/server/deep-link.js +69 -0
  67. package/dist/server/deep-link.js.map +1 -0
  68. package/dist/server/index.d.ts +2 -0
  69. package/dist/server/index.d.ts.map +1 -1
  70. package/dist/server/index.js +2 -0
  71. package/dist/server/index.js.map +1 -1
  72. package/dist/server/open-route.d.ts +12 -0
  73. package/dist/server/open-route.d.ts.map +1 -0
  74. package/dist/server/open-route.js +159 -0
  75. package/dist/server/open-route.js.map +1 -0
  76. package/dist/server/request-context.d.ts +8 -0
  77. package/dist/server/request-context.d.ts.map +1 -1
  78. package/dist/server/request-context.js.map +1 -1
  79. package/docs/content/external-agents.md +177 -0
  80. package/package.json +1 -1
@@ -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
  // ---------------------------------------------------------------------------
@@ -200,10 +31,14 @@ export function mountMCP(nitroApp, config, routePrefix = "/_agent-native") {
200
31
  return;
201
32
  }
202
33
  const method = getMethod(event);
203
- // Auth check — also extracts the caller's identity from the JWT so
204
- // downstream tools run inside `runWithRequestContext`.
34
+ // Auth check — extracts the caller's identity from the JWT (`sub`),
35
+ // or, on the static-token / dev-open path, from the forwarded
36
+ // `X-Agent-Native-Owner-Email` hint the stdio proxy sends (the
37
+ // `agent-native mcp install` flow). Without this the install flow
38
+ // would run every tool unscoped (userEmail === undefined).
205
39
  const authHeader = getRequestHeader(event, "authorization");
206
- const authResult = await verifyAuth(authHeader);
40
+ const ownerEmailHeader = getRequestHeader(event, "x-agent-native-owner-email");
41
+ const authResult = await verifyAuth(authHeader, ownerEmailHeader);
207
42
  if (!authResult.authed) {
208
43
  setResponseStatus(event, 401);
209
44
  return { error: "Unauthorized" };
@@ -228,7 +63,22 @@ export function mountMCP(nitroApp, config, routePrefix = "/_agent-native") {
228
63
  const transport = new StreamableHTTPServerTransport({
229
64
  sessionIdGenerator: undefined, // stateless
230
65
  });
231
- const server = await createMCPServerForRequest(config, authResult.identity);
66
+ // Derive the running app's origin so relative deep links become
67
+ // absolute URLs the external agent can open (same approach as A2A).
68
+ const forwardedProto = getRequestHeader(event, "x-forwarded-proto");
69
+ const host = getRequestHeader(event, "host");
70
+ const proto = forwardedProto?.split(",")[0]?.trim() ||
71
+ (host && /^(localhost|127\.0\.0\.1)(:|$)/.test(host)
72
+ ? "http"
73
+ : "https");
74
+ const origin = host ? `${proto}://${host}` : undefined;
75
+ const targetHeader = getRequestHeader(event, "x-agent-native-open-target")?.toLowerCase();
76
+ const target = targetHeader === "desktop" ||
77
+ targetHeader === "terminal" ||
78
+ targetHeader === "browser"
79
+ ? targetHeader
80
+ : undefined;
81
+ const server = await createMCPServerForRequest(config, authResult.identity, { origin, target });
232
82
  await server.connect(transport);
233
83
  // Delegate to the transport — it writes directly to the Node response.
234
84
  // MCP's HTTP transport requires Node streams; this route is Node-only.
@@ -238,7 +88,22 @@ export function mountMCP(nitroApp, config, routePrefix = "/_agent-native") {
238
88
  setResponseStatus(event, 501);
239
89
  return { error: "MCP requires Node runtime" };
240
90
  }
241
- await transport.handleRequest(nodeReq, nodeRes, body);
91
+ try {
92
+ await transport.handleRequest(nodeReq, nodeRes, body);
93
+ }
94
+ catch (err) {
95
+ // The SDK transport writes directly to the Node response. If the
96
+ // socket is already closed/ended (client disconnected, or the host
97
+ // stream layer also flushed), Node throws ERR_STREAM_WRITE_AFTER_END
98
+ // *after* the MCP payload was already delivered correctly. Swallow
99
+ // that benign post-flush write so an external agent disconnecting
100
+ // mid-stream can never take down the server process; rethrow
101
+ // anything else.
102
+ if (err?.code !== "ERR_STREAM_WRITE_AFTER_END")
103
+ throw err;
104
+ if (process.env.DEBUG)
105
+ console.log("[mcp] ignored post-flush ERR_STREAM_WRITE_AFTER_END (client disconnected)");
106
+ }
242
107
  // Prevent H3 from double-writing the response
243
108
  event._handled = true;
244
109
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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","sourcesContent":["import * as jose from \"jose\";\nimport { getH3App } from \"../server/framework-request-handler.js\";\nimport {\n defineEventHandler,\n setResponseStatus,\n getMethod,\n getRequestHeader,\n} from \"h3\";\nimport type { ActionEntry } from \"../agent/production-agent.js\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport { runWithRequestContext } from \"../server/request-context.js\";\n\nexport interface MCPConfig {\n /** App name shown in MCP server info */\n name: string;\n /** App description */\n description: string;\n /** Version string (default \"1.0.0\") */\n version?: string;\n /** Action registry — same as agent chat and A2A */\n actions: Record<string, ActionEntry>;\n /** Handler for the ask-agent meta-tool — runs the full agent loop */\n askAgent?: (message: string) => Promise<string>;\n}\n\n/**\n * Identity extracted from a verified MCP bearer token / JWT. Used to wrap\n * `entry.run()` and `config.askAgent()` calls in `runWithRequestContext`\n * so downstream tools (db-query, accessFilter, resolveCredential) honour\n * per-user / per-org scoping. Without this wrap the MCP endpoint would\n * silently bypass tenant isolation. See finding #6 in\n * /tmp/security-audit/12-mcp-a2a-agent.md.\n */\ninterface MCPCallerIdentity {\n userEmail: string | undefined;\n orgDomain: string | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Auth — reuses the same pattern as A2A (Bearer token or JWT)\n// ---------------------------------------------------------------------------\n\nfunction getAccessTokens(): string[] {\n const single = process.env.ACCESS_TOKEN;\n const multi = process.env.ACCESS_TOKENS;\n const tokens: string[] = [];\n if (single) tokens.push(single);\n if (multi) {\n tokens.push(\n ...multi\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean),\n );\n }\n return tokens;\n}\n\n/**\n * Verify the inbound auth header. Returns:\n * - { authed: true, identity } when verified — `identity` may be empty\n * when authed via a static ACCESS_TOKEN (no caller email available).\n * - { authed: false } on rejection.\n *\n * When A2A_SECRET is set we extract the JWT's `sub` (caller email) and\n * `org_domain` claims so the MCP endpoint can wrap tool runs in\n * `runWithRequestContext({ userEmail, orgId })`. Without that wrap, the\n * MCP endpoint loses tenant identity and downstream `accessFilter` /\n * `resolveCredential` calls fall back to platform-wide defaults.\n */\nasync function verifyAuth(\n authHeader: string | undefined,\n): Promise<{ authed: boolean; identity?: MCPCallerIdentity }> {\n // No auth configured → allow (dev mode), but no identity to propagate.\n const accessTokens = getAccessTokens();\n const hasA2ASecret = !!process.env.A2A_SECRET;\n if (accessTokens.length === 0 && !hasA2ASecret) {\n return { authed: true };\n }\n\n if (!authHeader?.startsWith(\"Bearer \")) return { authed: false };\n const token = authHeader.slice(7);\n\n // Try JWT via A2A_SECRET\n if (hasA2ASecret) {\n try {\n const { payload } = await jose.jwtVerify(\n token,\n new TextEncoder().encode(process.env.A2A_SECRET!),\n );\n return {\n authed: true,\n identity: {\n userEmail: typeof payload.sub === \"string\" ? payload.sub : undefined,\n orgDomain:\n typeof payload.org_domain === \"string\"\n ? (payload.org_domain as string)\n : undefined,\n },\n };\n } catch {\n // Not a valid JWT — fall through to token check\n }\n }\n\n // Try ACCESS_TOKEN / ACCESS_TOKENS exact match (no per-caller identity).\n if (accessTokens.length > 0 && accessTokens.includes(token)) {\n return { authed: true };\n }\n\n return { authed: false };\n}\n\nasync function resolveOrgIdFromDomain(\n orgDomain: string | undefined,\n): Promise<string | undefined> {\n if (!orgDomain) return undefined;\n try {\n const { resolveOrgByDomain } = await import(\"../org/context.js\");\n const org = await resolveOrgByDomain(orgDomain);\n return org?.orgId ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n// ---------------------------------------------------------------------------\n// MCP Server creation — converts ActionEntry registry to MCP tools\n// ---------------------------------------------------------------------------\n\nasync function createMCPServerForRequest(\n config: MCPConfig,\n identity: MCPCallerIdentity | undefined,\n) {\n const { Server } = await import(\"@modelcontextprotocol/sdk/server/index.js\");\n const { ListToolsRequestSchema, CallToolRequestSchema } =\n await import(\"@modelcontextprotocol/sdk/types.js\");\n\n const server = new Server(\n { name: config.name, version: config.version ?? \"1.0.0\" },\n { capabilities: { tools: {} } },\n );\n\n // Resolve orgId once per request (DB lookup) so subsequent wraps are\n // synchronous. The caller identity may be undefined for ACCESS_TOKEN\n // auth — in that case we run with no userEmail/orgId, which makes\n // downstream tools that require per-user scope return empty results\n // rather than cross-tenant data (the safe default).\n const orgIdPromise = resolveOrgIdFromDomain(identity?.orgDomain);\n\n /**\n * Wrap a callback in `runWithRequestContext({ userEmail, orgId }, fn)`.\n * Both the tools/list and tools/call handlers go through this so\n * downstream `accessFilter`, `resolveCredential`, and per-user MCP\n * visibility checks see the verified caller's identity.\n */\n async function withCallerContext<T>(fn: () => Promise<T>): Promise<T> {\n const orgId = await orgIdPromise;\n return runWithRequestContext(\n { userEmail: identity?.userEmail, orgId },\n fn,\n ) as Promise<T>;\n }\n\n // tools/list — return all actions + ask-agent meta-tool. Wrapped in the\n // request context so per-user MCP visibility (mcp-client/visibility.ts)\n // applies to the listing too.\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return withCallerContext(async () => {\n const tools = Object.entries(config.actions).map(([name, entry]) => ({\n name,\n description: entry.tool.description ?? name,\n inputSchema: entry.tool.parameters ?? {\n type: \"object\" as const,\n properties: {},\n },\n }));\n\n if (config.askAgent) {\n tools.push({\n name: \"ask-agent\",\n description:\n \"Send a natural-language message to the app's AI agent and get a response. \" +\n \"Use this for complex, multi-step tasks that require the agent's reasoning \" +\n \"and full context about the app.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n message: {\n type: \"string\",\n description: \"The message to send to the agent\",\n },\n },\n required: [\"message\"],\n },\n });\n }\n\n return { tools };\n });\n });\n\n // tools/call — dispatch to action registry or ask-agent. Wrapped in the\n // request context so the action's `run(args)` and `askAgent()` execute\n // with the verified caller's identity, not the platform default.\n server.setRequestHandler(CallToolRequestSchema, async (request: any) => {\n return withCallerContext(async () => {\n const { name, arguments: args } = request.params;\n\n if (name === \"ask-agent\" && config.askAgent) {\n const message = args?.message ?? \"\";\n try {\n const result = await config.askAgent(message);\n return { content: [{ type: \"text\", text: result }] };\n } catch (err: any) {\n return {\n content: [{ type: \"text\", text: `Error: ${err.message}` }],\n isError: true,\n };\n }\n }\n\n const entry = config.actions[name];\n if (!entry) {\n return {\n content: [{ type: \"text\", text: `Unknown tool: ${name}` }],\n isError: true,\n };\n }\n\n try {\n const result = await entry.run((args as Record<string, string>) ?? {});\n return { content: [{ type: \"text\", text: result }] };\n } catch (err: any) {\n return {\n content: [{ type: \"text\", text: `Error: ${err.message}` }],\n isError: true,\n };\n }\n });\n });\n\n return server;\n}\n\n// ---------------------------------------------------------------------------\n// mountMCP — register MCP Streamable HTTP endpoint on H3/Nitro\n// ---------------------------------------------------------------------------\n\n/**\n * Mount an MCP remote server on an H3/Nitro app.\n *\n * Endpoint: `{routePrefix}/mcp` (default `/_agent-native/mcp`)\n *\n * Uses stateless Streamable HTTP transport — no in-memory sessions,\n * compatible with serverless deployments.\n *\n * Auth: Bearer token matching ACCESS_TOKEN/ACCESS_TOKENS or JWT via A2A_SECRET.\n * No auth required when neither is configured (dev mode).\n */\nexport function mountMCP(\n nitroApp: any,\n config: MCPConfig,\n routePrefix = \"/_agent-native\",\n): void {\n getH3App(nitroApp).use(\n `${routePrefix}/mcp`,\n defineEventHandler(async (event) => {\n const pathname = event.url?.pathname || \"/\";\n const subpath = pathname.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n if (subpath) {\n // Let management/status routes mounted under /_agent-native/mcp/*\n // handle their own requests instead of treating them as MCP protocol\n // traffic.\n return;\n }\n\n const method = getMethod(event);\n\n // Auth check — also extracts the caller's identity from the JWT so\n // downstream tools run inside `runWithRequestContext`.\n const authHeader = getRequestHeader(event, \"authorization\");\n const authResult = await verifyAuth(authHeader);\n if (!authResult.authed) {\n setResponseStatus(event, 401);\n return { error: \"Unauthorized\" };\n }\n\n // Stateless mode: only POST is meaningful\n if (method === \"DELETE\") {\n setResponseStatus(event, 204);\n return \"\";\n }\n\n if (method === \"GET\") {\n // SSE stream endpoint — not used in stateless mode but the SDK\n // handles it gracefully. Let it through for protocol compliance.\n }\n\n if (method !== \"POST\" && method !== \"GET\") {\n setResponseStatus(event, 405);\n return { error: \"Method not allowed\" };\n }\n\n // Read body for POST (GET has no body)\n const body = method === \"POST\" ? await readBody(event) : undefined;\n\n // Create per-request stateless transport + server\n const { StreamableHTTPServerTransport } =\n await import(\"@modelcontextprotocol/sdk/server/streamableHttp.js\");\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined, // stateless\n });\n const server = await createMCPServerForRequest(\n config,\n authResult.identity,\n );\n await server.connect(transport);\n\n // Delegate to the transport — it writes directly to the Node response.\n // MCP's HTTP transport requires Node streams; this route is Node-only.\n const nodeReq =\n (event as any).node?.req ?? (event as any).req?.runtime?.node?.req;\n const nodeRes =\n (event as any).node?.res ?? (event as any).req?.runtime?.node?.res;\n if (!nodeReq || !nodeRes) {\n setResponseStatus(event, 501);\n return { error: \"MCP requires Node runtime\" };\n }\n await transport.handleRequest(nodeReq, nodeRes, body);\n\n // Prevent H3 from double-writing the response\n (event as any)._handled = true;\n }),\n );\n\n if (process.env.DEBUG)\n console.log(\n `[mcp] Mounted MCP server at ${routePrefix}/mcp (${Object.keys(config.actions).length} tools${config.askAgent ? \" + ask-agent\" : \"\"})`,\n );\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { getH3App } from \"../server/framework-request-handler.js\";\nimport {\n defineEventHandler,\n setResponseStatus,\n getMethod,\n getRequestHeader,\n} from \"h3\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n createMCPServerForRequest,\n verifyAuth,\n getAccessTokens,\n resolveOrgIdFromDomain,\n buildLinkArtifacts,\n type MCPConfig,\n type MCPCallerIdentity,\n type MCPRequestMeta,\n} from \"./build-server.js\";\n\n// Re-export the shared MCP server builder + types so the stdio transport and\n// any (future) external importer of `@agent-native/core/mcp` keep resolving\n// against `./server.js` exactly as before this refactor.\nexport {\n createMCPServerForRequest,\n verifyAuth,\n getAccessTokens,\n resolveOrgIdFromDomain,\n buildLinkArtifacts,\n};\nexport type { MCPConfig, MCPCallerIdentity, MCPRequestMeta };\n\n// ---------------------------------------------------------------------------\n// mountMCP — register MCP Streamable HTTP endpoint on H3/Nitro\n// ---------------------------------------------------------------------------\n\n/**\n * Mount an MCP remote server on an H3/Nitro app.\n *\n * Endpoint: `{routePrefix}/mcp` (default `/_agent-native/mcp`)\n *\n * Uses stateless Streamable HTTP transport — no in-memory sessions,\n * compatible with serverless deployments.\n *\n * Auth: Bearer token matching ACCESS_TOKEN/ACCESS_TOKENS or JWT via A2A_SECRET.\n * No auth required when neither is configured (dev mode).\n */\nexport function mountMCP(\n nitroApp: any,\n config: MCPConfig,\n routePrefix = \"/_agent-native\",\n): void {\n getH3App(nitroApp).use(\n `${routePrefix}/mcp`,\n defineEventHandler(async (event) => {\n const pathname = event.url?.pathname || \"/\";\n const subpath = pathname.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n if (subpath) {\n // Let management/status routes mounted under /_agent-native/mcp/*\n // handle their own requests instead of treating them as MCP protocol\n // traffic.\n return;\n }\n\n const method = getMethod(event);\n\n // Auth check — extracts the caller's identity from the JWT (`sub`),\n // or, on the static-token / dev-open path, from the forwarded\n // `X-Agent-Native-Owner-Email` hint the stdio proxy sends (the\n // `agent-native mcp install` flow). Without this the install flow\n // would run every tool unscoped (userEmail === undefined).\n const authHeader = getRequestHeader(event, \"authorization\");\n const ownerEmailHeader = getRequestHeader(\n event,\n \"x-agent-native-owner-email\",\n );\n const authResult = await verifyAuth(authHeader, ownerEmailHeader);\n if (!authResult.authed) {\n setResponseStatus(event, 401);\n return { error: \"Unauthorized\" };\n }\n\n // Stateless mode: only POST is meaningful\n if (method === \"DELETE\") {\n setResponseStatus(event, 204);\n return \"\";\n }\n\n if (method === \"GET\") {\n // SSE stream endpoint — not used in stateless mode but the SDK\n // handles it gracefully. Let it through for protocol compliance.\n }\n\n if (method !== \"POST\" && method !== \"GET\") {\n setResponseStatus(event, 405);\n return { error: \"Method not allowed\" };\n }\n\n // Read body for POST (GET has no body)\n const body = method === \"POST\" ? await readBody(event) : undefined;\n\n // Create per-request stateless transport + server\n const { StreamableHTTPServerTransport } =\n await import(\"@modelcontextprotocol/sdk/server/streamableHttp.js\");\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined, // stateless\n });\n // Derive the running app's origin so relative deep links become\n // absolute URLs the external agent can open (same approach as A2A).\n const forwardedProto = getRequestHeader(event, \"x-forwarded-proto\");\n const host = getRequestHeader(event, \"host\");\n const proto =\n forwardedProto?.split(\",\")[0]?.trim() ||\n (host && /^(localhost|127\\.0\\.0\\.1)(:|$)/.test(host)\n ? \"http\"\n : \"https\");\n const origin = host ? `${proto}://${host}` : undefined;\n const targetHeader = getRequestHeader(\n event,\n \"x-agent-native-open-target\",\n )?.toLowerCase();\n const target =\n targetHeader === \"desktop\" ||\n targetHeader === \"terminal\" ||\n targetHeader === \"browser\"\n ? (targetHeader as MCPRequestMeta[\"target\"])\n : undefined;\n\n const server = await createMCPServerForRequest(\n config,\n authResult.identity,\n { origin, target },\n );\n await server.connect(transport);\n\n // Delegate to the transport — it writes directly to the Node response.\n // MCP's HTTP transport requires Node streams; this route is Node-only.\n const nodeReq =\n (event as any).node?.req ?? (event as any).req?.runtime?.node?.req;\n const nodeRes =\n (event as any).node?.res ?? (event as any).req?.runtime?.node?.res;\n if (!nodeReq || !nodeRes) {\n setResponseStatus(event, 501);\n return { error: \"MCP requires Node runtime\" };\n }\n try {\n await transport.handleRequest(nodeReq, nodeRes, body);\n } catch (err: any) {\n // The SDK transport writes directly to the Node response. If the\n // socket is already closed/ended (client disconnected, or the host\n // stream layer also flushed), Node throws ERR_STREAM_WRITE_AFTER_END\n // *after* the MCP payload was already delivered correctly. Swallow\n // that benign post-flush write so an external agent disconnecting\n // mid-stream can never take down the server process; rethrow\n // anything else.\n if (err?.code !== \"ERR_STREAM_WRITE_AFTER_END\") throw err;\n if (process.env.DEBUG)\n console.log(\n \"[mcp] ignored post-flush ERR_STREAM_WRITE_AFTER_END (client disconnected)\",\n );\n }\n\n // Prevent H3 from double-writing the response\n (event as any)._handled = true;\n }),\n );\n\n if (process.env.DEBUG)\n console.log(\n `[mcp] Mounted MCP server at ${routePrefix}/mcp (${Object.keys(config.actions).length} tools${config.askAgent ? \" + ask-agent\" : \"\"})`,\n );\n}\n"]}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * MCP **stdio** transport for the `agent-native mcp serve` command.
3
+ *
4
+ * This is the binary external coding agents (Claude Code, Claude Cowork,
5
+ * Codex) actually launch — they speak MCP over a child process's stdio, not
6
+ * HTTP. We expose the agent-native app's MCP surface over stdio in two modes:
7
+ *
8
+ * - **proxy (default)** — connect an MCP `Client` over
9
+ * `StreamableHTTPClientTransport` to the *already-running* local app's
10
+ * `http://127.0.0.1:<port>/_agent-native/mcp`, and run a stdio `Server`
11
+ * that forwards `tools/list` + `tools/call` to it. The live app is the
12
+ * single source of truth: HMR'd actions, the real registry, correct
13
+ * per-request deep links, and tenant scoping all come for free. If the
14
+ * app isn't running, we wait briefly for it (the workspace gateway boots
15
+ * it lazily on first request).
16
+ *
17
+ * - **standalone (`--standalone`)** — no running server, no HMR. Build the
18
+ * MCP server in-process from `autoDiscoverActions(cwd)` +
19
+ * `createMCPServerForRequest`, connected straight to a
20
+ * `StdioServerTransport`. Useful in CI / when nothing is serving.
21
+ *
22
+ * Node-only: imports `node:*` and the SDK stdio/http transports. Never part
23
+ * of the serverless bundle.
24
+ */
25
+ export interface RunMCPStdioOptions {
26
+ /** App id to bridge to (workspace). Optional in a single-app project. */
27
+ appId?: string;
28
+ /** Explicit port of the running app's dev server. Overrides discovery. */
29
+ port?: number;
30
+ /** Skip the HTTP proxy and build the server in-process from disk. */
31
+ standalone?: boolean;
32
+ /** Working directory (defaults to process.cwd()). */
33
+ cwd?: string;
34
+ /** Env (defaults to process.env). */
35
+ env?: NodeJS.ProcessEnv;
36
+ /** Max ms to wait for the running app before failing (proxy mode). */
37
+ waitForAppMs?: number;
38
+ }
39
+ /**
40
+ * Entry point for `agent-native mcp serve`. Defaults to proxy mode; pass
41
+ * `standalone: true` to build the server from disk with no running app.
42
+ */
43
+ export declare function runMCPStdio(opts?: RunMCPStdioOptions): Promise<void>;
44
+ //# sourceMappingURL=stdio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/mcp/stdio.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAqMD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAef"}
@@ -0,0 +1,209 @@
1
+ /**
2
+ * MCP **stdio** transport for the `agent-native mcp serve` command.
3
+ *
4
+ * This is the binary external coding agents (Claude Code, Claude Cowork,
5
+ * Codex) actually launch — they speak MCP over a child process's stdio, not
6
+ * HTTP. We expose the agent-native app's MCP surface over stdio in two modes:
7
+ *
8
+ * - **proxy (default)** — connect an MCP `Client` over
9
+ * `StreamableHTTPClientTransport` to the *already-running* local app's
10
+ * `http://127.0.0.1:<port>/_agent-native/mcp`, and run a stdio `Server`
11
+ * that forwards `tools/list` + `tools/call` to it. The live app is the
12
+ * single source of truth: HMR'd actions, the real registry, correct
13
+ * per-request deep links, and tenant scoping all come for free. If the
14
+ * app isn't running, we wait briefly for it (the workspace gateway boots
15
+ * it lazily on first request).
16
+ *
17
+ * - **standalone (`--standalone`)** — no running server, no HMR. Build the
18
+ * MCP server in-process from `autoDiscoverActions(cwd)` +
19
+ * `createMCPServerForRequest`, connected straight to a
20
+ * `StdioServerTransport`. Useful in CI / when nothing is serving.
21
+ *
22
+ * Node-only: imports `node:*` and the SDK stdio/http transports. Never part
23
+ * of the serverless bundle.
24
+ */
25
+ import { resolveLocalAppOrigin } from "./workspace-resolve.js";
26
+ const MCP_SUBPATH = "/_agent-native/mcp";
27
+ function log(msg) {
28
+ // stderr only — stdout is the MCP protocol channel and must stay clean.
29
+ process.stderr.write(`[mcp] ${msg}\n`);
30
+ }
31
+ /**
32
+ * Owner identity the installer wrote into the client config's env. Passed
33
+ * through to the HTTP MCP endpoint as a JWT/identity bearer (when present)
34
+ * so tool runs stay tenant-scoped. For local dev with a static ACCESS_TOKEN
35
+ * the email is informational; for hosted JWT auth the token already carries
36
+ * `sub`, so we only add an `X-Agent-Native-Owner-Email` hint header.
37
+ */
38
+ function authHeaders(env) {
39
+ const headers = {};
40
+ const token = env.ACCESS_TOKEN || env.AGENT_NATIVE_MCP_TOKEN;
41
+ if (token)
42
+ headers["Authorization"] = `Bearer ${token}`;
43
+ const owner = env.AGENT_NATIVE_OWNER_EMAIL;
44
+ if (owner)
45
+ headers["X-Agent-Native-Owner-Email"] = owner;
46
+ return headers;
47
+ }
48
+ async function probeOrigin(origin, timeoutMs = 800) {
49
+ try {
50
+ const res = await fetch(`${origin}${MCP_SUBPATH}`, {
51
+ method: "GET",
52
+ signal: AbortSignal.timeout(timeoutMs),
53
+ });
54
+ // Any HTTP response (even 401/405/406) means the server is up.
55
+ return res.status > 0;
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
61
+ /**
62
+ * Proxy mode: stdio Server ⇄ HTTP Client to the running app.
63
+ *
64
+ * We register the standard `tools/list` and `tools/call` handlers on the
65
+ * stdio server and forward them verbatim to the upstream HTTP MCP server via
66
+ * the SDK `Client`. The upstream owns tool definitions, results, and the
67
+ * appended deep-link block / `_meta`, so nothing is duplicated here.
68
+ */
69
+ async function runProxy(opts) {
70
+ const { origin, appId } = await resolveLocalAppOrigin({
71
+ cwd: opts.cwd,
72
+ env: opts.env,
73
+ appId: opts.appId,
74
+ port: opts.port,
75
+ });
76
+ const env = opts.env ?? process.env;
77
+ const target = `${origin}${MCP_SUBPATH}`;
78
+ // Wait for the app to come up. The workspace gateway lazily boots an app's
79
+ // dev server on first request, so a fresh `mcp serve` may briefly race the
80
+ // boot. Hit the gateway path too so the lazy start is triggered.
81
+ const deadline = Date.now() + (opts.waitForAppMs ?? 60_000);
82
+ let up = await probeOrigin(origin);
83
+ if (!up) {
84
+ log(`Waiting for ${appId} at ${origin} …`);
85
+ while (!up && Date.now() < deadline) {
86
+ await new Promise((r) => setTimeout(r, 750));
87
+ up = await probeOrigin(origin);
88
+ }
89
+ }
90
+ if (!up) {
91
+ throw new Error(`Timed out waiting for the local app at ${origin}. Start it with ` +
92
+ `\`agent-native dev\` (or \`agent-native workspace-dev\`), or run ` +
93
+ `\`agent-native mcp serve --standalone\` to build the server from disk.`);
94
+ }
95
+ const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
96
+ const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
97
+ const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
98
+ const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
99
+ const { ListToolsRequestSchema, CallToolRequestSchema } = await import("@modelcontextprotocol/sdk/types.js");
100
+ // --- Upstream HTTP client -------------------------------------------------
101
+ const clientTransport = new StreamableHTTPClientTransport(new URL(target), {
102
+ requestInit: { headers: authHeaders(env) },
103
+ });
104
+ const client = new Client({ name: "agent-native-mcp-proxy", version: "1.0.0" }, { capabilities: {} });
105
+ await client.connect(clientTransport);
106
+ log(`Proxying stdio ⇄ ${target} (app: ${appId})`);
107
+ // --- Downstream stdio server ---------------------------------------------
108
+ const server = new Server({ name: `agent-native-${appId}`, version: "1.0.0" }, { capabilities: { tools: {} } });
109
+ server.setRequestHandler(ListToolsRequestSchema, async (request) => {
110
+ return client.listTools(request.params);
111
+ });
112
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
113
+ // Forward the call verbatim; the upstream appends the deep-link block.
114
+ return client.callTool(request.params);
115
+ });
116
+ const stdioTransport = new StdioServerTransport();
117
+ await server.connect(stdioTransport);
118
+ // Keep the proxy alive until the client/transport closes.
119
+ await new Promise((resolve) => {
120
+ const done = () => resolve();
121
+ stdioTransport.onclose = done;
122
+ clientTransport.onclose = done;
123
+ process.once("SIGINT", done);
124
+ process.once("SIGTERM", done);
125
+ });
126
+ try {
127
+ await client.close();
128
+ }
129
+ catch {
130
+ // best-effort
131
+ }
132
+ }
133
+ /**
134
+ * Standalone mode: build the MCP server in-process from disk.
135
+ *
136
+ * No running server, no HMR — actions are discovered via
137
+ * `autoDiscoverActions(cwd)` and the shared `createMCPServerForRequest`
138
+ * builder is reused so behavior (tools, deep links, builtin cross-app tools)
139
+ * matches the HTTP mount exactly.
140
+ */
141
+ async function runStandalone(opts) {
142
+ const cwd = opts.cwd ?? process.cwd();
143
+ const env = opts.env ?? process.env;
144
+ const { resolveLocalAppOrigin } = await import("./workspace-resolve.js");
145
+ let appId = opts.appId ?? "app";
146
+ let origin;
147
+ try {
148
+ const resolved = await resolveLocalAppOrigin({
149
+ cwd,
150
+ env,
151
+ appId: opts.appId,
152
+ port: opts.port,
153
+ });
154
+ appId = resolved.appId;
155
+ // Origin is best-effort here (server may not be running) — still useful
156
+ // so a `link` builder's relative deep link becomes an absolute URL.
157
+ origin = resolved.origin;
158
+ }
159
+ catch {
160
+ // No workspace / can't resolve — fall back to a bare app id.
161
+ }
162
+ const { autoDiscoverActions } = await import("../server/action-discovery.js");
163
+ const { createMCPServerForRequest } = await import("./build-server.js");
164
+ const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
165
+ const actions = await autoDiscoverActions(cwd);
166
+ log(`Standalone: discovered ${Object.keys(actions).length} action(s) in ${cwd}`);
167
+ const server = await createMCPServerForRequest({
168
+ name: appId.charAt(0).toUpperCase() + appId.slice(1),
169
+ appId,
170
+ description: `Agent-native ${appId} app (standalone MCP)`,
171
+ actions,
172
+ // No askAgent in standalone — there is no running engine/runtime here.
173
+ // builtin cross-app tools stay on so `list_apps` / `open_app` /
174
+ // `create_workspace_app` / `list_templates` still work from disk.
175
+ },
176
+ // No verified identity in standalone (no inbound auth header). Runs with
177
+ // platform-default scope, same as a tokenless local HTTP mount.
178
+ undefined, { origin });
179
+ const transport = new StdioServerTransport();
180
+ await server.connect(transport);
181
+ await new Promise((resolve) => {
182
+ const done = () => resolve();
183
+ transport.onclose = done;
184
+ process.once("SIGINT", done);
185
+ process.once("SIGTERM", done);
186
+ });
187
+ }
188
+ /**
189
+ * Entry point for `agent-native mcp serve`. Defaults to proxy mode; pass
190
+ * `standalone: true` to build the server from disk with no running app.
191
+ */
192
+ export async function runMCPStdio(opts = {}) {
193
+ if (opts.standalone) {
194
+ await runStandalone(opts);
195
+ return;
196
+ }
197
+ try {
198
+ await runProxy(opts);
199
+ }
200
+ catch (err) {
201
+ // Proxy couldn't reach a running app — surface a clear, actionable
202
+ // message on stderr. We do NOT silently fall back to standalone: the
203
+ // caller asked for the live registry; auto-falling-back would hide a
204
+ // broken dev server and serve stale tools.
205
+ log(`Proxy mode failed: ${err?.message ?? err}`);
206
+ throw err;
207
+ }
208
+ }
209
+ //# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/mcp/stdio.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP **stdio** transport for the `agent-native mcp serve` command.\n *\n * This is the binary external coding agents (Claude Code, Claude Cowork,\n * Codex) actually launch — they speak MCP over a child process's stdio, not\n * HTTP. We expose the agent-native app's MCP surface over stdio in two modes:\n *\n * - **proxy (default)** — connect an MCP `Client` over\n * `StreamableHTTPClientTransport` to the *already-running* local app's\n * `http://127.0.0.1:<port>/_agent-native/mcp`, and run a stdio `Server`\n * that forwards `tools/list` + `tools/call` to it. The live app is the\n * single source of truth: HMR'd actions, the real registry, correct\n * per-request deep links, and tenant scoping all come for free. If the\n * app isn't running, we wait briefly for it (the workspace gateway boots\n * it lazily on first request).\n *\n * - **standalone (`--standalone`)** — no running server, no HMR. Build the\n * MCP server in-process from `autoDiscoverActions(cwd)` +\n * `createMCPServerForRequest`, connected straight to a\n * `StdioServerTransport`. Useful in CI / when nothing is serving.\n *\n * Node-only: imports `node:*` and the SDK stdio/http transports. Never part\n * of the serverless bundle.\n */\n\nimport { resolveLocalAppOrigin } from \"./workspace-resolve.js\";\n\nexport interface RunMCPStdioOptions {\n /** App id to bridge to (workspace). Optional in a single-app project. */\n appId?: string;\n /** Explicit port of the running app's dev server. Overrides discovery. */\n port?: number;\n /** Skip the HTTP proxy and build the server in-process from disk. */\n standalone?: boolean;\n /** Working directory (defaults to process.cwd()). */\n cwd?: string;\n /** Env (defaults to process.env). */\n env?: NodeJS.ProcessEnv;\n /** Max ms to wait for the running app before failing (proxy mode). */\n waitForAppMs?: number;\n}\n\nconst MCP_SUBPATH = \"/_agent-native/mcp\";\n\nfunction log(msg: string): void {\n // stderr only — stdout is the MCP protocol channel and must stay clean.\n process.stderr.write(`[mcp] ${msg}\\n`);\n}\n\n/**\n * Owner identity the installer wrote into the client config's env. Passed\n * through to the HTTP MCP endpoint as a JWT/identity bearer (when present)\n * so tool runs stay tenant-scoped. For local dev with a static ACCESS_TOKEN\n * the email is informational; for hosted JWT auth the token already carries\n * `sub`, so we only add an `X-Agent-Native-Owner-Email` hint header.\n */\nfunction authHeaders(env: NodeJS.ProcessEnv): Record<string, string> {\n const headers: Record<string, string> = {};\n const token = env.ACCESS_TOKEN || env.AGENT_NATIVE_MCP_TOKEN;\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n const owner = env.AGENT_NATIVE_OWNER_EMAIL;\n if (owner) headers[\"X-Agent-Native-Owner-Email\"] = owner;\n return headers;\n}\n\nasync function probeOrigin(origin: string, timeoutMs = 800): Promise<boolean> {\n try {\n const res = await fetch(`${origin}${MCP_SUBPATH}`, {\n method: \"GET\",\n signal: AbortSignal.timeout(timeoutMs),\n });\n // Any HTTP response (even 401/405/406) means the server is up.\n return res.status > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Proxy mode: stdio Server ⇄ HTTP Client to the running app.\n *\n * We register the standard `tools/list` and `tools/call` handlers on the\n * stdio server and forward them verbatim to the upstream HTTP MCP server via\n * the SDK `Client`. The upstream owns tool definitions, results, and the\n * appended deep-link block / `_meta`, so nothing is duplicated here.\n */\nasync function runProxy(opts: RunMCPStdioOptions): Promise<void> {\n const { origin, appId } = await resolveLocalAppOrigin({\n cwd: opts.cwd,\n env: opts.env,\n appId: opts.appId,\n port: opts.port,\n });\n const env = opts.env ?? process.env;\n const target = `${origin}${MCP_SUBPATH}`;\n\n // Wait for the app to come up. The workspace gateway lazily boots an app's\n // dev server on first request, so a fresh `mcp serve` may briefly race the\n // boot. Hit the gateway path too so the lazy start is triggered.\n const deadline = Date.now() + (opts.waitForAppMs ?? 60_000);\n let up = await probeOrigin(origin);\n if (!up) {\n log(`Waiting for ${appId} at ${origin} …`);\n while (!up && Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, 750));\n up = await probeOrigin(origin);\n }\n }\n if (!up) {\n throw new Error(\n `Timed out waiting for the local app at ${origin}. Start it with ` +\n `\\`agent-native dev\\` (or \\`agent-native workspace-dev\\`), or run ` +\n `\\`agent-native mcp serve --standalone\\` to build the server from disk.`,\n );\n }\n\n const { Client } = await import(\"@modelcontextprotocol/sdk/client/index.js\");\n const { StreamableHTTPClientTransport } =\n await import(\"@modelcontextprotocol/sdk/client/streamableHttp.js\");\n const { Server } = await import(\"@modelcontextprotocol/sdk/server/index.js\");\n const { StdioServerTransport } =\n await import(\"@modelcontextprotocol/sdk/server/stdio.js\");\n const { ListToolsRequestSchema, CallToolRequestSchema } =\n await import(\"@modelcontextprotocol/sdk/types.js\");\n\n // --- Upstream HTTP client -------------------------------------------------\n const clientTransport = new StreamableHTTPClientTransport(new URL(target), {\n requestInit: { headers: authHeaders(env) },\n });\n const client = new Client(\n { name: \"agent-native-mcp-proxy\", version: \"1.0.0\" },\n { capabilities: {} },\n );\n await client.connect(clientTransport);\n log(`Proxying stdio ⇄ ${target} (app: ${appId})`);\n\n // --- Downstream stdio server ---------------------------------------------\n const server = new Server(\n { name: `agent-native-${appId}`, version: \"1.0.0\" },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async (request: any) => {\n return client.listTools(request.params);\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request: any) => {\n // Forward the call verbatim; the upstream appends the deep-link block.\n return client.callTool(request.params);\n });\n\n const stdioTransport = new StdioServerTransport();\n await server.connect(stdioTransport);\n\n // Keep the proxy alive until the client/transport closes.\n await new Promise<void>((resolve) => {\n const done = () => resolve();\n stdioTransport.onclose = done;\n clientTransport.onclose = done;\n process.once(\"SIGINT\", done);\n process.once(\"SIGTERM\", done);\n });\n\n try {\n await client.close();\n } catch {\n // best-effort\n }\n}\n\n/**\n * Standalone mode: build the MCP server in-process from disk.\n *\n * No running server, no HMR — actions are discovered via\n * `autoDiscoverActions(cwd)` and the shared `createMCPServerForRequest`\n * builder is reused so behavior (tools, deep links, builtin cross-app tools)\n * matches the HTTP mount exactly.\n */\nasync function runStandalone(opts: RunMCPStdioOptions): Promise<void> {\n const cwd = opts.cwd ?? process.cwd();\n const env = opts.env ?? process.env;\n\n const { resolveLocalAppOrigin } = await import(\"./workspace-resolve.js\");\n let appId = opts.appId ?? \"app\";\n let origin: string | undefined;\n try {\n const resolved = await resolveLocalAppOrigin({\n cwd,\n env,\n appId: opts.appId,\n port: opts.port,\n });\n appId = resolved.appId;\n // Origin is best-effort here (server may not be running) — still useful\n // so a `link` builder's relative deep link becomes an absolute URL.\n origin = resolved.origin;\n } catch {\n // No workspace / can't resolve — fall back to a bare app id.\n }\n\n const { autoDiscoverActions } = await import(\"../server/action-discovery.js\");\n const { createMCPServerForRequest } = await import(\"./build-server.js\");\n const { StdioServerTransport } =\n await import(\"@modelcontextprotocol/sdk/server/stdio.js\");\n\n const actions = await autoDiscoverActions(cwd);\n log(\n `Standalone: discovered ${Object.keys(actions).length} action(s) in ${cwd}`,\n );\n\n const server = await createMCPServerForRequest(\n {\n name: appId.charAt(0).toUpperCase() + appId.slice(1),\n appId,\n description: `Agent-native ${appId} app (standalone MCP)`,\n actions,\n // No askAgent in standalone — there is no running engine/runtime here.\n // builtin cross-app tools stay on so `list_apps` / `open_app` /\n // `create_workspace_app` / `list_templates` still work from disk.\n },\n // No verified identity in standalone (no inbound auth header). Runs with\n // platform-default scope, same as a tokenless local HTTP mount.\n undefined,\n { origin },\n );\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n await new Promise<void>((resolve) => {\n const done = () => resolve();\n transport.onclose = done;\n process.once(\"SIGINT\", done);\n process.once(\"SIGTERM\", done);\n });\n}\n\n/**\n * Entry point for `agent-native mcp serve`. Defaults to proxy mode; pass\n * `standalone: true` to build the server from disk with no running app.\n */\nexport async function runMCPStdio(\n opts: RunMCPStdioOptions = {},\n): Promise<void> {\n if (opts.standalone) {\n await runStandalone(opts);\n return;\n }\n try {\n await runProxy(opts);\n } catch (err: any) {\n // Proxy couldn't reach a running app — surface a clear, actionable\n // message on stderr. We do NOT silently fall back to standalone: the\n // caller asked for the live registry; auto-falling-back would hide a\n // broken dev server and serve stale tools.\n log(`Proxy mode failed: ${err?.message ?? err}`);\n throw err;\n }\n}\n"]}