@agent-native/core 0.201.0 → 0.201.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 (53) hide show
  1. package/dist/a2a/handlers.js +96 -44
  2. package/dist/a2a/organization-identity.d.ts +9 -0
  3. package/dist/a2a/organization-identity.js +34 -0
  4. package/dist/a2a/server.d.ts +13 -7
  5. package/dist/a2a/server.js +187 -88
  6. package/dist/a2a/task-store.d.ts +7 -2
  7. package/dist/a2a/task-store.js +23 -8
  8. package/dist/a2a-claims.js +12 -2
  9. package/dist/app-config/mcp.d.ts +4 -0
  10. package/dist/app-config/mcp.js +7 -0
  11. package/dist/app-config/schema.d.ts +3 -0
  12. package/dist/app-config/schema.js +2 -0
  13. package/dist/automation/index.d.ts +1 -1
  14. package/dist/client/route-chunk-recovery.js +75 -11
  15. package/dist/client/route-warmup.d.ts +2 -0
  16. package/dist/client/route-warmup.js +3 -0
  17. package/dist/client/vite-dev-recovery-script.js +85 -17
  18. package/dist/collab/awareness.d.ts +2 -2
  19. package/dist/collab/struct-routes.d.ts +1 -1
  20. package/dist/deploy/build.js +3 -1
  21. package/dist/feature-flags/a2a-action-route.js +3 -4
  22. package/dist/mcp/build-server.d.ts +4 -0
  23. package/dist/mcp/build-server.js +152 -43
  24. package/dist/mcp/embed-app.js +5 -5
  25. package/dist/mcp/server.d.ts +0 -18
  26. package/dist/mcp/server.js +99 -9
  27. package/dist/onboarding/app-profile.js +2 -1
  28. package/dist/onboarding/workspace-services.js +3 -0
  29. package/dist/org/context.d.ts +13 -0
  30. package/dist/org/context.js +60 -0
  31. package/dist/org/handlers.d.ts +1 -1
  32. package/dist/org/index.d.ts +1 -1
  33. package/dist/org/membership.d.ts +6 -0
  34. package/dist/org/membership.js +23 -5
  35. package/dist/progress/routes.d.ts +1 -1
  36. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  37. package/dist/provider-api/actions/provider-api.d.ts +6 -6
  38. package/dist/secrets/routes.d.ts +11 -11
  39. package/dist/server/action-routes.js +12 -5
  40. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  41. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  42. package/dist/server/builder-browser.d.ts +4 -2
  43. package/dist/server/builder-browser.js +147 -9
  44. package/dist/server/core-routes-plugin.js +13 -6
  45. package/dist/server/entry-server.js +43 -2
  46. package/dist/server/sentry.js +9 -1
  47. package/dist/server/ssr-handler.d.ts +1 -1
  48. package/dist/server/ssr-handler.js +3 -7
  49. package/dist/shared/cache-control.d.ts +4 -1
  50. package/dist/shared/cache-control.js +11 -6
  51. package/dist/shared/route-chunk-recovery-bootstrap.d.ts +7 -0
  52. package/dist/shared/route-chunk-recovery-bootstrap.js +38 -0
  53. package/package.json +5 -3
@@ -1,4 +1,5 @@
1
1
  import "../authorization/check-action.js";
2
+ import { verifyA2AOrganizationIdentity } from "../a2a/organization-identity.js";
2
3
  import { actionCallEmitsChange, actionChangeResource, } from "../action-call-classification.js";
3
4
  import { MCP_APP_EXTENSION_ID, MCP_APP_MIME_TYPE, MCP_APP_RESOURCE_URI_META_KEY, } from "../action.js";
4
5
  import { isActionContractError, isActionExposedToExternalAgents, } from "../action.js";
@@ -7,6 +8,7 @@ import { getAppConfig } from "../app-config/store.js";
7
8
  import { isMcpActionResult } from "../mcp-client/app-result.js";
8
9
  import { writeActionChangeMarker } from "../server/action-change-marker-write.js";
9
10
  import { getConfiguredAppBasePath } from "../server/app-base-path.js";
11
+ import { readDeployCredentialEnv } from "../server/credential-provider.js";
10
12
  import { buildDeepLink, isAgentNativeOpenUrl, isSameOriginUrl, toAbsoluteOpenUrl, toDesktopOpenUrl, toVsCodeOpenUrl, } from "../server/deep-link.js";
11
13
  import { getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
12
14
  import { agentNativeMcpInstructions, agentNativeToolTitle, } from "../shared/agent-mcp-metadata.js";
@@ -849,7 +851,7 @@ function openAiWidgetCsp(cspConfig, requestMeta) {
849
851
  csp.frame_domains = frameDomains;
850
852
  return Object.keys(csp).length > 0 ? csp : undefined;
851
853
  }
852
- function mcpAppUiMeta(resource, resolvedCsp, requestMeta, description, widgetDomain) {
854
+ function mcpAppUiMeta(resource, resolvedCsp, requestMeta, description, widgetDomain, directoryMode = false) {
853
855
  const base = resource._meta && typeof resource._meta === "object"
854
856
  ? { ...resource._meta }
855
857
  : {};
@@ -858,13 +860,28 @@ function mcpAppUiMeta(resource, resolvedCsp, requestMeta, description, widgetDom
858
860
  : {};
859
861
  const ui = { ...existingUi };
860
862
  delete ui.domain;
863
+ if (directoryMode &&
864
+ ui.csp &&
865
+ typeof ui.csp === "object" &&
866
+ !Array.isArray(ui.csp)) {
867
+ const csp = { ...ui.csp };
868
+ delete csp.baseUriDomains;
869
+ ui.csp = csp;
870
+ }
861
871
  if (resolvedCsp) {
872
+ const csp = { ...resolvedCsp };
873
+ if (directoryMode)
874
+ delete csp.baseUriDomains;
862
875
  ui.csp = {
863
- ...resolvedCsp,
876
+ ...csp,
864
877
  connectDomains: expandRequestOriginSources(resolvedCsp.connectDomains, requestMeta),
865
878
  resourceDomains: expandRequestOriginSources(resolvedCsp.resourceDomains, requestMeta),
866
879
  frameDomains: expandRequestOriginSources(resolvedCsp.frameDomains, requestMeta),
867
- baseUriDomains: expandRequestOriginSources(resolvedCsp.baseUriDomains, requestMeta),
880
+ ...(!directoryMode
881
+ ? {
882
+ baseUriDomains: expandRequestOriginSources(resolvedCsp.baseUriDomains, requestMeta),
883
+ }
884
+ : {}),
868
885
  };
869
886
  }
870
887
  if (resource.permissions)
@@ -923,7 +940,7 @@ async function resolveMcpAppResource(config, actionName, entry, requestMeta) {
923
940
  requestOrigin: requestMeta?.origin,
924
941
  catalogMode: config.catalogMode,
925
942
  });
926
- const resourceMeta = mcpAppUiMeta(resource, resolvedCsp, requestMeta, description, config.catalogMode === "directory" ? config.widgetDomain : undefined);
943
+ const resourceMeta = mcpAppUiMeta(resource, resolvedCsp, requestMeta, description, config.catalogMode === "directory" ? config.widgetDomain : undefined, config.catalogMode === "directory");
927
944
  return {
928
945
  uri: resolvedUri.uri,
929
946
  ...(resolvedUri.legacyUris ? { legacyUris: resolvedUri.legacyUris } : {}),
@@ -1925,11 +1942,13 @@ export function getBearerToken(authHeader) {
1925
1942
  const match = /^Bearer\s+(.+)$/i.exec(authHeader.trim());
1926
1943
  return match?.[1]?.trim() || undefined;
1927
1944
  }
1928
- function addSecretCandidate(candidates, secret) {
1929
- const trimmed = secret?.trim();
1930
- if (!trimmed || candidates.includes(trimmed))
1931
- return;
1932
- candidates.push(trimmed);
1945
+ export class McpIdentityVerificationUnavailableError extends Error {
1946
+ cause;
1947
+ constructor(cause) {
1948
+ super("MCP identity verification is temporarily unavailable");
1949
+ this.name = "McpIdentityVerificationUnavailableError";
1950
+ this.cause = cause;
1951
+ }
1933
1952
  }
1934
1953
  async function verifyA2AJwtForMcp(token, resourceUrl) {
1935
1954
  const jose = await import("jose");
@@ -1940,37 +1959,96 @@ async function verifyA2AJwtForMcp(token, resourceUrl) {
1940
1959
  catch {
1941
1960
  return null;
1942
1961
  }
1943
- const candidateSecrets = [];
1944
- addSecretCandidate(candidateSecrets, process.env.A2A_SECRET);
1945
1962
  const orgDomain = typeof unverifiedPayload.org_domain === "string"
1946
- ? unverifiedPayload.org_domain
1963
+ ? unverifiedPayload.org_domain.trim().toLowerCase()
1947
1964
  : undefined;
1948
- if (orgDomain) {
1949
- try {
1950
- const { getA2ASecretByDomain } = await import("../org/context.js");
1951
- addSecretCandidate(candidateSecrets, await getA2ASecretByDomain(orgDomain));
1952
- }
1953
- catch {
1954
- // DB not ready or org lookup unavailable — fall back to other candidates.
1955
- }
1956
- }
1957
1965
  const firstPartyMcp = unverifiedPayload.agent_native_first_party_mcp === true;
1958
1966
  const audiences = firstPartyMcp ? mcpAudienceList(resourceUrl) : null;
1959
1967
  if (firstPartyMcp && !audiences?.length)
1960
1968
  return null;
1961
- for (const secret of candidateSecrets) {
1962
- const encodedSecret = new TextEncoder().encode(secret);
1969
+ const verifyWithSecret = async (secret) => {
1963
1970
  for (const audience of audiences ?? [undefined]) {
1964
1971
  try {
1965
- const { payload } = await jose.jwtVerify(token, encodedSecret, audience ? { audience } : undefined);
1972
+ const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(secret), audience ? { audience } : undefined);
1966
1973
  return payload;
1967
1974
  }
1968
1975
  catch {
1969
- // Try the next candidate without exposing which secret matched.
1976
+ // coercion-ok: bad signature or audience rejects this candidate; credential lookup failures throw separately.
1977
+ }
1978
+ }
1979
+ return null;
1980
+ };
1981
+ const globalSecret = readDeployCredentialEnv("A2A_SECRET")?.trim();
1982
+ if (globalSecret) {
1983
+ const payload = await verifyWithSecret(globalSecret);
1984
+ if (payload) {
1985
+ const tokenScope = typeof payload.scope === "string" ? payload.scope : undefined;
1986
+ const firstPartyMcp = payload.agent_native_first_party_mcp === true;
1987
+ const hasOrganizationClaim = Object.prototype.hasOwnProperty.call(payload, "org_id") &&
1988
+ payload.org_id !== null;
1989
+ const locallyIssuedConnectToken = tokenScope === MCP_CONNECT_SCOPE && !firstPartyMcp;
1990
+ const unclaimedFirstPartyToken = firstPartyMcp && !hasOrganizationClaim;
1991
+ if (locallyIssuedConnectToken || unclaimedFirstPartyToken) {
1992
+ return payload;
1993
+ }
1994
+ let verifiedOrganization;
1995
+ try {
1996
+ verifiedOrganization = await verifyA2AOrganizationIdentity(payload);
1997
+ }
1998
+ catch (error) {
1999
+ throw new McpIdentityVerificationUnavailableError(error);
1970
2000
  }
2001
+ if (verifiedOrganization === null)
2002
+ return null;
2003
+ return verifiedOrganization
2004
+ ? {
2005
+ ...payload,
2006
+ org_id: verifiedOrganization.orgId,
2007
+ org_domain: verifiedOrganization.orgDomain,
2008
+ }
2009
+ : payload;
1971
2010
  }
1972
2011
  }
1973
- return null;
2012
+ if (!orgDomain)
2013
+ return null;
2014
+ let organization;
2015
+ try {
2016
+ const { resolveA2AOrganizationCredentialsByDomain } = await import("../org/context.js");
2017
+ organization = await resolveA2AOrganizationCredentialsByDomain(orgDomain);
2018
+ }
2019
+ catch (error) {
2020
+ throw new McpIdentityVerificationUnavailableError(error);
2021
+ }
2022
+ if (!organization)
2023
+ return null;
2024
+ const payload = await verifyWithSecret(organization.secret);
2025
+ if (!payload)
2026
+ return null;
2027
+ const verifiedDomain = typeof payload.org_domain === "string"
2028
+ ? payload.org_domain.trim().toLowerCase()
2029
+ : "";
2030
+ const email = typeof payload.sub === "string" ? payload.sub.trim() : "";
2031
+ const claimedOrgId = payload.org_id;
2032
+ if (!email ||
2033
+ verifiedDomain !== organization.orgDomain ||
2034
+ (typeof claimedOrgId !== "undefined" &&
2035
+ (typeof claimedOrgId !== "string" ||
2036
+ claimedOrgId.trim() !== organization.orgId))) {
2037
+ return null;
2038
+ }
2039
+ try {
2040
+ const { isOrgMemberForA2A } = await import("../org/membership.js");
2041
+ if (!(await isOrgMemberForA2A(organization.orgId, email)))
2042
+ return null;
2043
+ }
2044
+ catch (error) {
2045
+ throw new McpIdentityVerificationUnavailableError(error);
2046
+ }
2047
+ return {
2048
+ ...payload,
2049
+ org_id: organization.orgId,
2050
+ org_domain: organization.orgDomain,
2051
+ };
1974
2052
  }
1975
2053
  function mcpAudienceList(resource) {
1976
2054
  const raw = Array.isArray(resource) ? resource : resource ? [resource] : [];
@@ -2043,13 +2121,24 @@ function orgIdFromConnectTokenResolution(resolution) {
2043
2121
  if (resolution.status === "claimed" || resolution.status === "found") {
2044
2122
  return resolution.orgId;
2045
2123
  }
2046
- // A connect token with no row here was not issued for any org this app
2047
- // knows. Its `org_domain` claim must not grant that domain's org, so it
2048
- // runs Personal.
2124
+ // A first-party cross-app token with no row here was not issued for any org
2125
+ // this app knows. Its `org_domain` claim must not grant that domain's org.
2049
2126
  if (resolution.status === "missing")
2050
2127
  return null;
2051
2128
  return undefined;
2052
2129
  }
2130
+ function matchesStoredConnectTokenIdentity(resolution, input) {
2131
+ const stored = resolution.status === "found"
2132
+ ? resolution
2133
+ : resolution.status === "claimed"
2134
+ ? resolution.storedConnectToken
2135
+ : undefined;
2136
+ if (!stored || !input.ownerEmail?.trim())
2137
+ return false;
2138
+ return (stored.ownerEmail.trim().toLowerCase() ===
2139
+ input.ownerEmail.trim().toLowerCase() &&
2140
+ (input.orgId === undefined || stored.orgId === input.orgId));
2141
+ }
2053
2142
  /**
2054
2143
  * Credentials this app issues (MCP OAuth access tokens and connect tokens)
2055
2144
  * carry the organization chosen when they were issued: the signed `org_id`
@@ -2061,9 +2150,10 @@ function orgIdFromConnectTokenResolution(resolution) {
2061
2150
  * They also carry the subject's address, which an email change retires; a
2062
2151
  * credential signed for it before the change is refused, Personal or not.
2063
2152
  *
2064
- * Cross-app A2A JWTs, first-party MCP tokens included, are not checked: their
2065
- * `org_id`, like `org_domain`, is the signing app's assertion, and the caller
2066
- * may have no membership row in this app's database.
2153
+ * Shared-secret A2A org claims must resolve against local metadata before they
2154
+ * become request scope. A sibling app's user roster is not this app's roster;
2155
+ * locally issued connect credentials instead use their stored JTI owner and
2156
+ * organization.
2067
2157
  */
2068
2158
  async function admitIssuedCredential(result, requestOrigin, orgResolution, issuedAt) {
2069
2159
  const { checkCredentialEmailRetirement, checkCredentialOrgMembership } = await import("./credential-membership.js");
@@ -2124,7 +2214,7 @@ async function admitIssuedCredential(result, requestOrigin, orgResolution, issue
2124
2214
  */
2125
2215
  export async function verifyAuth(authHeader, ownerEmailHeader, options = {}) {
2126
2216
  const accessTokens = getAccessTokens();
2127
- const hasA2ASecret = !!process.env.A2A_SECRET?.trim();
2217
+ const hasA2ASecret = !!readDeployCredentialEnv("A2A_SECRET")?.trim();
2128
2218
  const token = getBearerToken(authHeader);
2129
2219
  if (token) {
2130
2220
  const oauthIdentity = await verifyMcpOAuthAccessToken(token, options.resourceUrl);
@@ -2140,6 +2230,13 @@ export async function verifyAuth(authHeader, ownerEmailHeader, options = {}) {
2140
2230
  if (orgResolution.status === "unavailable") {
2141
2231
  return { authed: false, unavailable: true };
2142
2232
  }
2233
+ if (oauthIdentity.clientId === MCP_CONNECT_OAUTH_CLIENT_ID &&
2234
+ !matchesStoredConnectTokenIdentity(orgResolution, {
2235
+ ownerEmail: oauthIdentity.userEmail,
2236
+ orgId: oauthIdentity.orgId,
2237
+ })) {
2238
+ return { authed: false };
2239
+ }
2143
2240
  const orgId = orgIdFromConnectTokenResolution(orgResolution);
2144
2241
  const admitted = await admitIssuedCredential({
2145
2242
  authed: true,
@@ -2172,7 +2269,16 @@ export async function verifyAuth(authHeader, ownerEmailHeader, options = {}) {
2172
2269
  }
2173
2270
  if (!token)
2174
2271
  return { authed: false };
2175
- const payload = await verifyA2AJwtForMcp(token, options.resourceUrl);
2272
+ let payload;
2273
+ try {
2274
+ payload = await verifyA2AJwtForMcp(token, options.resourceUrl);
2275
+ }
2276
+ catch (error) {
2277
+ if (error instanceof McpIdentityVerificationUnavailableError) {
2278
+ return { authed: false, unavailable: true };
2279
+ }
2280
+ throw error;
2281
+ }
2176
2282
  if (payload) {
2177
2283
  const tokenScope = typeof payload.scope === "string" ? payload.scope : undefined;
2178
2284
  if (tokenScope && tokenScope !== MCP_CONNECT_SCOPE) {
@@ -2198,6 +2304,14 @@ export async function verifyAuth(authHeader, ownerEmailHeader, options = {}) {
2198
2304
  if (orgResolution.status === "unavailable") {
2199
2305
  return { authed: false, unavailable: true };
2200
2306
  }
2307
+ if (tokenScope === MCP_CONNECT_SCOPE && !firstPartyMcp) {
2308
+ if (!matchesStoredConnectTokenIdentity(orgResolution, {
2309
+ ownerEmail: typeof payload.sub === "string" ? payload.sub : undefined,
2310
+ orgId: orgIdClaim.orgId,
2311
+ })) {
2312
+ return { authed: false };
2313
+ }
2314
+ }
2201
2315
  const orgId = orgIdFromConnectTokenResolution(orgResolution);
2202
2316
  const verified = {
2203
2317
  authed: true,
@@ -2222,15 +2336,10 @@ export async function verifyAuth(authHeader, ownerEmailHeader, options = {}) {
2222
2336
  }
2223
2337
  return admitted;
2224
2338
  }
2225
- if (accessTokens.length === 0 && !hasA2ASecret) {
2226
- if (options.allowDevOpen === false) {
2227
- return { authed: false };
2228
- }
2229
- return {
2230
- authed: true,
2231
- identity: deriveStaticTokenIdentity(ownerEmailHeader),
2232
- fullSurface: !!(ownerEmailHeader && ownerEmailHeader.trim()),
2233
- };
2339
+ if (accessTokens.length === 0) {
2340
+ // A supplied bearer that failed JWT verification must not fall through to
2341
+ // dev-open auth or reuse a forwarded owner-email hint.
2342
+ return { authed: false };
2234
2343
  }
2235
2344
  // Try ACCESS_TOKEN / ACCESS_TOKENS exact match. Static tokens carry no
2236
2345
  // per-caller claims, so derive identity from the forwarded owner-email
@@ -98,7 +98,6 @@ export function embedApp(options = {}) {
98
98
  const nativeBridgeInitializeTimeoutMs = 5000;
99
99
  const nativeBridgeRequestTimeoutMs = 30000;
100
100
  const wrapperRequestTimeoutMs = 5000;
101
- const remoteBridgeFallbackEnabled = ${remoteBridgeFallbackEnabled};
102
101
  let app = null;
103
102
  let appConnectPromise = null;
104
103
  let openAiBridge = null;
@@ -2144,7 +2143,8 @@ export function embedApp(options = {}) {
2144
2143
  sendHostContext();
2145
2144
  }
2146
2145
 
2147
- async function startMcpAppsBridge() {
2146
+ ${remoteBridgeFallbackEnabled
2147
+ ? `async function startMcpAppsBridge() {
2148
2148
  const { App } = await import("${MCP_APP_IMPORT}");
2149
2149
  app = new App(
2150
2150
  { name: "Agent-Native Embed", version: "1.0.0" },
@@ -2174,7 +2174,8 @@ export function embedApp(options = {}) {
2174
2174
  updateDisplayButton();
2175
2175
  notifyHostHeight();
2176
2176
  sendHostContext();
2177
- }
2177
+ }`
2178
+ : ""}
2178
2179
 
2179
2180
  try {
2180
2181
  const initialOpenAiBridge = await waitForOpenAiBridge();
@@ -2183,8 +2184,7 @@ export function embedApp(options = {}) {
2183
2184
  await startNativeMcpAppsBridge();
2184
2185
  } catch (nativeErr) {
2185
2186
  console.warn("[agent-native] native MCP Apps bridge failed", nativeErr);
2186
- if (!remoteBridgeFallbackEnabled) throw nativeErr;
2187
- await startMcpAppsBridge();
2187
+ ${remoteBridgeFallbackEnabled ? "await startMcpAppsBridge();" : "throw nativeErr;"}
2188
2188
  }
2189
2189
  }
2190
2190
  } catch (err) {
@@ -2,24 +2,6 @@ import type { H3Event } from "h3";
2
2
  import { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, type MCPConfig, type MCPCallerIdentity, type MCPRequestMeta } from "./build-server.js";
3
3
  export { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, };
4
4
  export type { MCPConfig, MCPCallerIdentity, MCPRequestMeta };
5
- /**
6
- * Handle a single `{routePrefix}/mcp` request on either runtime.
7
- *
8
- * Builds one request-scoped MCP `Server` from the verified caller identity and
9
- * drives it through the SDK's v2 `createMcpHandler`. That entry serves native
10
- * 2026-07-28 envelopes and stateless 2025-era traffic from the same factory,
11
- * so protocol generations cannot drift apart.
12
- *
13
- * The handler is Web Standard on every runtime. H3 owns the Node response when
14
- * one exists, avoiding the double-write race from a transport writing directly
15
- * to `node.res`.
16
- *
17
- * Returns:
18
- * - `undefined` when the request targets a sub-route (so management/status
19
- * routes mounted under `/_agent-native/mcp/*` handle it themselves) — the
20
- * h3 mount falls through to the next handler.
21
- * - a Web `Response` or an auth-error object otherwise.
22
- */
23
5
  export declare function handleMcpRequest(event: H3Event, config: MCPConfig, routePath?: string): Promise<Response | string | {
24
6
  error: string;
25
7
  } | Record<string, unknown> | undefined>;
@@ -7,7 +7,7 @@ import { getH3App } from "../server/framework-request-handler.js";
7
7
  import { getOrigin } from "../server/google-oauth.js";
8
8
  import { readBody } from "../server/h3-helpers.js";
9
9
  import { trackMcpInitialize } from "./analytics.js";
10
- import { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, McpDirectoryProfileValidationError, validateMcpDirectoryProfile, validateMcpDirectoryWidgetDomain, selectMcpActionSurface, } from "./build-server.js";
10
+ import { createMCPServerForRequest, verifyAuth, McpIdentityVerificationUnavailableError, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, McpDirectoryProfileValidationError, validateMcpDirectoryProfile, validateMcpDirectoryWidgetDomain, selectMcpActionSurface, } from "./build-server.js";
11
11
  import { buildMcpOAuthChallenge, getMcpOAuthAudiences, getMcpOAuthIssuer, getMcpOAuthProtectedResourceMetadataUrl, getMcpOAuthResource, } from "./oauth-route.js";
12
12
  import { MCP_DIRECTORY_ROUTE_PREFIX, MCP_PUBLIC_ROUTE_PREFIX, MCP_ROUTE_PREFIXES, joinMcpRoute, } from "./route-paths.js";
13
13
  export { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFromDomain, buildLinkArtifacts, };
@@ -121,6 +121,51 @@ function buildUnauthorizedBody(event, routePath = MCP_PUBLIC_ROUTE_PREFIX) {
121
121
  };
122
122
  }
123
123
  const loggedDirectoryProfileFailures = new Set();
124
+ const directoryLogMethods = new Set([
125
+ "initialize",
126
+ "notifications/initialized",
127
+ "server/discover",
128
+ "tools/list",
129
+ "tools/call",
130
+ "resources/list",
131
+ "resources/templates/list",
132
+ "resources/read",
133
+ "prompts/list",
134
+ "prompts/get",
135
+ "ping",
136
+ ]);
137
+ function directoryLogMethod(body) {
138
+ if (!body || typeof body !== "object")
139
+ return undefined;
140
+ if (Array.isArray(body))
141
+ return "batch";
142
+ const method = body.method;
143
+ if (typeof method !== "string")
144
+ return "other";
145
+ return directoryLogMethods.has(method) ? method : "other";
146
+ }
147
+ function responseStatusFromEvent(event) {
148
+ const status = event.res?.status ??
149
+ event.node?.res?.statusCode ??
150
+ event._status;
151
+ return typeof status === "number" && Number.isInteger(status) && status > 0
152
+ ? status
153
+ : 200;
154
+ }
155
+ function responseStatusFromError(error) {
156
+ if (!error || typeof error !== "object")
157
+ return undefined;
158
+ const status = error;
159
+ for (const candidate of [status.status, status.statusCode]) {
160
+ if (typeof candidate === "number" &&
161
+ Number.isInteger(candidate) &&
162
+ candidate >= 100 &&
163
+ candidate <= 599) {
164
+ return candidate;
165
+ }
166
+ }
167
+ return undefined;
168
+ }
124
169
  function directoryProfileUnavailable(event, validationError) {
125
170
  const failureKey = `${validationError.code}\0${validationError.message}`;
126
171
  if (!loggedDirectoryProfileFailures.has(failureKey)) {
@@ -155,7 +200,7 @@ function directoryProfileUnavailable(event, validationError) {
155
200
  * h3 mount falls through to the next handler.
156
201
  * - a Web `Response` or an auth-error object otherwise.
157
202
  */
158
- export async function handleMcpRequest(event, config, routePath = MCP_PUBLIC_ROUTE_PREFIX) {
203
+ async function handleMcpRequestInternal(event, config, routePath = MCP_PUBLIC_ROUTE_PREFIX, onMcpMethod) {
159
204
  const pathname = event.url?.pathname || "/";
160
205
  const subpath = pathname.replace(/^\/+/, "").replace(/\/+$/, "");
161
206
  if (subpath) {
@@ -183,13 +228,26 @@ export async function handleMcpRequest(event, config, routePath = MCP_PUBLIC_ROU
183
228
  widgetDomain: requestMeta.origin,
184
229
  }
185
230
  : config;
186
- const authResult = await verifyAuth(authHeader, ownerEmailHeader, {
187
- allowDevOpen: isLoopbackRequest(event) &&
188
- isLoopbackOrigin(requestMeta.origin) &&
189
- (hasLocalOwnerHint || process.env.AGENT_NATIVE_MCP_DEV_OPEN === "1"),
190
- resourceUrl: getMcpOAuthAudiences(event, routePath),
191
- requestOrigin: getOrigin(event),
192
- });
231
+ let authResult;
232
+ try {
233
+ authResult = await verifyAuth(authHeader, ownerEmailHeader, {
234
+ allowDevOpen: isLoopbackRequest(event) &&
235
+ isLoopbackOrigin(requestMeta.origin) &&
236
+ (hasLocalOwnerHint || getAppConfig().mcp.allowDevOpen),
237
+ resourceUrl: getMcpOAuthAudiences(event, routePath),
238
+ requestOrigin: getOrigin(event),
239
+ });
240
+ }
241
+ catch (error) {
242
+ if (!(error instanceof McpIdentityVerificationUnavailableError))
243
+ throw error;
244
+ setResponseStatus(event, 503);
245
+ setResponseHeader(event, "Retry-After", "5");
246
+ return {
247
+ error: "Service Unavailable",
248
+ message: CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE,
249
+ };
250
+ }
193
251
  if (!authResult.authed && authResult.unavailable) {
194
252
  // The token is valid but its org membership could not be checked. No auth
195
253
  // challenge: re-authenticating would not help, and the client must keep
@@ -207,6 +265,7 @@ export async function handleMcpRequest(event, config, routePath = MCP_PUBLIC_ROU
207
265
  return buildUnauthorizedBody(event, routePath);
208
266
  }
209
267
  const body = method === "POST" ? await readBody(event) : undefined;
268
+ onMcpMethod?.(directoryLogMethod(body) ?? method);
210
269
  const initializeRequest = body
211
270
  ? (Array.isArray(body) ? body : [body]).find((m) => typeof m === "object" &&
212
271
  m !== null &&
@@ -266,6 +325,37 @@ export async function handleMcpRequest(event, config, routePath = MCP_PUBLIC_ROU
266
325
  const webRequest = buildWebRequest(event, method, routePath);
267
326
  return handler.fetch(webRequest, method === "POST" ? { parsedBody: body } : undefined);
268
327
  }
328
+ export async function handleMcpRequest(event, config, routePath = MCP_PUBLIC_ROUTE_PREFIX) {
329
+ const pathname = event.url?.pathname || "/";
330
+ const subpath = pathname.replace(/^\/+/, "").replace(/\/+$/, "");
331
+ const isDirectoryRequest = routePath === MCP_DIRECTORY_ROUTE_PREFIX && !subpath;
332
+ const startedAt = performance.now();
333
+ let method = getMethod(event);
334
+ let status = 500;
335
+ try {
336
+ const result = await handleMcpRequestInternal(event, config, routePath, (requestMethod) => {
337
+ method = requestMethod;
338
+ });
339
+ status =
340
+ result instanceof Response
341
+ ? result.status
342
+ : responseStatusFromEvent(event);
343
+ return result;
344
+ }
345
+ catch (error) {
346
+ status = responseStatusFromError(error) ?? 500;
347
+ throw error;
348
+ }
349
+ finally {
350
+ if (isDirectoryRequest) {
351
+ console.info("[mcp:directory] request", {
352
+ method,
353
+ status,
354
+ durationMs: Math.max(0, Math.round(performance.now() - startedAt)),
355
+ });
356
+ }
357
+ }
358
+ }
269
359
  export function mountMCP(nitroApp, config, routePrefix = "/_agent-native") {
270
360
  const routePaths = routePrefix === "/_agent-native"
271
361
  ? [...MCP_ROUTE_PREFIXES]
@@ -7,7 +7,8 @@ const SYSTEM_ONE_CAPABILITY = {
7
7
  suggested: true,
8
8
  builderIncluded: true,
9
9
  keySummary: "Jev decision model key",
10
- why: "Jev is an optional decision model that helps choose relevant tools and skills before the agent's first model request. Use Builder-managed access when available, or add a direct JEV_API_KEY.",
10
+ whyKey: "agentChat.onboarding.capability.systemOne.why",
11
+ why: "Jev is an optional decision model that helps choose relevant tools and skills before the agent's first model request.",
11
12
  };
12
13
  const PROFILES = {
13
14
  analytics: {
@@ -15,6 +15,7 @@ export const LLM_CAPABILITY = {
15
15
  keySummary: "Connect your own AI model",
16
16
  labelKey: "agentChat.settingsInfra.aiModel",
17
17
  keySummaryKey: "agentChat.onboarding.capability.llm.keySummary",
18
+ whyKey: "agentChat.onboarding.capability.llm.why",
18
19
  why: "The agent uses a language model to understand requests and produce answers.",
19
20
  };
20
21
  export const FILE_UPLOAD_STORAGE_CAPABILITY = {
@@ -26,6 +27,7 @@ export const FILE_UPLOAD_STORAGE_CAPABILITY = {
26
27
  keySummary: "File uploads and storage",
27
28
  labelKey: "agentChat.settingsShell.search.fileUploads",
28
29
  keySummaryKey: "agentChat.onboarding.capability.fileStorage.keySummary",
30
+ whyKey: "agentChat.onboarding.capability.fileStorage.why",
29
31
  why: "Uploaded images and files need durable object storage so the agent can reuse them throughout a thread.",
30
32
  };
31
33
  export const VOICE_INPUT_CAPABILITY = {
@@ -70,6 +72,7 @@ export const DESIGN_SYSTEM_INTELLIGENCE_CAPABILITY = {
70
72
  builderIncluded: true,
71
73
  keySummary: "Builder Design System Intelligence",
72
74
  labelKey: "agentChat.settingsInfra.designSystem",
75
+ whyKey: "agentChat.settingsInfra.whyDesignSystem",
73
76
  why: "Uses your brand and design-system guidance to keep generated work on brand.",
74
77
  };
75
78
  export const BACKGROUND_AGENTS_CAPABILITY = {
@@ -47,4 +47,17 @@ export declare function resolveOrgByDomain(domain: string): Promise<{
47
47
  orgId: string;
48
48
  orgName: string;
49
49
  } | null>;
50
+ export declare function resolveA2AOrganizationMetadataByDomain(domain: string): Promise<{
51
+ orgId: string;
52
+ orgDomain: string | null;
53
+ } | null>;
54
+ export declare function resolveA2AOrganizationMetadataById(orgId: string): Promise<{
55
+ orgId: string;
56
+ orgDomain: string | null;
57
+ } | null>;
58
+ export declare function resolveA2AOrganizationCredentialsByDomain(domain: string): Promise<{
59
+ orgId: string;
60
+ orgDomain: string;
61
+ secret: string;
62
+ } | null>;
50
63
  export declare function isSoleOrgDomain(domain: string): Promise<boolean>;
@@ -743,6 +743,66 @@ export async function resolveOrgByDomain(domain) {
743
743
  return null;
744
744
  }
745
745
  }
746
+ export async function resolveA2AOrganizationMetadataByDomain(domain) {
747
+ const normalizedDomain = domain.trim().toLowerCase();
748
+ if (!normalizedDomain)
749
+ return null;
750
+ const { rows } = await getDbExec().execute({
751
+ sql: `SELECT id, allowed_domain
752
+ FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 1`,
753
+ args: [normalizedDomain],
754
+ });
755
+ const row = rows[0];
756
+ if (!row)
757
+ return null;
758
+ const orgId = String(row.id ?? "").trim();
759
+ const orgDomain = String(row.allowed_domain ?? "")
760
+ .trim()
761
+ .toLowerCase();
762
+ if (!orgId || !orgDomain || orgDomain !== normalizedDomain)
763
+ return null;
764
+ return { orgId, orgDomain };
765
+ }
766
+ export async function resolveA2AOrganizationMetadataById(orgId) {
767
+ const normalizedOrgId = orgId.trim();
768
+ if (!normalizedOrgId)
769
+ return null;
770
+ const { rows } = await getDbExec().execute({
771
+ sql: `SELECT id, allowed_domain FROM organizations WHERE id = ? LIMIT 1`,
772
+ args: [normalizedOrgId],
773
+ });
774
+ const row = rows[0];
775
+ if (!row)
776
+ return null;
777
+ const resolvedOrgId = String(row.id ?? "").trim();
778
+ if (!resolvedOrgId || resolvedOrgId !== normalizedOrgId)
779
+ return null;
780
+ const rawDomain = String(row.allowed_domain ?? "")
781
+ .trim()
782
+ .toLowerCase();
783
+ return { orgId: resolvedOrgId, orgDomain: rawDomain || null };
784
+ }
785
+ export async function resolveA2AOrganizationCredentialsByDomain(domain) {
786
+ const normalizedDomain = domain.trim().toLowerCase();
787
+ if (!normalizedDomain)
788
+ return null;
789
+ const { rows } = await getDbExec().execute({
790
+ sql: `SELECT id, allowed_domain, a2a_secret
791
+ FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 1`,
792
+ args: [normalizedDomain],
793
+ });
794
+ const row = rows[0];
795
+ if (!row)
796
+ return null;
797
+ const orgId = String(row.id ?? "").trim();
798
+ const orgDomain = String(row.allowed_domain ?? "")
799
+ .trim()
800
+ .toLowerCase();
801
+ const secret = String(row.a2a_secret ?? "").trim();
802
+ if (!orgId || orgDomain !== normalizedDomain || !secret)
803
+ return null;
804
+ return { orgId, orgDomain, secret };
805
+ }
746
806
  export async function isSoleOrgDomain(domain) {
747
807
  try {
748
808
  const exec = getDbExec();
@@ -72,9 +72,9 @@ export declare const setWorkspaceAppDefaultVisibilityHandler: import("h3").Event
72
72
  }>>;
73
73
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
74
74
  success: boolean;
75
+ id?: undefined;
75
76
  name?: undefined;
76
77
  role?: undefined;
77
- id?: undefined;
78
78
  } | {
79
79
  success?: undefined;
80
80
  id: string;
@@ -179,8 +179,8 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
179
179
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
180
180
  name?: undefined;
181
181
  success: boolean;
182
- role?: undefined;
183
182
  id?: undefined;
183
+ role?: undefined;
184
184
  } | {
185
185
  success?: undefined;
186
186
  id: string;