@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.
- package/dist/a2a/handlers.js +96 -44
- package/dist/a2a/organization-identity.d.ts +9 -0
- package/dist/a2a/organization-identity.js +34 -0
- package/dist/a2a/server.d.ts +13 -7
- package/dist/a2a/server.js +187 -88
- package/dist/a2a/task-store.d.ts +7 -2
- package/dist/a2a/task-store.js +23 -8
- package/dist/a2a-claims.js +12 -2
- package/dist/app-config/mcp.d.ts +4 -0
- package/dist/app-config/mcp.js +7 -0
- package/dist/app-config/schema.d.ts +3 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/route-chunk-recovery.js +75 -11
- package/dist/client/route-warmup.d.ts +2 -0
- package/dist/client/route-warmup.js +3 -0
- package/dist/client/vite-dev-recovery-script.js +85 -17
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +3 -1
- package/dist/feature-flags/a2a-action-route.js +3 -4
- package/dist/mcp/build-server.d.ts +4 -0
- package/dist/mcp/build-server.js +152 -43
- package/dist/mcp/embed-app.js +5 -5
- package/dist/mcp/server.d.ts +0 -18
- package/dist/mcp/server.js +99 -9
- package/dist/onboarding/app-profile.js +2 -1
- package/dist/onboarding/workspace-services.js +3 -0
- package/dist/org/context.d.ts +13 -0
- package/dist/org/context.js +60 -0
- package/dist/org/handlers.d.ts +1 -1
- package/dist/org/index.d.ts +1 -1
- package/dist/org/membership.d.ts +6 -0
- package/dist/org/membership.js +23 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/secrets/routes.d.ts +11 -11
- package/dist/server/action-routes.js +12 -5
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/builder-browser.d.ts +4 -2
- package/dist/server/builder-browser.js +147 -9
- package/dist/server/core-routes-plugin.js +13 -6
- package/dist/server/entry-server.js +43 -2
- package/dist/server/sentry.js +9 -1
- package/dist/server/ssr-handler.d.ts +1 -1
- package/dist/server/ssr-handler.js +3 -7
- package/dist/shared/cache-control.d.ts +4 -1
- package/dist/shared/cache-control.js +11 -6
- package/dist/shared/route-chunk-recovery-bootstrap.d.ts +7 -0
- package/dist/shared/route-chunk-recovery-bootstrap.js +38 -0
- package/package.json +5 -3
package/dist/mcp/build-server.js
CHANGED
|
@@ -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
|
-
...
|
|
876
|
+
...csp,
|
|
864
877
|
connectDomains: expandRequestOriginSources(resolvedCsp.connectDomains, requestMeta),
|
|
865
878
|
resourceDomains: expandRequestOriginSources(resolvedCsp.resourceDomains, requestMeta),
|
|
866
879
|
frameDomains: expandRequestOriginSources(resolvedCsp.frameDomains, requestMeta),
|
|
867
|
-
|
|
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
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
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
|
-
|
|
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,
|
|
1972
|
+
const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(secret), audience ? { audience } : undefined);
|
|
1966
1973
|
return payload;
|
|
1967
1974
|
}
|
|
1968
1975
|
catch {
|
|
1969
|
-
//
|
|
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
|
-
|
|
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
|
|
2047
|
-
// knows. Its `org_domain` claim must not grant that domain's org
|
|
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
|
-
*
|
|
2065
|
-
*
|
|
2066
|
-
*
|
|
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 = !!
|
|
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
|
-
|
|
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
|
|
2226
|
-
|
|
2227
|
-
|
|
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
|
package/dist/mcp/embed-app.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
2187
|
-
await startMcpAppsBridge();
|
|
2187
|
+
${remoteBridgeFallbackEnabled ? "await startMcpAppsBridge();" : "throw nativeErr;"}
|
|
2188
2188
|
}
|
|
2189
2189
|
}
|
|
2190
2190
|
} catch (err) {
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -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>;
|
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
(
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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 = {
|
package/dist/org/context.d.ts
CHANGED
|
@@ -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>;
|
package/dist/org/context.js
CHANGED
|
@@ -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();
|
package/dist/org/handlers.d.ts
CHANGED
|
@@ -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;
|
package/dist/org/index.d.ts
CHANGED
|
@@ -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;
|