@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
@@ -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 — 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 // 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 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"]}
@@ -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;AAoMD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAef"}
@@ -0,0 +1,208 @@
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
+ description: `Agent-native ${appId} app (standalone MCP)`,
170
+ actions,
171
+ // No askAgent in standalone — there is no running engine/runtime here.
172
+ // builtin cross-app tools stay on so `list_apps` / `open_app` /
173
+ // `create_workspace_app` / `list_templates` still work from disk.
174
+ },
175
+ // No verified identity in standalone (no inbound auth header). Runs with
176
+ // platform-default scope, same as a tokenless local HTTP mount.
177
+ undefined, { origin });
178
+ const transport = new StdioServerTransport();
179
+ await server.connect(transport);
180
+ await new Promise((resolve) => {
181
+ const done = () => resolve();
182
+ transport.onclose = done;
183
+ process.once("SIGINT", done);
184
+ process.once("SIGTERM", done);
185
+ });
186
+ }
187
+ /**
188
+ * Entry point for `agent-native mcp serve`. Defaults to proxy mode; pass
189
+ * `standalone: true` to build the server from disk with no running app.
190
+ */
191
+ export async function runMCPStdio(opts = {}) {
192
+ if (opts.standalone) {
193
+ await runStandalone(opts);
194
+ return;
195
+ }
196
+ try {
197
+ await runProxy(opts);
198
+ }
199
+ catch (err) {
200
+ // Proxy couldn't reach a running app — surface a clear, actionable
201
+ // message on stderr. We do NOT silently fall back to standalone: the
202
+ // caller asked for the live registry; auto-falling-back would hide a
203
+ // broken dev server and serve stale tools.
204
+ log(`Proxy mode failed: ${err?.message ?? err}`);
205
+ throw err;
206
+ }
207
+ }
208
+ //# 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 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"]}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Workspace / app resolution for the MCP stdio transport + builtin tools.
3
+ *
4
+ * Node-only. Never bundled into the serverless function — only the local
5
+ * `agent-native mcp` CLI path and the in-process standalone builder use it.
6
+ *
7
+ * Resolution model (mirrors `cli/workspace-dev.ts`):
8
+ *
9
+ * - Workspace root = nearest ancestor whose package.json has
10
+ * `agent-native.workspaceCore` set, with an `apps/` dir.
11
+ * - Gateway = `http://127.0.0.1:<WORKSPACE_PORT|PORT|8080>`.
12
+ * - Per-app ports = the gateway's `/_workspace/apps` JSON (authoritative,
13
+ * accounts for port reservation when 8100+ are taken). Fallback when the
14
+ * gateway isn't up yet: discover `apps/*` dirs and assign `8100 + index`
15
+ * in the same sorted order `discoverApps` uses (dispatch first).
16
+ * - Standalone (no workspace) = the single app at the cwd; dev server on
17
+ * `PORT` (default Vite 5173 / framework dev). The app id is the package
18
+ * name's last path segment.
19
+ */
20
+ export interface ResolvedApp {
21
+ id: string;
22
+ /** Local origin where this app's dev server listens, e.g. http://127.0.0.1:8100 */
23
+ url: string;
24
+ port: number;
25
+ /** True when a TCP probe to the port succeeds. */
26
+ running: boolean;
27
+ }
28
+ export interface ResolvedWorkspace {
29
+ /** Workspace root dir, or the standalone app dir. */
30
+ root: string;
31
+ /** True when `root` is a multi-app workspace (has apps/ + workspaceCore). */
32
+ isWorkspace: boolean;
33
+ /** Gateway origin (workspace) — undefined for standalone single app. */
34
+ gatewayUrl?: string;
35
+ /** Discovered apps. For standalone this is a single entry. */
36
+ apps: ResolvedApp[];
37
+ }
38
+ /** Walk up from `startDir` for a package.json with `agent-native.workspaceCore`. */
39
+ export declare function findWorkspaceRoot(startDir: string): string | null;
40
+ /**
41
+ * Resolve the workspace (or standalone app) the MCP server should bridge to.
42
+ *
43
+ * @param cwd Working directory (defaults to process.cwd()).
44
+ * @param env Env (defaults to process.env). Reads WORKSPACE_PORT / PORT.
45
+ */
46
+ export declare function resolveWorkspace(cwd?: string, env?: NodeJS.ProcessEnv): Promise<ResolvedWorkspace>;
47
+ /**
48
+ * Resolve the local app the stdio proxy should connect its MCP HTTP client
49
+ * to. Honours an explicit `--app` / appId and `--port` / explicit port.
50
+ * Returns the chosen app's origin (where `/_agent-native/mcp` is mounted).
51
+ *
52
+ * Order of precedence:
53
+ * 1. explicit `port` → http://127.0.0.1:<port>
54
+ * 2. explicit `appId` matched against resolved apps
55
+ * 3. workspace default (dispatch if present, else first app)
56
+ * 4. standalone single app
57
+ */
58
+ export declare function resolveLocalAppOrigin(opts: {
59
+ cwd?: string;
60
+ env?: NodeJS.ProcessEnv;
61
+ appId?: string;
62
+ port?: number;
63
+ }): Promise<{
64
+ origin: string;
65
+ appId: string;
66
+ ws: ResolvedWorkspace;
67
+ }>;
68
+ //# sourceMappingURL=workspace-resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-resolve.d.ts","sourceRoot":"","sources":["../../src/mcp/workspace-resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,WAAW,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,IAAI,EAAE,WAAW,EAAE,CAAC;CACrB;AAKD,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBjE;AAgFD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,GAAE,MAAsB,EAC3B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,iBAAiB,CAAC,CAkD5B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,iBAAiB,CAAA;CAAE,CAAC,CA+BpE"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Workspace / app resolution for the MCP stdio transport + builtin tools.
3
+ *
4
+ * Node-only. Never bundled into the serverless function — only the local
5
+ * `agent-native mcp` CLI path and the in-process standalone builder use it.
6
+ *
7
+ * Resolution model (mirrors `cli/workspace-dev.ts`):
8
+ *
9
+ * - Workspace root = nearest ancestor whose package.json has
10
+ * `agent-native.workspaceCore` set, with an `apps/` dir.
11
+ * - Gateway = `http://127.0.0.1:<WORKSPACE_PORT|PORT|8080>`.
12
+ * - Per-app ports = the gateway's `/_workspace/apps` JSON (authoritative,
13
+ * accounts for port reservation when 8100+ are taken). Fallback when the
14
+ * gateway isn't up yet: discover `apps/*` dirs and assign `8100 + index`
15
+ * in the same sorted order `discoverApps` uses (dispatch first).
16
+ * - Standalone (no workspace) = the single app at the cwd; dev server on
17
+ * `PORT` (default Vite 5173 / framework dev). The app id is the package
18
+ * name's last path segment.
19
+ */
20
+ import fs from "node:fs";
21
+ import path from "node:path";
22
+ const DEFAULT_GATEWAY_PORT = 8080;
23
+ const DEFAULT_APP_PORT_START = 8100;
24
+ /** Walk up from `startDir` for a package.json with `agent-native.workspaceCore`. */
25
+ export function findWorkspaceRoot(startDir) {
26
+ let dir = path.resolve(startDir);
27
+ for (let i = 0; i < 20; i++) {
28
+ const pkgPath = path.join(dir, "package.json");
29
+ if (fs.existsSync(pkgPath)) {
30
+ try {
31
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
32
+ const wsCore = pkg?.["agent-native"]?.workspaceCore;
33
+ if (typeof wsCore === "string" &&
34
+ wsCore.length > 0 &&
35
+ fs.existsSync(path.join(dir, "apps"))) {
36
+ return dir;
37
+ }
38
+ }
39
+ catch {
40
+ // ignore unparsable package.json and keep walking up
41
+ }
42
+ }
43
+ const parent = path.dirname(dir);
44
+ if (parent === dir)
45
+ break;
46
+ dir = parent;
47
+ }
48
+ return null;
49
+ }
50
+ function readJson(file) {
51
+ try {
52
+ return JSON.parse(fs.readFileSync(file, "utf8"));
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ /**
59
+ * Mirror of `cli/workspace-dev.ts`'s `compareApps` — dispatch first, then
60
+ * alphabetical. Keeps the fallback port assignment aligned with the gateway's.
61
+ */
62
+ function compareApps(a, b) {
63
+ if (a.id === "dispatch")
64
+ return -1;
65
+ if (b.id === "dispatch")
66
+ return 1;
67
+ return a.id.localeCompare(b.id);
68
+ }
69
+ function discoverAppDirs(appsDir, appPortStart) {
70
+ if (!fs.existsSync(appsDir))
71
+ return [];
72
+ let entries;
73
+ try {
74
+ entries = fs.readdirSync(appsDir, { withFileTypes: true });
75
+ }
76
+ catch {
77
+ return [];
78
+ }
79
+ return entries
80
+ .filter((e) => e.isDirectory())
81
+ .map((e) => ({ id: e.name }))
82
+ .filter((a) => fs.existsSync(path.join(appsDir, a.id, "package.json")))
83
+ .sort(compareApps)
84
+ .map((a, index) => ({ id: a.id, port: appPortStart + index }));
85
+ }
86
+ function probePort(port, timeoutMs = 600) {
87
+ return new Promise((resolve) => {
88
+ import("node:net")
89
+ .then(({ default: net }) => {
90
+ const socket = new net.Socket();
91
+ let done = false;
92
+ const finish = (ok) => {
93
+ if (done)
94
+ return;
95
+ done = true;
96
+ socket.destroy();
97
+ resolve(ok);
98
+ };
99
+ socket.setTimeout(timeoutMs);
100
+ socket.once("connect", () => finish(true));
101
+ socket.once("error", () => finish(false));
102
+ socket.once("timeout", () => finish(false));
103
+ socket.connect(port, "127.0.0.1");
104
+ })
105
+ .catch(() => resolve(false));
106
+ });
107
+ }
108
+ /** Fetch the gateway's authoritative apps list (ports may be reassigned). */
109
+ async function fetchGatewayApps(gatewayUrl) {
110
+ try {
111
+ const res = await fetch(`${gatewayUrl}/_workspace/apps`, {
112
+ signal: AbortSignal.timeout(1500),
113
+ });
114
+ if (!res.ok)
115
+ return null;
116
+ const json = (await res.json());
117
+ if (!Array.isArray(json))
118
+ return null;
119
+ return json
120
+ .filter((a) => a && typeof a.id === "string")
121
+ .map((a) => ({ id: a.id, port: Number(a.port) }));
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ }
127
+ /**
128
+ * Resolve the workspace (or standalone app) the MCP server should bridge to.
129
+ *
130
+ * @param cwd Working directory (defaults to process.cwd()).
131
+ * @param env Env (defaults to process.env). Reads WORKSPACE_PORT / PORT.
132
+ */
133
+ export async function resolveWorkspace(cwd = process.cwd(), env = process.env) {
134
+ const root = findWorkspaceRoot(cwd);
135
+ if (root) {
136
+ const gatewayPort = Number(env.WORKSPACE_PORT || env.PORT || DEFAULT_GATEWAY_PORT);
137
+ const gatewayHost = env.WORKSPACE_HOST || "127.0.0.1";
138
+ const gatewayUrl = `http://${gatewayHost}:${gatewayPort}`;
139
+ const appPortStart = Number(env.WORKSPACE_APP_PORT_START || DEFAULT_APP_PORT_START);
140
+ // Prefer the gateway's authoritative list (handles port reassignment);
141
+ // fall back to a filesystem scan with the same ordering the gateway uses.
142
+ const fromGateway = await fetchGatewayApps(gatewayUrl);
143
+ const discovered = fromGateway ?? discoverAppDirs(path.join(root, "apps"), appPortStart);
144
+ const apps = await Promise.all(discovered.map(async (a) => ({
145
+ id: a.id,
146
+ port: a.port,
147
+ url: `http://127.0.0.1:${a.port}`,
148
+ running: await probePort(a.port),
149
+ })));
150
+ return { root, isWorkspace: true, gatewayUrl, apps };
151
+ }
152
+ // Standalone single app — the cwd is the app.
153
+ const pkg = readJson(path.join(cwd, "package.json"));
154
+ const rawName = (typeof pkg?.name === "string" && pkg.name) ||
155
+ path.basename(path.resolve(cwd));
156
+ const id = rawName.replace(/^@[^/]+\//, "").replace(/^agent-native-/, "");
157
+ const port = Number(env.PORT || 5173);
158
+ return {
159
+ root: path.resolve(cwd),
160
+ isWorkspace: false,
161
+ apps: [
162
+ {
163
+ id,
164
+ port,
165
+ url: `http://127.0.0.1:${port}`,
166
+ running: await probePort(port),
167
+ },
168
+ ],
169
+ };
170
+ }
171
+ /**
172
+ * Resolve the local app the stdio proxy should connect its MCP HTTP client
173
+ * to. Honours an explicit `--app` / appId and `--port` / explicit port.
174
+ * Returns the chosen app's origin (where `/_agent-native/mcp` is mounted).
175
+ *
176
+ * Order of precedence:
177
+ * 1. explicit `port` → http://127.0.0.1:<port>
178
+ * 2. explicit `appId` matched against resolved apps
179
+ * 3. workspace default (dispatch if present, else first app)
180
+ * 4. standalone single app
181
+ */
182
+ export async function resolveLocalAppOrigin(opts) {
183
+ const ws = await resolveWorkspace(opts.cwd, opts.env);
184
+ if (opts.port) {
185
+ const match = ws.apps.find((a) => a.port === opts.port);
186
+ return {
187
+ origin: `http://127.0.0.1:${opts.port}`,
188
+ appId: match?.id ?? opts.appId ?? ws.apps[0]?.id ?? "app",
189
+ ws,
190
+ };
191
+ }
192
+ if (opts.appId) {
193
+ const match = ws.apps.find((a) => a.id === opts.appId);
194
+ if (match)
195
+ return { origin: match.url, appId: match.id, ws };
196
+ throw new Error(`App "${opts.appId}" not found. Available: ${ws.apps.map((a) => a.id).join(", ") || "(none)"}`);
197
+ }
198
+ if (ws.apps.length === 0) {
199
+ throw new Error("No apps found. Run this from a workspace root (with apps/) or a single app directory.");
200
+ }
201
+ const dispatch = ws.apps.find((a) => a.id === "dispatch");
202
+ const chosen = dispatch ?? ws.apps[0];
203
+ return { origin: chosen.url, appId: chosen.id, ws };
204
+ }
205
+ //# sourceMappingURL=workspace-resolve.js.map