import "../authorization/check-action.js"; import type { CallToolResult } from "@modelcontextprotocol/server"; import { MCP_APP_MIME_TYPE, type ActionMcpAppResourceConfig } from "../action.js"; import type { ActionEntry } from "../agent/production-agent.js"; import { type NormalizedToolResultImages } from "../agent/tool-result-images.js"; import type { BearerCredentialRefusal } from "../server/bearer-credential-refusal.js"; import { type McpDirectoryWidgetWriteCapabilityInput, type McpDirectoryWidgetReadArgument } from "../shared/embed-auth.js"; export { isAutoReadExcludedActionName } from "./action-eligibility.js"; import { type EmbedCredentialGuard, type McpApprovalReview, type UpstreamStatus } from "./execute-action-call.js"; import type { ExternalAgentPolicy } from "./external-agent-policy.js"; export declare const PRESERVE_MCP_OBJECT_RESULT: unique symbol; export type MCPActionEntry = ActionEntry & { [PRESERVE_MCP_OBJECT_RESULT]?: true; }; export declare function returnsQueryPayload(entry: ActionEntry): boolean; export interface MCPConfig { name: string; title?: string; appId?: string; description: string; instructions?: string; keyToolNames?: readonly string[]; websiteUrl?: string; icons?: Array<{ src: string; mimeType?: string; sizes?: string[]; theme?: "light" | "dark"; }>; version?: string; actions: Record; /** * Full ("production") action surface served to an **authenticated real * caller** — a connect-minted token, an `agent-native mcp install` stdio * proxy (owner-email header / `AGENT_NATIVE_OWNER_EMAIL`), or a deployed / * `AGENT_MODE=production` app. In local dev `actions` is intentionally the * sparse, dev-toggled surface (builtins + read-only public-agent actions) * so the local agent chat and unauthenticated dev probes don't see every * mutating tool; but per the external-agents contract a real caller that * connected with a token MUST get the full surface even in dev. When unset * (production, where `actions` already IS the full set) the swap is a * no-op. See `external-agents` skill, "Dev vs production tool surface". */ productionActions?: Record; /** Unlisted from this MCP surface; used only when minting scoped widget tickets. */ widgetReadActions?: Record; /** Unlisted from this MCP surface; editor mutations callable only by scoped widget grants. */ widgetWriteActions?: Record; askAgent?: (message: string) => Promise; /** Server-only adapter for this app's durable tasks on authenticated HTTP MCP. */ createLocalAgentTaskClient?: (event: import("h3").H3Event) => import("../a2a/mcp-task-client.js").McpAgentTaskClient; /** * The app renders approval reviews in its own pages, as the toolkit's * `AppProviders` does, so a signed-in browser opening a review link is sent * to the app's home page with the grant id under `MCP_APPROVAL_HASH_PARAM`. * Otherwise the link shows the framework's plain review page. The app's * review reads approvals under `/_agent-native`, so `mountMCP` refuses this * with any other route prefix. */ appApprovalPage?: boolean; builtinCrossAppTools?: boolean; /** * `"app"` serves the app's own action registry flat. `"directory"` serves * only the explicitly curated connectorCatalog; it also requires explicit * per-action MCP annotations and a unique HTTPS widgetDomain. Both profiles * omit cross-app builtins, `ask-agent`, and `tool-search`, and ignore the * full-catalog opt-in. Explicit denies and OAuth scope filters still apply. * * The dev-open surface split is deliberately NOT bypassed: an * unauthenticated loopback probe still gets `actions`, not * `productionActions`. Parity is with the app's agent for a real * authenticated caller, not an escalation for anonymous ones. */ catalogMode?: "app" | "directory"; directoryProfile?: { connectorCatalog: string[]; instructions?: string; widgets?: boolean; widgetDomain?: string; widgetTargets?: Record, result: unknown) => McpDirectoryWidgetTarget | null>; authorizeWidgetWrite?: (input: { toolName: string; args: Record; result: unknown; target: McpDirectoryWidgetTarget; identity: MCPCallerIdentity; }) => boolean | Promise; widgetReadActionArguments?: Record>; widgetWriteActionArguments?: Record>; /** Actions whose capability-backed `mcp-widget` execution is strictly read-only. */ widgetReadOnlyActions?: readonly string[]; /** Unlisted public reads that are available only through a scoped widget ticket. */ widgetReadPublicActions?: readonly string[]; /** Unlisted authenticated reads available only through a scoped widget ticket. */ widgetReadPrivateActions?: readonly string[]; /** Authenticated reads surfaced on other agent profiles, but only scoped here. */ widgetReadAuthenticatedActions?: readonly string[]; /** * Scoped reads minted only into a write capability, and only for a target * that lists the mapped write action: `{ "list-resource-shares": * "share-resource" }` keeps collaborator lists out of read-only tickets. */ widgetReadActionWriteGates?: Record; /** Omit the one shared resource title when tools have distinct invocation labels. */ widgetResourceTitle?: string | false; keyToolNames?: readonly string[]; toolDescriptions?: Record; toolParameterDescriptions?: Record>; hiddenToolParameters?: Record; projectResult?: (toolName: string, result: unknown) => unknown; }; connectorCatalog?: string[]; widgetDomain?: string; externalAgents?: ExternalAgentPolicy; } export interface McpDirectoryWidgetTarget { targetPath: string; resourceIds: Record; /** Mutations available to this artifact's editor, selected from the profile allowlist. */ writeActions?: readonly string[]; } export interface MCPCallerIdentity { userEmail: string | undefined; identityAssurance?: "user" | "org-member" | "organization" | "service"; /** Issue time, in milliseconds, from a verified app-issued MCP credential. */ mcpCredentialIssuedAtMs?: number; orgId?: string | null; orgDomain: string | undefined; oauthScopes?: string[]; oauthClientId?: string; firstPartyMcp?: boolean; } export declare function hasVerifiedMcpUserIdentity(identity: MCPCallerIdentity | undefined): identity is MCPCallerIdentity & { userEmail: string; identityAssurance: "user"; }; export declare const MCP_ACTION_APPROVAL_INPUT_KEY = "actionApproval"; export declare function actionApprovalError(message: string): CallToolResult; /** Where a call's approval stands when the review page has to decide it. */ export type McpApprovalReviewReason = "unprompted" | "declined" | "opened"; /** * The tool result for a call the review page has to approve: what happened, * the page's link, and how to finish. An error result, so a host neither * renders an empty widget for it nor holds it to the tool's output schema. * Every word is the server's, and none of it approves anything. */ export declare function actionApprovalReviewResult(review: McpApprovalReview, reason: McpApprovalReviewReason, requestMeta: MCPRequestMeta | undefined, ownerEmail: string | undefined): Promise; export interface MCPRequestMeta { localAgentTaskClient?: import("../a2a/mcp-task-client.js").McpAgentTaskClient; origin?: string; resolveEmbedRequestOrigin?: () => string; /** Where this mount serves the approval review page; `mcpApprovalRoute()` by default. */ approvalRoute?: string; basePath?: string; target?: "browser" | "desktop" | "terminal"; clientName?: string; clientHint?: string; mcpRetryToken?: string; fullCatalog?: boolean; fullSurface?: boolean; inlineMcpApps?: boolean; transport?: "http" | "stdio"; } export declare class McpDirectoryProfileValidationError extends Error { readonly code = "MCP_DIRECTORY_PROFILE_INVALID"; constructor(message: string); } export declare function selectMcpActionSurface(config: MCPConfig, requestMeta?: MCPRequestMeta): Record; export declare function selectMcpDirectoryWidgetReadActions(profile: MCPConfig["directoryProfile"] | undefined, actions: Record): Record | undefined; export declare function selectMcpDirectoryWidgetWriteActions(profile: MCPConfig["directoryProfile"] | undefined, actions: Record): Record | undefined; export declare function getConfiguredMcpOwnerEmail(): string | undefined; export declare function validateMcpDirectoryProfile(config: MCPConfig, sourceActions?: Record): void; export declare function validateMcpDirectoryWidgetDomain(domain: string | undefined): void; export declare function declaredMcpToolNames(actions: Record): string[]; export declare function isAuthenticatedReadAction(entry: ActionEntry): boolean; interface ResolvedMcpAppResource { uri: string; legacyUris?: string[]; name: string; title?: string; description?: string; html: ActionMcpAppResourceConfig["html"]; requestOrigin?: string | undefined; mimeType: typeof MCP_APP_MIME_TYPE; _meta?: Record; } export declare function metadataObject(value: unknown): Record; export declare function mcpResultHasContent(result: unknown): boolean; export declare function mcpAppEmbedOpenLinkMeta(result: unknown, resource: ResolvedMcpAppResource, meta: MCPRequestMeta | undefined): Record; /** * The `_meta` that hands an app-only tool's start URL, such as * `create_embed_session`'s, to the view that called it. Hosts may show * structured content to the model, so the URL travels only here. */ export declare function mcpAppOnlyEmbedStartMeta(result: unknown): Record; export declare function mcpDirectoryWidgetSourceMeta(toolName: string, embedMeta: Record): Record; export declare function withServerMintedMcpAppEmbedStart(result: unknown, meta: MCPRequestMeta | undefined, directoryWidget?: { targetPath: string; capability: { mode: "read"; value: { appId: string; resourceUri: string; resourceIds: Record; actionArguments: Record>; }; } | { mode: "write"; value: McpDirectoryWidgetWriteCapabilityInput; }; }): Promise; export declare function withoutMcpAppEmbedTicket(result: unknown): unknown; export declare function mcpDirectoryWidgetCapabilityForTool(config: MCPConfig, resource: ResolvedMcpAppResource, toolName: string, args: Record, result: unknown, actions: Record, identity: MCPCallerIdentity | undefined): Promise<{ targetPath: string; capability: { mode: "write"; value: McpDirectoryWidgetWriteCapabilityInput; }; } | { targetPath: string; capability: { mode: "read"; value: { appId: string; resourceUri: string; resourceIds: Record; actionArguments: Record>; }; }; } | undefined>; export declare function buildLinkArtifacts(entry: ActionEntry, args: Record, result: any, meta: MCPRequestMeta | undefined): { block?: { type: "text"; text: string; }; _meta?: Record; }; export declare function getMcpDirectoryWidgetResourceUri(appId: string | undefined, fallback?: string): string; export declare function resolveMcpAppResourceSafely(config: MCPConfig, actionName: string, entry: ActionEntry, requestMeta?: MCPRequestMeta): Promise; export declare function mcpAppWidgetsEnabledForIdentity(config: MCPConfig, identity: MCPCallerIdentity | undefined): boolean; export declare function openAiToolResultMeta(resource: ResolvedMcpAppResource, directoryMode?: boolean): Record; export declare function mcpAppStructuredContent(result: unknown, meta: Record | undefined, options?: { contracted?: boolean; }): Record; /** * The transport transform from an action's sanitized result to * `structuredContent`. An opted-in action's advertised `outputSchema` is * derived from its own by these same steps (`deriveMcpResponseOutputSchema`), * so a change here must change that derivation too. Opted-in (`contracted`) * actions keep an empty object as `{}`, since the `{ status: "ok" }` * placeholder is not in their contract. A result arrives with an embed URL * field masked in place rather than deleted, which their contract may * require. */ export declare function mcpResponseStructuredContent(result: unknown, meta: Record | undefined, options?: { contracted?: boolean; }): Record; /** * The `_meta` keys a direct call may send an embed credential in: the start * URL a widget or an app-only tool's view starts its embed session from, and * the source ticket a directory widget renews that session with. Only an * entry the call built as that handoff qualifies. An open link is never one: * it names the page to open outside the widget, not a start URL. */ export declare const EMBED_HANDOFF_META_KEYS: ReadonlySet; /** * A direct `tools/call` result as it leaves the server, built last from the * assembled response, the call's `guard`, the `_meta` entries it built as an * embed handoff, and the result's images, added here so they are checked as * sent. In embed context every text block and `structuredContent` goes through * the walker in that context, so a credential the call holds is hidden * wherever one of them echoes it. An image, or any other block, that carries * one in its URL or data is left out whole, so neither stands in for the * other. In `_meta`, a handoff entry under one of `EMBED_HANDOFF_META_KEYS` * passes as built, and any other entry that carries a credential is left out * whole: a link with its ticket hidden would not open. A call the guard could * not read in full throws `UnreadableResultError`, so none of it is shown. */ export declare function modelVisibleToolResponse; _meta?: Record; }>(response: T, guard: EmbedCredentialGuard, handoff?: Record, images?: NormalizedToolResultImages): T; /** How a tool's response opens, by what its upstream reported. */ export declare function upstreamStatusText(name: string, upstream: UpstreamStatus): string; /** * A write whose result cannot be shown, because it could not be checked or, * with `error`, because building the response failed after the action ran. * The write has run, so it reports success rather than invite a retry, unless * its upstream reported failure or its status could not be read, and its * widget still gets the embed handoff it built. */ export declare function withheldWriteResponse(name: string, handoff: Record, upstream: UpstreamStatus, error: string | undefined): { content: { type: "text"; text: string; }[]; isError?: boolean | undefined; _meta?: { [k: string]: unknown; } | undefined; }; export declare function textPagingInputs(parameters: unknown, readOnly: boolean): string[]; export declare function conciseMcpAppToolText(name: string, result: unknown, structuredContent: Record, paging?: readonly string[]): string; export declare function conciseToolResultText(name: string, result: unknown, options?: { preserveObjectResult?: boolean; paging?: readonly string[]; }): string; export declare function createMCPServerForRequest(config: MCPConfig, identity: MCPCallerIdentity | undefined, requestMeta?: MCPRequestMeta): Promise; export declare function getAccessTokens(): string[]; export declare function getBearerToken(authHeader: string | undefined): string | undefined; export declare class McpIdentityVerificationUnavailableError extends Error { readonly cause: unknown; constructor(cause: unknown); } export declare class McpIdentityVerificationRefusedError extends Error { constructor(); } export type VerifyAuthResult = { authed: boolean; identity?: MCPCallerIdentity; fullSurface?: boolean; fullCatalog?: boolean; /** * The token verified, but its standing could not be checked: the * connect-token lookup, the retired-address check, or the * membership check hit a database or identity-authority error. Answer with * a retryable error, not an auth challenge: signing in again would not help. */ unavailable?: true; /** * Why a presented bearer token was refused. Absent when no token was * presented, and when the refusal is `unavailable`. */ refusal?: BearerCredentialRefusal; }; /** * Verify the inbound auth header. Returns: * - { authed: true, identity } when verified. A credential this app issued * (an MCP OAuth access token or a connect token) supplies its subject and * org, checked against live membership. A deployment-secret A2A JWT may * supply its asserted user (`sub`), while an org-secret JWT supplies only * verified organization scope. Static-token auth gets identity from * `AGENT_NATIVE_OWNER_EMAIL` or `X-Agent-Native-Owner-Email` (the * `agent-native mcp install` flow). `identity` is undefined only for true * dev-open with no owner hint. * - { authed: false, refusal? } on rejection; `refusal` is set whenever a * presented token was refused. * * Credentials this app issued are recognized and verified before any A2A * rule runs, so a change to cross-app trust cannot reclassify them. Both A2A * paths bind `org_domain` to local organization metadata. The MCP endpoint * wraps tool runs in `runWithRequestContext({ userEmail, orgId })`. Without * that wrap, the MCP endpoint loses tenant identity and downstream * `accessFilter` / `resolveCredential` calls fall back to platform-wide * defaults. * * `ownerEmailHeader` is the forwarded `X-Agent-Native-Owner-Email` value; it * is consulted ONLY on the static-token / dev-open path (never to influence * verified JWT identity), so the install flow runs tools as the configured * owner instead of an unscoped anonymous caller. */ export declare function verifyAuth(authHeader: string | undefined, ownerEmailHeader?: string, options?: { allowDevOpen?: boolean; resourceUrl?: string | string[]; event?: unknown; /** This app's public origin; federated orgs need it for the membership check. */ requestOrigin?: string; }): Promise; export declare function resolveOrgIdFromDomain(orgDomain: string | undefined): Promise; export declare function resolveMcpIdentityOrgId(identity: MCPCallerIdentity | undefined): Promise;