import { C as ClientCapabilityOptions, o as MCPTask, M as MCPServerConfig, R as RpcLogger, a as RetryPolicy, H as HttpServerConfig, l as MCPReadResourceResult } from './types-CI0Xyszt.js'; import { CallToolResult, Tool } from '@modelcontextprotocol/client'; import { d as ModelVisibleMcpToolResults, m as HostConfigOAuthProfile, M as McpProtocolVersion } from './types-HXAijHji.js'; import { z } from 'zod'; import { P as PluginFileSource } from './types-m3TBGFFa.js'; import { S as SkillEntry, aN as SkillResourceRef, aM as SkillIdentityFrontmatter, bf as DetailedTaskExt, bg as GetTaskExtResult, bh as TaskExtNotificationParams, aW as TaskLifecycleObservation, M as MCPClientManager, bi as TasksWire } from './index-fZyfLCHE.js'; import { p as probeMcpServer, C as ConnectedServerDoctorState, S as ServerDoctorResult, O as OAuthProtocolVersion, s as OAuthDynamicRegistrationMetadata, R as RegistrationStrategy2025_03_26, j as RegistrationStrategy2025_06_18, k as RegistrationStrategy2025_11_25, Q as OAuthAuthMode, F as ResourceIndicatorDecision, T as BaseOAuthStateMachineConfig, l as RegistrationStrategy2026_07_28, B as OAuthRequestResult, U as OAuthHttpRequest, D as OAuthStateMachine, y as OAuthFlowStep, H as HttpHistoryEntry, m as OAuthFlowState, V as MaybePromise, t as OAuthEmulationConfig, n as EmulatedAuthAttempt, u as OAuthEmulationCoverage, v as OAuthEmulationDivergence, W as OAuthHttpResponse, z as OAuthRequestExecutor, L as LogErrorDetails, q as InfoLogLevel, I as InfoLogEntry } from './server-doctor-core-DFaTigIr.js'; declare const MCP_UI_EXTENSION_ID = "io.modelcontextprotocol/ui"; declare const MCP_UI_RESOURCE_MIME_TYPE = "text/html;profile=mcp-app"; /** * The SDK's default advertised capabilities. * * `io.modelcontextprotocol/skills` is deliberately ABSENT. SEP-2133 makes * extension support opt-in, and the SDK on its own has no SEP-2640 fulfiller: * the thing that actually loads, verifies, and renders a server-served skill * is the inspector app. A default declaration would advertise behavior the * bare SDK cannot complete — the advertise=enforce rule read backwards. * Callers that DO implement it opt in with * {@link withSkillsExtensionCapability}, which is what the inspector's connect * seams do (local) and what the MCPJam host persona's stored * `clientCapabilities` does (hosted). * * The backend's `DEFAULT_CLIENT_CAPABILITIES_V2` is the hand-mirror of this * function and stays in lockstep with it. */ declare function getDefaultClientCapabilities(): ClientCapabilityOptions; /** * Merges `io.modelcontextprotocol/skills` (SEP-2640) into a client * capabilities object. * * Same shape and contract as `withXaaExtensionCapability` in the app: merge, * never overwrite, and never clobber an existing settings object for the same * extension id (a caller that already pinned settings meant them). * * Applied per connection by a surface that knows a SEP-2640 fulfiller is * actually installed. Because the extension is connection-level, this is the * ONLY seam skills declaration uses — there is no per-request `_meta` * equivalent, which is what makes it simpler than the tasks extension. */ declare function withSkillsExtensionCapability | undefined>(capabilities: T): Record; declare function normalizeClientCapabilities(capabilities?: ClientCapabilityOptions): ClientCapabilityOptions; /** * Adds runtime-gated capabilities that are only valid when the corresponding * handlers are actually installed on the client. */ declare function applyRuntimeClientCapabilities(capabilities?: ClientCapabilityOptions, runtime?: { elicitation?: boolean; }): ClientCapabilityOptions; declare function mergeClientCapabilities(base?: ClientCapabilityOptions, overrides?: ClientCapabilityOptions): ClientCapabilityOptions; declare const MCP_DIRECT_IMAGE_MAX_BYTES: number; declare const MCP_IMAGE_MAX_MEDIA_PARTS = 16; declare const MCP_IMAGE_MAX_TOTAL_BYTES: number; declare const MCP_LINKED_RESOURCE_MAX_READS = 16; declare const MCP_PRESERVE_RAW_RESULT_FOR_UI = "_mcpjamPreserveRawResultForUi"; type McpModelOutputContentPart = { type: "text"; text: string; } | { type: "media"; data: string; mediaType: string; }; type McpModelOutputContent = { type: "content"; value: McpModelOutputContentPart[]; }; type McpModelVisibleToolResultPolicy = { modelVisibleMcpToolResults?: ModelVisibleMcpToolResults; }; type McpModelOutputOptions = McpModelVisibleToolResultPolicy & { maxImageBytes?: number; maxImageCount?: number; maxTotalImageBytes?: number; maxLinkedResourceReads?: number; }; type McpLinkedResourceReader = (params: { uri: string; options?: { abortSignal?: AbortSignal; }; }) => Promise; type McpModelOutputWithLinkedResourcesOptions = McpModelOutputOptions & { readResource?: McpLinkedResourceReader; abortSignal?: AbortSignal; }; /** * Converts direct and embedded MCP image tool results into AI SDK content * output. * * Returns undefined when there are no direct `type: "image"` or embedded image * resource content blocks so existing JSON/text serialization paths stay * unchanged for ordinary results. */ declare function mcpCallToolResultToModelOutput(result: CallToolResult, options?: McpModelOutputOptions): McpModelOutputContent | undefined; /** * Converts direct, embedded, and linked MCP image tool results into AI SDK * content output. Linked resources are resolved only through the supplied * MCP `resources/read` callback; this helper never fetches a URI directly. */ declare function mcpCallToolResultToModelOutputWithLinkedResources(result: CallToolResult, options?: McpModelOutputWithLinkedResourcesOptions): Promise; /** * The publisher-agnostic directory-readiness result algebra. * * WHY THIS EXISTS. `claude-readiness` was written first and answered one * question: would Anthropic list this connector. The OpenAI plugin directory * asks a structurally identical question against an entirely different policy, * and the two share nothing about WHAT is required — but they share everything * about how a requirement is graded: a finding cites a source, declares its * provenance, names the capabilities it needed, and can be dispositive or not; * a lane rolls its findings up; coverage is reported separately from verdicts * so an unevaluated requirement is never mistaken for a satisfied one. * * Duplicating that algebra per publisher would let the two drift, and the * first thing to drift would be the rule that keeps this product honest — * "did not run" must never read as "conformed". So the algebra lives here, * once, and each publisher supplies its own lane union, source-reference type * and capability union as type parameters. * * WHAT IS *NOT* HERE. Anything a publisher decides: which lanes exist, which * of them are dispositive, what a source reference looks like, what the * engine version is. Those are arguments, not constants — a shared module that * knew Anthropic's lane names would not be shared, it would be Anthropic's * module with a second caller. * * Pure data reasoning: no MCP client, no transport, no Node built-ins. Safe * from the browser entry. */ /** * What KIND of statement a finding is making. Deliberately not `MUST`/`SHOULD` * — those belong to the MCP spec, and reusing them here would let a publisher's * policy preference read as a protocol violation. * * These classes are shared across publishers because they describe the * EPISTEMIC status of a statement, not its content: "a human has to look at * this" means the same thing whoever is reviewing. * * - `required` — the publisher states it as a submission/review requirement. * A violation means the listing will be rejected or delisted. * - `runtime-blocker` — the host cannot complete the flow at all. Distinct * from `required` because it fails before policy is even reached. * - `recommended` — stated guidance whose violation is not disqualifying. * - `experimental-feature` — a capability badge, not a grade. Absence is * never a defect. * - `manual-review` — a human has to look. Quality, ownership, and * credential validity cannot be decided from the wire. * - `heuristic` — a signal, not a verdict. May be confirmed by an LLM or a * person; never fails a lane. */ declare const DIRECTORY_FINDING_CLASSES: readonly ["required", "runtime-blocker", "recommended", "experimental-feature", "manual-review", "heuristic"]; type DirectoryFindingClass = (typeof DIRECTORY_FINDING_CLASSES)[number]; /** * The status of a lane whose findings are dispositive. * * - `ready` — every applicable requirement was evaluated and satisfied. * - `not-ready` — at least one applicable requirement was violated. * - `incomplete` — nothing was violated, but something the lane needs was * never evaluated. `missingInputs` says what the caller must supply. * * `incomplete` is load-bearing and self-describing: a wire-only run cannot see * a screenshot, and reporting `ready` for a lane it could not evaluate would * be the single most damaging thing this product could do. */ type DirectoryLaneStatus = "ready" | "not-ready" | "incomplete"; /** * How a finding was established. A grade that cannot say where its evidence * came from is not auditable, and provenance is what stops a static lint from * being read as an observed runtime fact. * * - `wire` — observed in an HTTP/MCP exchange this run performed. * - `browser` — observed in a rendered widget/browser harness. * - `static` — read out of a document, manifest, archive or schema without * dialing anything. * - `declared` — asserted by the submitter in a submission profile. Never * independently verified by this run. * - `manual` — recorded by a person. * - `llm` — a language model READ evidence this run gathered and said * something about it. Its own provenance value rather than being folded * into `static` or `manual`, because a reader deciding how much weight to * put on a line has to be able to see that a model, not a person and not * the wire, is what produced it. Findings carrying this provenance are * confined to non-dispositive classes by * `directory-readiness/observations`; the separation is enforced there, * not merely documented here. */ declare const DIRECTORY_EVIDENCE_PROVENANCE: readonly ["wire", "browser", "static", "declared", "manual", "llm"]; type DirectoryEvidenceProvenance = (typeof DIRECTORY_EVIDENCE_PROVENANCE)[number]; /** * How much a check DOES to the target. * * - `passive` — no request attributable to this check. * - `read-only` — requests with no persistent effect on the target. * - `side-effecting` — registers a client, spends a grant, mutates state. * Only ever reached through an explicit intrusive opt-in. */ declare const DIRECTORY_INTRUSIVENESS_LEVELS: readonly ["passive", "read-only", "side-effecting"]; type DirectoryIntrusiveness = (typeof DIRECTORY_INTRUSIVENESS_LEVELS)[number]; /** A finding's verdict. `informational` carries no pass/fail meaning at all. */ type DirectoryFindingStatus = "satisfied" | "violated" | "not-evaluated" | "not-applicable" | "informational"; /** * One graded statement about the target. * * Every field below the verdict exists so the finding survives contact with * time: publisher documentation changes, and a grade that cannot say WHICH * revision it was made against becomes silently wrong rather than visibly * stale. * * `SourceRef` is a type parameter rather than a shared interface on purpose. * Anthropic's citation names a page key from Anthropic's corpus and OpenAI's * names one from OpenAI's; a union of the two would let a check cite the wrong * publisher's documentation and still typecheck. */ interface DirectoryReadinessFinding { /** * Stable identifier, shipped WITH its check. There is deliberately no frozen * union of ids for checks that do not exist yet: publishing one would make * the inventory look complete while the coverage was not, which is the same * lie `incomplete` exists to prevent. */ id: string; title: string; lane: Lane; class: DirectoryFindingClass; status: DirectoryFindingStatus; /** One sentence a submitter can act on. Absent for `satisfied`. */ remediation?: string; /** Where in the publisher's documentation this requirement comes from. */ source: SourceRef; provenance: DirectoryEvidenceProvenance; intrusiveness: DirectoryIntrusiveness; /** * Capabilities the runner needed. When the run lacks one, the finding is * `not-evaluated` and this is why — which is what makes a coverage gap * legible instead of silent. */ requiresCapabilities?: Capability[]; /** Why a `not-evaluated` finding was not evaluated, in plain words. */ notEvaluatedReason?: string; /** ISO-8601. The moment the verdict was reached, not when it was rendered. */ evaluatedAt: string; /** Version of the readiness engine that produced this finding. */ engineVersion: string; /** Raw observation behind the verdict. Redacted before telemetry. */ details?: Record; /** * Suite results this finding was DERIVED from rather than re-observed, e.g. * `"oauth-conformance:oauth-prm-resource-match"`. Readiness composes; a * finding that quietly re-ran an existing check would let the two disagree. */ derivedFrom?: string[]; } /** * What a lane managed to look at, reported SEPARATELY from what it found. * * A lane with zero violations and zero evaluated checks is not a pass, and the * only way to keep those apart is to publish the denominator. */ interface DirectoryLaneCoverage { lane: Lane; /** Findings that reached a `satisfied`/`violated` verdict. */ evaluated: number; /** Applicable but never exercised. Each one is an unanswered question. */ notEvaluated: number; /** Could not apply to this target; not a gap. */ notApplicable: number; /** * Named inputs the caller could supply to close the gap, e.g. * `"submissionProfile"`. Empty when the gap is not the caller's to close. */ missingInputs: string[]; } interface DirectoryReadinessLaneResult { lane: Lane; status: DirectoryLaneStatus; /** One line a human can read without opening the findings. */ summary: string; coverage: DirectoryLaneCoverage; } /** * A capability badge. Never a defect when absent — that is the whole * difference between a badge and a requirement. */ interface DirectoryCapabilityBadge { id: string; title: string; /** * `supported` — observed working. `unsupported` — observed absent. * `claimed` — the submitter declared it and this run did not verify it. * `not-evaluated` — never looked, which is the default for a badge whose * depth-evaluation was neither claimed nor selected. */ state: "supported" | "unsupported" | "claimed" | "not-evaluated"; detail?: string; provenance: DirectoryEvidenceProvenance; } /** * Decide one lane's status from its findings. * * Only `required` and `runtime-blocker` findings can make a lane `not-ready`. * A `heuristic` or `manual-review` finding never does, however alarming it * reads — that separation is what keeps an LLM's opinion out of a verdict a * submitter is held to. */ declare function decideLaneStatus(findings: readonly Pick, "class" | "status">[]): DirectoryLaneStatus; /** Tally a lane's coverage from its findings. */ declare function summarizeLaneCoverage(lane: Lane, findings: readonly Pick, "status">[], missingInputs?: readonly string[]): DirectoryLaneCoverage; /** * Finding constructors, bound once per publisher. * * Checks never build a {@link DirectoryReadinessFinding} literal. Going through * these is what guarantees the fields that make a grade auditable — source * citation, provenance, intrusiveness, engine version, timestamp — are present * on every finding rather than on the ones whose author remembered. A check * that could construct a finding by hand is a check that can quietly ship one * with no provenance. * * WHY A FACTORY RATHER THAN FREE FUNCTIONS. The engine version is a property of * the PUBLISHER's check inventory, not of a call site: Anthropic's checks and * OpenAI's are versioned independently, so two grades of the same target under * one publisher's snapshot stay comparable while the other publisher's engine * moves. Threading it through every call would put it in the hands of the * caller — and a check that passes the wrong version stamps a finding with a * provenance it does not have. Binding it once, at module scope, makes the * version impossible to get wrong from inside a check. * * Pure data. No transport. */ /** Everything about a check that does not depend on what it observed. */ interface DirectoryCheckDefinition { id: string; title: string; lane: Lane; class: DirectoryFindingClass; source: SourceRef; provenance: DirectoryEvidenceProvenance; /** Defaults to `read-only`; a purely static lint should say `passive`. */ intrusiveness?: DirectoryIntrusiveness; requiresCapabilities?: Capability[]; } /** What every check is handed, so none of them reads a clock of its own. */ interface DirectoryCheckStamp { /** One timestamp for the whole run: findings from one run are one moment. */ evaluatedAt: string; } /** * Mark a finding as derived from an existing suite result rather than * re-observed. * * Readiness COMPOSES: re-running an equivalent check would let readiness and * the suite disagree about the same server, and the first question anyone asks * about a disagreement is which one to believe. * * Publisher-agnostic, and generic over the finding rather than over the three * type parameters, so a publisher's alias keeps its own finding type on both * sides of the call instead of widening it to the base shape. */ declare function derivedFrom, "derivedFrom">>(finding: Finding, ...sources: string[]): Finding; /** * Model-backed experience observations, and the wall between them and a * verdict. * * WHAT AN OBSERVATION IS. A bounded sentence a language model produced after * reading evidence THIS run already gathered: "the tool descriptions repeat * the product name in every entry", "two skills describe the same task". It is * a reading, not a measurement, and it is worth surfacing precisely because * the deterministic graders cannot produce it. * * WHAT AN OBSERVATION IS NOT, and this is the entire reason this module exists * rather than a `parse()` call at each call site: * * - it is not a finding. The model returns observation IDs from a frozen * catalogue; the SDK — pure, offline, testable — decides what finding each * ID becomes. * - it is not a requirement. A model cannot invent an ID, so it cannot * invent a rule to grade against. * - it is not dispositive. Every finding this module produces lands in the * publisher's experience lane as `heuristic` or `manual-review`, which * `decideLaneStatus` already ignores. {@link mapObservationsToFindings} * re-checks that at runtime rather than trusting the catalogue's author. * - it is not evidence. `provenance` is `llm`, so a reader can always see * that a model, not the wire, is behind the line. * * WHY THE FAILURE PATH IS A FIRST-CLASS TYPE. Every way this can go wrong — * the provider erred, the org is out of credits, the model returned something * that did not validate, nobody asked for observations at all — has to arrive * at the reader as an explicit `not-evaluated` state with a machine-readable * reason. A billing denial that silently produced no observations would be * indistinguishable from a model that found nothing to say, and "we could not * afford to look" must never render as "we looked and it was fine". Equally, * none of these states may make the TARGET fail: a payment problem is the * account's, not the server's. * * Pure data reasoning: no transport, no provider SDK, no Node built-ins. Safe * from the browser entry, which is what lets a UI render an observation and * its billing status without being able to request one. */ /** * The only two classes an AI-derived finding may carry. * * Both are ignored by {@link import("./types.js").decideLaneStatus}, so a * finding constrained to them cannot decide a lane however it is worded. The * constraint is a runtime list rather than only a type because a catalogue can * be assembled from data, and a `class` that arrived as a string would satisfy * the compiler on its way to deciding a required lane. */ declare const DIRECTORY_OBSERVATION_FINDING_CLASSES: readonly ["heuristic", "manual-review"]; type DirectoryObservationFindingClass = (typeof DIRECTORY_OBSERVATION_FINDING_CLASSES)[number]; /** * How sure the model says it is. * * Three coarse buckets, never a number. A percentage invites arithmetic — * averaging confidences, thresholding on them, ranking findings by them — and * every one of those operations treats a model's self-report as a calibrated * probability, which it is not. Buckets can be displayed and sorted and * nothing else, which is all this deserves. * * NO ADAPTER MAY REINTERPRET THIS. A surface that promoted `high` to a * violation would have re-derived a verdict from an opinion, which is the one * thing this module exists to prevent. */ declare const DIRECTORY_OBSERVATION_CONFIDENCE: readonly ["low", "medium", "high"]; type DirectoryObservationConfidence = (typeof DIRECTORY_OBSERVATION_CONFIDENCE)[number]; /** * Why a run holds no model observations, as an independent axis from the run's * own status. * * INDEPENDENT IS THE POINT. A run whose deterministic lanes all graded cleanly * is a COMPLETED run even when the observation call was refused for credit, and * collapsing the two would either fail a healthy run over a billing problem or * hide the billing problem inside a green result. * * - `not-requested` — the caller did not ask. The default, and free. * - `pending` — asked for, not yet answered. Only ever seen mid-run. * - `completed` — a validated envelope is attached. * - `billing-blocked` — the reservation was denied before any provider call. * No model ran, so nothing was charged. * - `provider-failed` — the provider errored or timed out. * - `invalid-output` — the provider answered and the answer did not validate * against the schema. Deliberately distinct from `provider-failed`: one is * an outage and the other is a prompt/schema defect, and they are fixed by * different people. */ declare const DIRECTORY_OBSERVATION_STATUSES: readonly ["not-requested", "pending", "completed", "billing-blocked", "provider-failed", "invalid-output"]; type DirectoryObservationStatus = (typeof DIRECTORY_OBSERVATION_STATUSES)[number]; /** * Machine-readable reasons, so a surface can BRANCH rather than string-match a * human sentence. * * `billing_limit_reached` is the load-bearing one: it is the value a UI keys * on to offer a top-up, and the value a CLI keys on for its distinct exit * code. Renaming it is a breaking change to three surfaces at once. */ declare const DIRECTORY_OBSERVATION_REASONS: readonly ["not_requested", "billing_limit_reached", "provider_error", "provider_timeout", "schema_invalid", "no_evidence", "cancelled"]; type DirectoryObservationReason = (typeof DIRECTORY_OBSERVATION_REASONS)[number]; /** One thing the model said, after validation. */ interface DirectoryObservation { /** * A member of the publisher's frozen catalogue. The model SELECTS; it does * not name. An unknown ID is rejected rather than passed through, because a * passed-through ID is a requirement the model invented. */ id: Id; /** One bounded sentence, rendered verbatim and never parsed for meaning. */ summary: string; confidence: DirectoryObservationConfidence; /** * Pointers into the evidence the model was shown — a tool name, a skill * name, a manifest field. Free-form strings, bounded in count and length, * carried so a reader can check the model's homework. */ evidenceRefs: string[]; } /** * A validated batch of observations, stamped with everything needed to * reproduce or discredit it later. * * The four version/identity fields are not bookkeeping. A model's output is * only interpretable against the prompt that elicited it and the schema that * shaped it; an envelope that cannot say which model, which prompt and which * schema produced it becomes an unattributable opinion the moment any of the * three moves — and this feature will move all three. */ interface DirectoryObservationEnvelope { /** Which publisher's catalogue `observations[].id` are drawn from. */ readinessKind: string; /** Which observation pass this is — publishers may run more than one. */ observationKind: Kind; /** The envelope/catalogue revision. Bumped when IDs or bounds change. */ observationSchemaVersion: string; /** The prompt revision that elicited this. Versioned separately. */ promptVersion: string; /** The provider model ID, exactly as the backend chose it. */ modelId: string; /** ISO-8601, stamped by the backend when the provider answered. */ observedAt: string; observations: DirectoryObservation[]; } /** * The observation axis of a result, whatever happened on it. * * ALWAYS PRESENT, including when nobody asked: an absent field reads as "this * build has no AI" and a `not-requested` one reads as "you did not ask for * it", and only the second is true. */ interface DirectoryObservationState { status: DirectoryObservationStatus; /** Absent only when `status` is `completed`. */ reason?: DirectoryObservationReason; /** A sentence for a human. Never parsed — branch on `reason`. */ detail?: string; /** Present only when `status` is `completed`. */ envelope?: DirectoryObservationEnvelope; } /** * Bounds every envelope is held to, before a single ID is looked up. * * These are REFUSAL thresholds, not truncation thresholds. A model that * returned four hundred observations has misunderstood its instructions, and * silently keeping the first twenty would ship a report that looked considered * and was arbitrary. The whole envelope is rejected, the state becomes * `invalid-output`, and the reader is told why. */ declare const DIRECTORY_OBSERVATION_LIMITS: Readonly<{ maxObservations: 24; maxSummaryChars: 400; maxEvidenceRefs: 8; maxEvidenceRefChars: 200; maxModelIdChars: 128; maxVersionChars: 64; }>; type DirectoryObservationParseFailure = { ok: false; /** Always `schema_invalid` today; typed so a caller switches, not guesses. */ reason: Extract; /** Names the offending field and what was wrong with it. */ detail: string; }; type DirectoryObservationParseResult = { ok: true; envelope: DirectoryObservationEnvelope; } | DirectoryObservationParseFailure; interface DirectoryObservationSchema { /** The publisher discriminator an envelope must carry to be accepted. */ readinessKind: string; /** The observation passes this publisher defines. */ observationKinds: readonly Kind[]; /** The frozen catalogue. Anything outside it is rejected. */ knownIds: readonly Id[]; /** The revision this SDK build understands. */ schemaVersion: string; } /** * Validate raw provider output into an envelope, or refuse it with a reason. * * TOTAL, never throwing. The caller is a run that must still finish: a bad * envelope degrades the observation axis and leaves every deterministic lane * exactly as it was, so throwing here would convert a cosmetic gap into a * failed readiness run. * * Validated in the order a reader would ask the questions: is this the right * publisher's envelope, does this build understand its schema, is it within * bounds, and only then, is every ID one we published. Reversing that order * would report "unknown observation id" for output that was never meant for * this publisher at all. */ declare function parseDirectoryObservationEnvelope(value: unknown, schema: DirectoryObservationSchema): DirectoryObservationParseResult; /** * What one catalogued observation ID becomes when the model reports it. * * The lane, class, title and citation are the SDK's, fixed at build time. The * model contributes a summary, a confidence and some references — the parts * that cannot change what the finding MEANS. */ interface DirectoryObservationMapping { id: Id; title: string; /** The publisher's experience lane. Never a dispositive one. */ lane: Lane; class: DirectoryObservationFindingClass; source: SourceRef; /** One sentence the reader can act on, when the observation is worth acting on. */ remediation?: string; } interface DirectoryObservationCatalog { /** * The one lane every mapped finding may land in. * * Named separately from each mapping's `lane` so the guard below has * something to check the mappings AGAINST. A catalogue whose entries all * agree with each other is not evidence they agree with the publisher. */ experienceLane: Lane; engineVersion: string; mappings: readonly DirectoryObservationMapping[]; } /** * Turn a validated envelope into findings. * * THE GUARD IS NOT DECORATIVE. Both invariants below are re-checked here, at * the moment the finding is built, rather than trusted from the catalogue's * type: catalogues are data, data gets edited, and the edit that moves an * observation into `directory-policy` as `required` is one character wide and * would hand a model a vote on a submitter's verdict. A mapping that breaks * either invariant is DROPPED — not downgraded — because a catalogue in that * state is a bug to fix, and silently rewriting it would hide the bug behind a * plausible-looking finding. * * Observations with no mapping are dropped for a different and less alarming * reason: a backend running a newer catalogue than this SDK build will send * IDs this build has no rendering for, and dropping them degrades gracefully * where throwing would fail the whole run over an unknown line item. (An ID * outside the schema's `knownIds` never reaches here — the parse refused it.) */ declare function mapObservationsToFindings(envelope: DirectoryObservationEnvelope | undefined, catalog: DirectoryObservationCatalog, stamp: DirectoryCheckStamp): DirectoryReadinessFinding[]; /** * The state a run reports when observations were never asked for. * * A constant rather than an inline literal at each call site, because the * default is the one value every free run publishes and a call site that spelt * it `{ status: "not-requested" }` with no reason would make the reason field * look optional in exactly the case a reader most wants it. * * PARAMETERISED `` so it satisfies every publisher's narrowed * state without a cast. Both parameters appear only inside `envelope`, which * this value does not carry, and `never` is assignable to any id union — so * the one constant fits a Claude result and an OpenAI one alike. Typed * `` it would fit neither, and each publisher would need its * own copy of a sentence that says the same thing. */ declare const NOT_REQUESTED_OBSERVATIONS: DirectoryObservationState; /** * Build the state for a failure, with the reason a surface branches on. * * Centralised so `billing-blocked` always arrives carrying * `billing_limit_reached` — the pairing three surfaces key on, and one a call * site could otherwise get half right. */ declare function observationFailure(status: Exclude, detail: string): DirectoryObservationState; /** * The verdict vocabulary shared by every conformance suite. * * The tasks suite arrived at this model first, after a run where six of eight * checks silently skipped and the suite still reported success. The rule it * encodes is the whole point: **"did not run" must never read as "conformed"**. * Protocol, apps and OAuth now speak the same vocabulary, so a caller — * inspector, CLI, or a score — can reason about any suite the same way. * * This module is pure data reasoning: no MCP client, no transport, no Node * built-ins, so it is safe from the browser entry. */ /** * Why a skipped check produced no verdict. The two are NOT interchangeable: * * - `"not-applicable"` — the requirement cannot apply to THIS server, so * nothing is left unverified. An era-gated check on the wrong protocol * version, a capability the server never advertised, a transport-specific * requirement on another transport. A run may still pass with these, and * a score must leave them out of its denominator entirely. * - `"could-not-run"` — the requirement DOES apply, but the run could not * exercise it: a missing probe tool, an unreachable prerequisite, an * opt-in the caller did not supply. The obligation is untested, so the * run cannot claim conformance. */ type ConformanceSkipReason = "not-applicable" | "could-not-run"; /** * A run's verdict. * * - `"passed"` — every selected check either ran and passed, or was * inapplicable to this server. * - `"failed"` — at least one check produced a violation. * - `"incomplete"` — nothing failed, but at least one selected check could * not be run, so the run does not establish conformance. */ type ConformanceRunOutcome = "passed" | "failed" | "incomplete"; /** The minimum a check must expose for the shared verdict logic to read it. */ interface OutcomeCheckLike { id: string; status: "passed" | "failed" | "skipped"; skipReason?: ConformanceSkipReason; error?: { message: string; }; } /** Selected, applicable, and never exercised. */ declare function isUnrunCheck(check: OutcomeCheckLike): boolean; /** Skipped because it could not apply here — nothing was left unverified. */ declare function isInapplicableCheck(check: OutcomeCheckLike): boolean; /** * The run's verdict, plus the reason when it is `incomplete`. * * `passed` requires that every SELECTED check actually produced a verdict — * either it ran, or it was inapplicable to this server. A check that could not * run is neither a violation nor a pass, and collapsing it into "not failed" * is what let a two-of-eight run report success. */ declare function decideConformanceOutcome(checks: OutcomeCheckLike[]): { outcome: ConformanceRunOutcome; incompleteReason?: string; }; /** The one-line tally every suite prints. */ declare function buildOutcomeSummary(checks: OutcomeCheckLike[]): string; /** * Whether an existing suite result may be reused as readiness evidence. * * WHY READINESS COMPOSES AT ALL. A conformance run has already dialled the * server, read its tools, fetched its widget resources and graded them. * Re-observing all of that inside a readiness run would double the traffic to * somebody else's server and — worse — let the two runs DISAGREE about it. The * first question anyone asks about a disagreement is which one to believe, and * there is no good answer. * * WHY IT IS GUARDED. Reuse is only honest when the earlier run looked at the * SAME THING. Three ways it silently is not: * * - a different target. Two servers in one project, one URL typed slightly * differently, a staging host: the result renders identically and grades * the wrong server. * - a different config. A suite run with a bearer token sees tools an * anonymous run does not, so its listing is not the listing a directory * reviewer would see. * - an incomplete source. A run that could not finish carries checks that * never ran, and adopting its evidence turns "nobody looked" into * "everything was fine". * * Every refusal here leaves the readiness lane exactly as it would have been * with no source at all: `not-evaluated`, with the input named. That is the * whole design — an incompatible source must degrade to MISSING, never to a * pass and never to a failure. * * Pure data reasoning. No transport. */ /** What a reused result was, and what it looked at. */ interface AttributableEvidenceSource { /** Suite kind, e.g. `"apps-conformance"`. Names the provenance in reports. */ kind: string; /** * The source run's own id, when the caller has one. * * Carried so a finding can say WHICH run it rests on. A readiness report * that cites "the apps suite" without saying which run is unauditable the * moment a second run exists. */ runId?: string; /** The target the source run graded, exactly as that run named it. */ target: string; /** * A digest of the configuration the source run used — headers, auth, * selected checks. Opaque here: this module compares it, it does not * compute it, because what belongs in a config fingerprint is the caller's * question and a shared guess would be wrong for every caller. */ configFingerprint?: string; /** Whether the source run itself finished everything it selected. */ complete: boolean; } /** What the readiness run needs the source to have looked at. */ interface EvidenceReuseExpectation { target: string; /** * The fingerprint the readiness run is grading under. * * When BOTH sides carry one they must match. When either side has none, the * comparison is skipped rather than failed: a caller that does not fingerprint * its configs is not thereby claiming they differ, and refusing every such * reuse would make the whole mechanism unusable for the callers most likely * to need it. */ configFingerprint?: string; } declare const EVIDENCE_REUSE_REFUSALS: readonly ["target_mismatch", "config_mismatch", "source_incomplete"]; type EvidenceReuseRefusal = (typeof EVIDENCE_REUSE_REFUSALS)[number]; type EvidenceReuse = { ok: true; evidence: Evidence; /** * The provenance string a finding's `derivedFrom` carries, e.g. * `"apps-conformance:run_123"`. */ sourceRef: string; } | { ok: false; refusal: EvidenceReuseRefusal; detail: string; }; /** * Compare two target URLs the way a reader would. * * Scheme, host, port and path decide; a trailing slash, a case difference in * the HOST, and the ORDER of query parameters do not. Query parameters * themselves are compared, because an MCP endpoint that keys on one is a * different endpoint. * * ONLY THE AUTHORITY IS CASE-FOLDED. Scheme and host are case-insensitive by * specification; a path and a query value are not, and folding them would * accept evidence gathered from `/MCP?tenant=AcmeCorp` as evidence about * `/mcp?tenant=acmecorp`. Those can be two different tenants on two different * endpoints, which is exactly the confusion this function exists to prevent. * * An unparseable URL falls back to a trimmed comparison rather than being * treated as a mismatch: two callers that both typed the same malformed thing * looked at the same thing. That fallback stays case-SENSITIVE for the same * reason as above — with no parse there is no authority to fold separately. */ declare function sameReadinessTarget(left: string, right: string): boolean; /** * Decide whether a source may be adapted, in the order a reader would ask. * * Target first: an answer about the wrong server is wrong whatever else is * true of it, and reporting "the source run was incomplete" for a result that * graded a different host sends the reader to fix the wrong thing. */ declare function checkEvidenceReuse(source: AttributableEvidenceSource, expectation: EvidenceReuseExpectation): { ok: true; sourceRef: string; } | { ok: false; refusal: EvidenceReuseRefusal; detail: string; }; /** * The policy corpus this product grades against, pinned. * * WHY A MANIFEST AT ALL. Anthropic's directory requirements are documentation, * and documentation moves. A readiness grade with no record of WHICH revision * it was made against does not become obviously wrong when the policy changes — * it becomes quietly wrong, which is worse, because a submitter keeps trusting * it. Every finding therefore carries a {@link ClaudePolicySourceRef} naming * the page, the section, and the snapshot it was graded against, so a stale * grade says so about itself. * * ON `revision`. Every entry ships `null` until the sync script * (`npm run claude-policy:sync`) has actually fetched the page, and that is * deliberate rather than unfinished: a hash is a claim that someone read those * exact bytes, and inventing one would make an unverified corpus look audited. * The script rewrites the GENERATED block below in place — rather than writing * a JSON file this module would then have to read — so the manifest stays pure * static data with no loader, no `fs`, and nothing to go stale between two * files. `npm run claude-policy:check` re-fetches and fails on drift: a changed * hash against an unchanged `snapshotDate` is the signal that the checks citing * that page need re-auditing before any grade made against them is trusted. * * ON THE URLs. They are reconstructed from the doc snapshot the check * inventory was written against (2026-08-19). {@link CLAUDE_DOCS_BASE_URL} is * a single constant precisely so a base that has since moved is one edit and * not fourteen, and the sync script reports any entry that no longer resolves * instead of silently hashing a 404 page. * * Pure data. Safe from the browser entry. */ /** The date the check inventory was written against this corpus. ISO date. */ declare const CLAUDE_POLICY_SNAPSHOT_DATE = "2026-08-19"; /** * Base for every documentation page below. Not inlined into the entries so * that a moved docs root is a one-line change; the sync script verifies it. */ declare const CLAUDE_DOCS_BASE_URL = "https://docs.claude.com/en/docs/mcp"; /** * The pages, by stable key. The key — not the URL — is what findings cite, so * a URL that moves does not invalidate every finding id that referenced it. */ declare const CLAUDE_POLICY_PAGES: readonly ["directory", "verification", "submission", "review-criteria", "authentication", "lazy-authentication", "enterprise-managed-auth", "troubleshooting", "mcp-apps/cross-compatibility", "mcp-apps/external-links", "mcp-apps/troubleshooting", "mcp-apps/design-guidelines"]; type ClaudePolicyPage = (typeof CLAUDE_POLICY_PAGES)[number]; interface ClaudePolicySourceEntry { page: ClaudePolicyPage; url: string; /** * Content hash of the page text at {@link snapshotDate}, or `null` when the * sync script has not run. Never fabricated — see the module docblock. */ revision: string | null; snapshotDate: string; } /** * A finding's citation: which page, and where on it. * * `section` is free text on purpose. Anchors churn faster than prose, and a * reader who is handed "the §Authentication → Lazy authentication heading" * can find it in a reorganised page; a dead `#anchor` helps nobody. */ interface ClaudePolicySourceRef { page: ClaudePolicyPage; section: string; url: string; revision: string | null; snapshotDate: string; } /** The manifest, keyed by page. Total over {@link CLAUDE_POLICY_PAGES}. */ declare const CLAUDE_POLICY_MANIFEST: Readonly>; /** * Build a finding's citation. * * Findings call this rather than composing a ref by hand, so a page's URL, * revision and snapshot date can only ever come from the manifest — the whole * point being that no finding can cite a source the manifest does not track. */ declare function claudePolicySource(page: ClaudePolicyPage, section: string): ClaudePolicySourceRef; /** * Whether this corpus has actually been snapshotted. * * Surfaces show it next to a grade: "graded against an unverified policy * snapshot" is a materially weaker claim than "graded against the corpus as of * 2026-08-19", and collapsing the two would misrepresent the product. */ declare function isPolicyCorpusVerified(): boolean; /** * The Claude directory-readiness result model. * * WHAT THIS IS NOT: a fifth scored MCP conformance suite. Anthropic's * connector-directory requirements are a PUBLISHER'S POLICY, not the MCP * specification, and the two answer different questions — "does this server * speak MCP correctly" versus "would Anthropic list it". Mixing them corrupts * both: a server can be flawless MCP and unlistable, or listed and sloppy. So * readiness reuses none of the suites' MUST/SHOULD vocabulary, carries no * conformance score, and is excluded from `pooledConformanceScore`. It * CONSUMES the protocol/oauth/apps/host-compat results as evidence rather than * re-running equivalent checks. * * WHAT IT IS: five independent lanes, each with its own semantics, plus * coverage reported separately from findings so an unevaluated requirement is * never mistaken for a satisfied one. `decideConformanceOutcome`'s rule — * "did not run" must never read as "conformed" — is the ancestor of the * `incomplete` status here. * * WHERE THE ALGEBRA LIVES. Everything below that is not specific to Anthropic * — what a finding is, how a lane decides, how coverage is tallied, how a * capability gate downgrades a verdict — now comes from * `../directory-readiness/`, which the OpenAI plugin-directory product shares. * The names, signatures and shapes exported here are UNCHANGED: this module is * the Anthropic-flavoured face of that algebra, and every alias below is * structurally identical to the interface it replaced, so no consumer and no * test had to move. * * Pure data: no MCP client, no transport, no Node built-ins. Safe from the * browser entry. */ /** * The engine that produced a finding, stamped onto every one of them. * * Bumped when a check's SEMANTICS change, not when the SDK version does: two * grades of the same target under the same policy snapshot should be * comparable, and tying this to the package version would make every unrelated * release look like a re-audit. */ declare const CLAUDE_READINESS_ENGINE_VERSION = "1"; /** * The five lanes. Each answers a different question and fails for different * reasons, so they are never collapsed into one verdict. */ declare const CLAUDE_READINESS_LANES: readonly ["runtime-compatibility", "directory-policy", "optional-features", "submission-artifacts", "experience-insights"]; type ClaudeReadinessLane = (typeof CLAUDE_READINESS_LANES)[number]; /** * What KIND of statement a finding is making. Deliberately not `MUST`/`SHOULD` * — those belong to the MCP spec, and reusing them here would let a policy * preference read as a protocol violation. * * - `required` — Anthropic states it as a submission/review requirement. * A violation means the connector will be rejected or delisted. * - `runtime-blocker` — Claude cannot complete the flow at all. Distinct * from `required` because it fails before policy is even reached: a broken * first `authorization_servers` entry is not a paperwork problem. * - `recommended` — stated guidance whose violation is not disqualifying. * - `experimental-feature` — a capability badge, not a grade. Absence is * never a defect. * - `manual-review` — a human has to look. Quality, ownership, and * credential validity cannot be decided from the wire. * - `heuristic` — a signal, not a verdict. Belongs to experience-insights * and may be confirmed by an LLM or a person; never fails a lane. */ declare const CLAUDE_FINDING_CLASSES: readonly ["required", "runtime-blocker", "recommended", "experimental-feature", "manual-review", "heuristic"]; type ClaudeFindingClass = DirectoryFindingClass; /** * The status of a lane whose findings are dispositive. * * - `ready` — every applicable requirement was evaluated and satisfied. * - `not-ready` — at least one applicable requirement was violated. * - `incomplete` — nothing was violated, but something the lane needs was * never evaluated. `missingInputs` says what the caller must supply. * * `incomplete` is load-bearing and self-describing: a wire-only run cannot see * a screenshot, and reporting `ready` for a lane it could not evaluate would * be the single most damaging thing this product could do. */ type ClaudeLaneStatus = DirectoryLaneStatus; /** * How a finding was established. A grade that cannot say where its evidence * came from is not auditable, and provenance is what stops a static lint from * being read as an observed runtime fact. * * - `wire` — observed in an HTTP/MCP exchange this run performed. * - `browser` — observed in a rendered widget/browser harness. * - `static` — read out of a document, manifest, or schema without dialing. * - `declared` — asserted by the submitter in a submission profile. Never * independently verified by this run. * - `manual` — recorded by a person. */ declare const CLAUDE_EVIDENCE_PROVENANCE: readonly ["wire", "browser", "static", "declared", "manual", "llm"]; type ClaudeEvidenceProvenance = DirectoryEvidenceProvenance; /** * How much a check DOES to the target. * * - `passive` — no request attributable to this check. * - `read-only` — requests with no persistent effect on the target. * - `side-effecting` — registers a client, spends a grant, mutates state. * Only ever reached through the explicit intrusive opt-in. */ declare const CLAUDE_INTRUSIVENESS_LEVELS: readonly ["passive", "read-only", "side-effecting"]; type ClaudeIntrusiveness = DirectoryIntrusiveness; /** * How the run authenticated. Recorded on the run so `incomplete` explains * itself: a headless run genuinely cannot complete an interactive consent * screen, and that is a property of the run, not a defect in the server. */ type ClaudeReadinessAuthMode = "headless" | "interactive" | "provided-token"; /** * A capability the RUNNER may or may not have. Surfaces differ — a CLI on a * laptop can resolve DNS and open a browser; a hosted node may refuse raw * origins; a Slack bot has neither — so two surfaces grading the same target * agree only on their SHARED capability subset. Asserting check-for-check * equality across surfaces would be asserting something false. */ declare const CLAUDE_RUNNER_CAPABILITIES: readonly ["dns", "raw-origin", "interactive-oauth", "browser", "webkit-browser", "intrusive-probes"]; type ClaudeRunnerCapability = (typeof CLAUDE_RUNNER_CAPABILITIES)[number]; /** A finding's verdict. `informational` carries no pass/fail meaning at all. */ type ClaudeFindingStatus = DirectoryFindingStatus; /** * One graded statement about the target. * * Every field below the verdict exists so the finding survives contact with * time: Anthropic's docs change, and a grade that cannot say WHICH revision it * was made against becomes silently wrong rather than visibly stale. */ type ClaudeReadinessFinding = DirectoryReadinessFinding; /** * What a lane managed to look at, reported SEPARATELY from what it found. * * A lane with zero violations and zero evaluated checks is not a pass, and the * only way to keep those apart is to publish the denominator. */ type ClaudeLaneCoverage = DirectoryLaneCoverage; type ClaudeReadinessLaneResult = DirectoryReadinessLaneResult; /** * A capability badge. Present in the optional-features lane only, and never a * defect when absent — that is the whole difference between a badge and a * requirement. */ type ClaudeCapabilityBadge = DirectoryCapabilityBadge; /** How the target was reached and what the runner could do while it was there. */ interface ClaudeReadinessRunContext { target: string; authMode: ClaudeReadinessAuthMode; capabilities: ClaudeRunnerCapability[]; /** Suite results consumed as evidence, by kind. */ evidenceSources: string[]; } interface ClaudeReadinessResult { /** * The one dispositive status: the REQUIRED lanes' rollup * (runtime-compatibility + directory-policy). Optional features and * experience insights can never move it, by construction. */ status: ClaudeLaneStatus; /** Human-readable rollup of `status`, naming what is missing when incomplete. */ summary: string; context: ClaudeReadinessRunContext; lanes: ClaudeReadinessLaneResult[]; findings: ClaudeReadinessFinding[]; badges: ClaudeCapabilityBadge[]; /** * The model-observation axis, ALWAYS present. * * Independent of {@link status} on purpose. A run whose required lanes * graded cleanly is `ready` even when the observation call was refused for * credit — a payment problem belongs to the account, not to the connector * under grading — and a run that could not afford to look must never render * as one that looked and found nothing. Optional in the TYPE only so * evidence gathered before this field existed still parses; the grader * always fills it, with `not-requested` when nobody asked. */ llmObservations?: DirectoryObservationState; /** Snapshot date of the policy corpus this run graded against (ISO date). */ policySnapshotDate: string; engineVersion: string; startedAt: string; durationMs: number; } /** Lanes whose status rolls up into {@link ClaudeReadinessResult.status}. */ declare const CLAUDE_REQUIRED_LANES: readonly ClaudeReadinessLane[]; /** * Roll the required lanes up. * * `not-ready` dominates `incomplete` dominates `ready`. The ordering is the * point: a run that found a violation AND could not evaluate something else is * `not-ready` — the violation is established, and softening it to `incomplete` * would let an unrelated coverage gap launder a real failure. * * Anthropic has exactly ONE required-lane set, so this keeps its single-argument * shape and closes over {@link CLAUDE_REQUIRED_LANES}. The shared rollup takes * the lane set as an argument because OpenAI's product grades two of them — * a technical preflight and a full submission-ready verdict — from one set of * findings. */ declare function rollUpLaneStatus(lanes: ClaudeReadinessLaneResult[]): ClaudeLaneStatus; /** * Whether a finding can DECIDE a lane. * * Exported because the report adapter needs the same answer: a finding that * decides a lane must render as a testcase, and one that does not must render * as an advisory. Two copies of this predicate could disagree, and then a * report would contradict the verdict it is reporting. */ declare function isDispositiveClaudeFinding(finding: Pick): boolean; /** * The Claude experience-observation catalogue. * * The Anthropic twin of `openai-readiness/observations`, and deliberately a * SEPARATE catalogue rather than a shared one with two lane maps. The two * directories review for different things — Anthropic's criteria dwell on * connector trust and what a user is consenting to, OpenAI's on tool selection * and listing copy — and a shared ID list would let a check cite the wrong * publisher's review criteria while typechecking perfectly. * * The invariants are the shared ones and hold identically here: the model * selects from a frozen list, contributes prose and a confidence and nothing * else, and every finding it produces lands in `experience-insights` as * `heuristic` or `manual-review` — a lane no rollup consults for a verdict. * * Pure data. No transport, no provider SDK. */ declare const CLAUDE_OBSERVATION_KINDS: readonly ["experience"]; type ClaudeObservationKind = (typeof CLAUDE_OBSERVATION_KINDS)[number]; /** See the OpenAI twin: an envelope stamped otherwise is refused, not adapted. */ declare const CLAUDE_OBSERVATION_SCHEMA_VERSION = "1"; /** Every observation ID the Claude pass may return. */ declare const CLAUDE_OBSERVATION_IDS: readonly ["claude.experience.tool-descriptions-uninformative", "claude.experience.tool-overlap", "claude.experience.connector-purpose-unclear", "claude.experience.consent-scope-unclear", "claude.experience.listing-copy-placeholder", "claude.experience.destructive-tool-underexplained", "claude.experience.app-copy-mismatched"]; type ClaudeObservationId = (typeof CLAUDE_OBSERVATION_IDS)[number]; declare const CLAUDE_OBSERVATION_SCHEMA: DirectoryObservationSchema; type ClaudeExperienceObservations = DirectoryObservationEnvelope; type ClaudeObservationState = DirectoryObservationState; declare const CLAUDE_OBSERVATION_CATALOG: DirectoryObservationCatalog; /** Validate raw provider output as this publisher's envelope, or say why not. */ declare function parseClaudeExperienceObservations(value: unknown): DirectoryObservationParseResult; /** Turn a validated envelope into experience-lane findings. */ declare function mapClaudeObservationsToFindings(envelope: ClaudeExperienceObservations | undefined, stamp: DirectoryCheckStamp): ClaudeReadinessFinding[]; /** * The OpenAI plugin-directory policy corpus this product grades against, * pinned. * * WHY A MANIFEST AT ALL. OpenAI's plugin-directory requirements are * documentation, and documentation moves. A readiness grade with no record of * WHICH revision it was made against does not become obviously wrong when the * policy changes — it becomes quietly wrong, which is worse, because a * submitter keeps trusting it. Every finding therefore carries an * {@link OpenAIPolicySourceRef} naming the page, the section, and the snapshot * it was graded against, so a stale grade says so about itself. * * WHAT THIS PRODUCT IS. A LOCAL PREFLIGHT that implements OpenAI's documented * rules. It is emphatically not a reproduction of the submission portal's * validator, and nothing here should be read as predicting a portal verdict: * the portal is authoritative, the submission-errors page is an error CATALOG * rather than the validator's source, and a preflight that claimed otherwise * would be making a promise it cannot keep. * * ON `revision`. Every entry ships `null` until the sync script * (`npm run openai-policy:sync`) has actually fetched the page, and that is * deliberate rather than unfinished: a hash is a claim that someone read those * exact bytes, and inventing one would make an unverified corpus look audited. * The script rewrites the GENERATED block below in place — rather than writing * a JSON file this module would then have to read — so the manifest stays pure * static data with no loader, no `fs`, and nothing to go stale between two * files. `npm run openai-policy:check` re-fetches and fails on drift. * * ON MARKDOWN. Unlike Anthropic's docs, every page under * {@link OPENAI_PLUGINS_DOCS_BASE_URL} is served with a `.md` twin, and * `llms.txt` is a machine-readable index of the whole set. The sync therefore * hashes the exact Markdown bytes — no HTML text extraction, so no dependence * on navigation chrome or a build hash — and separately diffs the live index * against {@link OPENAI_PLUGINS_POLICY_PAGES}. That second check catches the * failure a per-page hash cannot see: every pinned page byte-identical while * the publisher added a requirement on a page nobody cited. * * ON THE SECOND HOST. A few normative pages are help-centre articles with no * Markdown twin, so entries carry FULL URLs rather than a slug under one base. * A single base constant would have quietly excluded them from the corpus. * * Pure data. Safe from the browser entry. */ /** The date the check inventory was written against this corpus. ISO date. */ declare const OPENAI_POLICY_SNAPSHOT_DATE = "2026-08-19"; /** * Base for the plugins documentation. Not inlined into the entries so that a * moved docs root is a one-line change; the sync script verifies it. */ declare const OPENAI_PLUGINS_DOCS_BASE_URL = "https://developers.openai.com/plugins"; /** * The publisher's own index of the page set. * * Authoritative for "which pages exist", which is why the sync diffs it rather * than trusting {@link OPENAI_PLUGINS_POLICY_PAGES} to stay complete on its * own. A page added upstream is a policy change even when nothing we pinned * moved. */ declare const OPENAI_PLUGINS_LLMS_INDEX_URL = "https://developers.openai.com/plugins/llms.txt"; /** * The changelog, checked as a coarse SECOND drift signal. * * Cheap, and it fires in a case the per-page hashes miss entirely: a change * announced in the changelog before the reference pages catch up. It is not a * substitute for the page hashes and is never cited by a finding. */ declare const OPENAI_PLUGINS_CHANGELOG_URL = "https://developers.openai.com/plugins/changelog"; /** * Pages under {@link OPENAI_PLUGINS_DOCS_BASE_URL}, by slug. The slug — not the * URL — is what findings cite, so a URL that moves does not invalidate every * finding id that referenced it. * * The whole documented page set is pinned rather than only the pages a check * happens to cite today. Hashing is cheap, the set is small, and the * alternative — pin what we cite — means a requirement can appear on an * unpinned page and drift past every signal this module has. */ declare const OPENAI_PLUGINS_POLICY_PAGES: readonly ["quickstart", "app-guidelines", "reference", "changelog", "plan/use-case", "plan/tools", "concepts/plugins", "concepts/skills", "concepts/ui-guidelines", "build/plugins", "build/skills", "build/mcp-server", "build/chatgpt-ui", "build/auth", "build/monetization", "build/examples", "deploy/connect-chatgpt", "deploy/submission", "deploy/submission-errors", "deploy/troubleshooting", "guides/optimize-metadata", "guides/submit-claude-plugin", "guides/product-checkout-conversion-spec"]; /** * Normative pages that are NOT under the plugins base — help-centre articles * served as HTML with no Markdown twin. * * Carried as explicit `{page, url}` pairs because there is no shared base to * derive them from, and because a corpus that silently dropped them would grade * against a policy the submitter is still held to. */ declare const OPENAI_EXTERNAL_POLICY_PAGES: readonly [{ readonly page: "help/plugins-in-chatgpt-and-codex"; readonly url: "https://help.openai.com/en/articles/20001256-plugins-in-chatgpt-and-codex"; }, { readonly page: "help/submitting-apps-to-the-chatgpt-app-directory"; readonly url: "https://help.openai.com/en/articles/20001040-submitting-apps-to-the-chatgpt-app-directory"; }]; type OpenAIPluginsPolicyPage = (typeof OPENAI_PLUGINS_POLICY_PAGES)[number]; type OpenAIExternalPolicyPage = (typeof OPENAI_EXTERNAL_POLICY_PAGES)[number]["page"]; /** Every page in the corpus, whichever host serves it. */ type OpenAIPolicyPage = OpenAIPluginsPolicyPage | OpenAIExternalPolicyPage; declare const OPENAI_POLICY_PAGES: readonly OpenAIPolicyPage[]; /** How a page's revision is taken: over its Markdown twin, or its HTML text. */ type OpenAIPolicyPageFormat = "markdown" | "html"; interface OpenAIPolicySourceEntry { page: OpenAIPolicyPage; url: string; /** * The exact bytes the sync hashes. For the plugins corpus this is the `.md` * twin, which is why it is recorded rather than reconstructed at read time: * a check's citation should point a human at the rendered page, and the sync * at the machine-readable one. */ revisionUrl: string; format: OpenAIPolicyPageFormat; /** * Content hash at {@link snapshotDate}, or `null` when the sync script has * not run. Never fabricated — see the module docblock. */ revision: string | null; snapshotDate: string; } /** * A finding's citation: which page, and where on it. * * `section` is free text on purpose. Anchors churn faster than prose, and a * reader who is handed "the §Submission → Scan tools heading" can find it in a * reorganised page; a dead `#anchor` helps nobody. */ interface OpenAIPolicySourceRef { page: OpenAIPolicyPage; section: string; url: string; revision: string | null; snapshotDate: string; } /** The manifest, keyed by page. Total over {@link OPENAI_POLICY_PAGES}. */ declare const OPENAI_POLICY_MANIFEST: Readonly>; /** * Build a finding's citation. * * Findings call this rather than composing a ref by hand, so a page's URL, * revision and snapshot date can only ever come from the manifest — the whole * point being that no finding can cite a source the manifest does not track. */ declare function openaiPolicySource(page: OpenAIPolicyPage, section: string): OpenAIPolicySourceRef; /** * Whether this corpus has actually been snapshotted. * * Surfaces show it next to a grade: "graded against an unverified policy * snapshot" is a materially weaker claim than "graded against the corpus as of * 2026-08-19", and collapsing the two would misrepresent the product. */ declare function isOpenAIPolicyCorpusVerified(): boolean; /** * The OpenAI plugin-directory readiness result model. * * WHAT THIS IS. A LOCAL PREFLIGHT that implements OpenAI's documented rules for * the ChatGPT/Codex plugin directory. The submission portal remains the * authoritative validator; nothing here predicts its verdict, and the product * would be dishonest if it claimed to. What it can do is find, before anyone * uploads anything, the problems that are decidable from the package, the wire * and a declared submission profile. * * WHAT IT IS NOT. A conformance suite. The MCP specification and OpenAI's * listing policy answer different questions — "does this server speak MCP * correctly" versus "would OpenAI list it" — so this carries no conformance * score, is excluded from `pooledConformanceScore`, and CONSUMES the * protocol/oauth/apps suite results as evidence rather than re-running them. * * THE THREE STRATA, kept visibly apart because collapsing them is what turns a * readiness report into noise: * * - `runtime-blocker` — the protocol exchange fails; ChatGPT cannot use this * at all, before policy is even reached. * - `required` — OpenAI states it as a host or submission requirement. * - `manual-review` / `heuristic` — a human has to look, or a signal worth * surfacing. NEVER dispositive, however alarming it reads. * * Pure data: no MCP client, no transport, no Node built-ins. Safe from the * browser entry. */ /** * The engine that produced a finding, stamped onto every one of them. * * Versioned INDEPENDENTLY of `CLAUDE_READINESS_ENGINE_VERSION`: the two check * inventories move for unrelated reasons, and a shared counter would make every * Anthropic change look like an OpenAI re-audit. */ declare const OPENAI_READINESS_ENGINE_VERSION = "1"; /** * The seven lanes. Each answers a different question and fails for different * reasons, so they are never collapsed into one verdict. */ declare const OPENAI_READINESS_LANES: readonly ["runtime-compatibility", "directory-policy", "plugin-package", "release-contract", "optional-features", "submission-artifacts", "experience-insights"]; type OpenAIReadinessLane = (typeof OPENAI_READINESS_LANES)[number]; /** * The four public submission shapes. * * A REQUIRED grading input, never inferred from which inputs a run happens to * hold. Inference reads the absence of a bundle as "MCP-only" and then reports * the package lane `not-applicable` for a submitter who simply forgot to attach * their ZIP — turning a missing input into a clean bill of health, which is the * exact failure `incomplete` exists to prevent. Naming the mode makes a missing * input a GAP (`not-evaluated`, with the input named) and a genuinely absent * surface a `not-applicable`. * * - `skills-only` — a ZIP of skills, no server. * - `mcp-only` — an endpoint, no ZIP. * - `mcp-imported-skills` — an endpoint whose skills are read from the * `io.modelcontextprotocol/skills` extension at scan time, no ZIP. * - `mcp-uploaded-skills` — an endpoint plus a ZIP of skills. */ declare const OPENAI_SUBMISSION_MODES: readonly ["skills-only", "mcp-only", "mcp-imported-skills", "mcp-uploaded-skills"]; type OpenAISubmissionMode = (typeof OPENAI_SUBMISSION_MODES)[number]; /** Inputs a caller can supply, named so a coverage gap says how to close it. */ declare const OPENAI_READINESS_INPUTS: { readonly serverUrl: "serverUrl"; readonly pluginBundle: "pluginBundle"; readonly submissionProfile: "submissionProfile"; readonly draftSnapshot: "draftSnapshot"; readonly publishedSnapshot: "publishedSnapshot"; readonly toolListing: "toolListing"; readonly importedSkills: "importedSkills"; }; type OpenAIReadinessInputName = (typeof OPENAI_READINESS_INPUTS)[keyof typeof OPENAI_READINESS_INPUTS]; /** * What each mode's submission actually CONTAINS. * * The applicability matrix is derived from this rather than written out per * lane, so a fifth mode is one entry here instead of seven booleans that can * disagree with each other. */ declare const OPENAI_SUBMISSION_MODE_SHAPES: Readonly>; /** * Whether a lane can apply at all in this mode. * * `false` here means `not-applicable` — nothing was left unverified — and is * emphatically NOT `incomplete`. A skills-only submission has no endpoint to * grade, and reporting that as a coverage gap would send a submitter looking * for an input their submission shape does not have. */ declare function isLaneApplicableInMode(lane: OpenAIReadinessLane, mode: OpenAISubmissionMode): boolean; /** * The two staged rollups. * * WHY TWO. Submission artifacts — tests, attestations, release notes, domain * verification, scan currency — are DISPOSITIVE: a submission missing them is * not ready, and grading them as non-blocking suggestions would misrepresent * the directory. But a submitter running a quick technical check on their * server has supplied no submission profile, and failing that run on paperwork * they have not filled in yet would make the quick check useless. * * Two stages resolve it honestly instead of picking one and being wrong half * the time: a run with no profile is `ready` at `technical-preflight` and * `incomplete` at `submission-ready`, and both statements are true. * * - `technical-preflight` — is the thing technically fit to submit? * - `submission-ready` — is the submission complete? This is the headline. */ declare const OPENAI_READINESS_STAGES: readonly ["technical-preflight", "submission-ready"]; type OpenAIReadinessStage = (typeof OPENAI_READINESS_STAGES)[number]; /** The lanes each stage rolls up, before mode applicability is applied. */ declare const OPENAI_STAGE_LANES: Readonly>; /** The stage whose status becomes {@link OpenAIReadinessResult.status}. */ declare const OPENAI_HEADLINE_STAGE: OpenAIReadinessStage; /** * The lanes a stage actually rolls up for THIS run. * * Two things narrow the static set. A lane the mode excludes is dropped — * rolling up a `not-applicable` lane would make every skills-only submission * permanently incomplete on an endpoint it does not have. And * `release-contract` is dropped when there is no published version to compare * against, because a first submission has no contract to break. */ declare function stageLanesFor(stage: OpenAIReadinessStage, mode: OpenAISubmissionMode, options: { hasPublishedVersion: boolean; }): OpenAIReadinessLane[]; /** * A capability the RUNNER may or may not have. * * Anthropic's set minus `webkit-browser`: Claude's apps render in WebKit * specifically and a check there legitimately asks for it, while ChatGPT's do * not, so carrying the capability would let an OpenAI check request something * that means nothing for its host. */ declare const OPENAI_RUNNER_CAPABILITIES: readonly ["dns", "raw-origin", "interactive-oauth", "browser", "intrusive-probes"]; type OpenAIRunnerCapability = (typeof OPENAI_RUNNER_CAPABILITIES)[number]; /** How the run authenticated, recorded so `incomplete` explains itself. */ type OpenAIReadinessAuthMode = "headless" | "interactive" | "provided-token"; type OpenAIReadinessFinding = DirectoryReadinessFinding; type OpenAILaneCoverage = DirectoryLaneCoverage; type OpenAIReadinessLaneResult = DirectoryReadinessLaneResult; type OpenAILaneStatus = DirectoryLaneStatus; type OpenAICapabilityBadge = DirectoryCapabilityBadge; /** One stage's verdict, and the lanes it was computed from. */ interface OpenAIReadinessStageResult { stage: OpenAIReadinessStage; status: OpenAILaneStatus; /** Naming the lanes makes a stage's verdict reproducible by hand. */ lanes: OpenAIReadinessLane[]; summary: string; } /** How the target was reached and what the runner could do while it was there. */ interface OpenAIReadinessRunContext { /** The endpoint or package the run graded, as the caller named it. */ target: string; mode: OpenAISubmissionMode; authMode: OpenAIReadinessAuthMode; capabilities: OpenAIRunnerCapability[]; /** Suite results consumed as evidence, by kind. */ evidenceSources: string[]; } interface OpenAIReadinessResult { /** * Discriminator, present so a consumer never has to guess. * * Claude's readiness result carries lanes, findings and badges too, so its * structural shape matches this one exactly. Without an explicit kind, a * report adapter switching on shape would publish an OpenAI grade under * Anthropic's name. */ readinessKind: "openai-directory-readiness"; /** * The headline verdict: the {@link OPENAI_HEADLINE_STAGE} stage's status. * * Deliberately the STRICTER of the two stages. A submitter asking "am I * ready" is asking about the submission, and answering with the technical * preflight would report `ready` for a submission with no attestations. */ status: OpenAILaneStatus; /** Human-readable rollup, naming what is missing when incomplete. */ summary: string; context: OpenAIReadinessRunContext; /** Every stage, so the narrower verdict stays visible next to the headline. */ stages: OpenAIReadinessStageResult[]; lanes: OpenAIReadinessLaneResult[]; findings: OpenAIReadinessFinding[]; badges: OpenAICapabilityBadge[]; /** * The model-observation axis, ALWAYS present. * * Independent of {@link status} on purpose. A run whose deterministic lanes * graded cleanly is `ready` even when the observation call was refused for * credit — a payment problem belongs to the account, not to the server under * grading — and a run that could not afford to look must never render as one * that looked and found nothing. Optional in the TYPE only so evidence * gathered before this field existed still parses; the grader always fills * it, with `not-requested` when nobody asked. */ llmObservations?: DirectoryObservationState; /** Snapshot date of the policy corpus this run graded against (ISO date). */ policySnapshotDate: string; engineVersion: string; startedAt: string; durationMs: number; } /** Narrow an unknown result to this product's. Used by the report adapter. */ declare function isOpenAIReadinessResult(value: unknown): value is OpenAIReadinessResult; /** * The OpenAI experience-observation catalogue. * * WHAT A MODEL IS ALLOWED TO SAY ABOUT AN OPENAI SUBMISSION, exhaustively. * Every ID below is a question the deterministic checks provably cannot * answer — whether copy reads like a product or like a placeholder, whether * two tools overlap, whether a description tells the model when to reach for a * tool — paired with the finding it becomes. * * THE CATALOGUE IS THE SECURITY BOUNDARY. The provider returns IDs from this * list and prose to go with them; it cannot introduce an ID, so it cannot * introduce a rule. Everything that decides what a reader sees — the lane, the * class, the citation, the title — is here, in code, under review, and out of * the model's reach. A model that hallucinated `openai.tools.annotations` as a * violation would be refused at parse time, because that ID is not in this * list; and even an ID that IS in this list can only ever become a * `heuristic`/`manual-review` finding in `experience-insights`. * * Pure data. No transport, no provider SDK. */ /** * The observation passes defined for this publisher. * * One today. Named as a union rather than a bare string so adding a second * pass — a package-copy read, say — is a typed change that the backend broker, * the run record and this catalogue all have to agree on at once. */ declare const OPENAI_OBSERVATION_KINDS: readonly ["experience"]; type OpenAIObservationKind = (typeof OPENAI_OBSERVATION_KINDS)[number]; /** * The catalogue revision. * * Bumped whenever an ID is added, removed or changes meaning. An envelope * stamped with any other value is refused rather than best-efforted: the * version is what says which catalogue the producer was working from, and * grading a v2 envelope against a v1 catalogue would attribute the mismatch to * the model. */ declare const OPENAI_OBSERVATION_SCHEMA_VERSION = "1"; /** Every observation ID the OpenAI pass may return. */ declare const OPENAI_OBSERVATION_IDS: readonly ["openai.experience.tool-descriptions-uninformative", "openai.experience.tool-overlap", "openai.experience.naming-inconsistent", "openai.experience.listing-copy-placeholder", "openai.experience.listing-copy-overpromises", "openai.experience.skill-overlap", "openai.experience.destructive-tool-underexplained", "openai.experience.auth-story-unclear"]; type OpenAIObservationId = (typeof OPENAI_OBSERVATION_IDS)[number]; declare const OPENAI_OBSERVATION_SCHEMA: DirectoryObservationSchema; type OpenAIExperienceObservations = DirectoryObservationEnvelope; type OpenAIObservationState = DirectoryObservationState; declare const OPENAI_OBSERVATION_CATALOG: DirectoryObservationCatalog; /** Validate raw provider output as this publisher's envelope, or say why not. */ declare function parseOpenAIExperienceObservations(value: unknown): DirectoryObservationParseResult; /** * Turn a validated envelope into experience-lane findings. * * Bound to this publisher's catalogue so no caller can pass a different one — * the catalogue is the boundary, and a boundary a caller supplies is not one. */ declare function mapOpenAIObservationsToFindings(envelope: OpenAIExperienceObservations | undefined, stamp: DirectoryCheckStamp): OpenAIReadinessFinding[]; /** * The Claude host's own constants — the values a server has to match, as * opposed to the requirements it has to satisfy. * * Kept apart from the checks so that a Claude-side change (a new callback URL, * a different content host) is one edit to a named constant rather than a * grep through check bodies for a string literal. * * Pure data. Safe from the browser entry. */ /** * The redirect URIs Claude sends users back to. * * A server that allowlists redirect URIs must accept these EXACTLY, and the * list is versioned rather than pattern-matched: "any claude.ai URL" is not * what a conforming allowlist should contain, and a check that accepted a * pattern would pass a server that is about to fail in production. */ declare const CLAUDE_CALLBACK_URLS: readonly ["https://claude.ai/api/mcp/auth_callback", "https://claude.com/api/mcp/auth_callback"]; /** * Loopback redirect URIs compare with the PORT IGNORED. * * RFC 8252 §7.3: a native client's loopback redirect gets an ephemeral port, * so an authorization server that compares `http://127.0.0.1:49152/callback` * byte-for-byte against a registered `http://127.0.0.1/callback` rejects every * real attempt. This flag exists so the check states the rule it is applying * instead of hard-coding a port nobody can predict. */ declare const CLAUDE_LOOPBACK_REDIRECT_IGNORES_PORT = true; /** * The content host an MCP App's `ui.domain` must name when it sets one. * * The domain is derived, not chosen: `sha256()`, first 32 * hex characters, then this suffix. "Exact" is doing real work — a trailing * slash, a different scheme, or a normalised port produces a different digest * and therefore a domain Claude will not serve, which is why the check * compares against the URL as entered rather than a canonicalised one. */ declare const CLAUDE_APP_CONTENT_DOMAIN_SUFFIX = ".claudemcpcontent.com"; /** Hex characters of the digest that go into the label. */ declare const CLAUDE_APP_CONTENT_DOMAIN_HASH_LENGTH = 32; /** * The MIME profile a modern MCP App resource must declare. * * `text/html` alone is not it: the profile parameter is what tells the host * the payload is an app rather than a document, and a mismatch is a failure in * the modern-apps lane rather than a style note. */ declare const CLAUDE_APP_HTML_MIME = "text/html;profile=mcp-app"; /** * Latency budgets, in milliseconds. * * These are grading thresholds, not hard timeouts. Every check that uses them * samples and reports raw timings in its details: a shared CI node is a noisy * place to measure, and a single slow sample must never be the whole verdict. */ declare const CLAUDE_LATENCY_BUDGETS: { /** Time to a usable `tools/list` after connect. */ readonly toolListingMs: 5000; /** Time to first byte on the MCP endpoint. */ readonly handshakeMs: 3000; /** How long a widget may take to reach first paint. */ readonly widgetFirstPaintMs: 3000; }; /** * Minimum viewport width an MCP App must remain usable at, and the minimum * touch-target edge, from the design guidelines. */ declare const CLAUDE_APP_DESIGN_BUDGETS: { readonly minViewportWidthPx: 320; readonly minTouchTargetPx: 44; }; /** * Listing-field bounds from the submission form. Deterministic to check, which * is exactly why they belong to the directory-policy lane and not to a * heuristic one. */ declare const CLAUDE_SUBMISSION_LIMITS: { readonly nameMaxLength: 100; readonly taglineMaxLength: 55; readonly descriptionMaxLength: 2000; readonly categoriesMin: 1; readonly categoriesMax: 5; readonly screenshotsMin: 3; readonly screenshotsMax: 5; readonly screenshotMinWidthPx: 1000; /** MCP tool names Claude will accept. */ readonly toolNameMaxLength: 64; }; /** * The whole profile as one object, with its provenance attached, so a surface * can render "graded against Claude's published host profile, snapshot * 2026-08-19" without reassembling it from loose constants. */ declare const CLAUDE_HOST_PROFILE: { readonly callbackUrls: readonly ["https://claude.ai/api/mcp/auth_callback", "https://claude.com/api/mcp/auth_callback"]; readonly loopbackRedirectIgnoresPort: true; readonly appContentDomainSuffix: ".claudemcpcontent.com"; readonly appContentDomainHashLength: 32; readonly appHtmlMime: "text/html;profile=mcp-app"; readonly latencyBudgets: { /** Time to a usable `tools/list` after connect. */ readonly toolListingMs: 5000; /** Time to first byte on the MCP endpoint. */ readonly handshakeMs: 3000; /** How long a widget may take to reach first paint. */ readonly widgetFirstPaintMs: 3000; }; readonly appDesignBudgets: { readonly minViewportWidthPx: 320; readonly minTouchTargetPx: 44; }; readonly submissionLimits: { readonly nameMaxLength: 100; readonly taglineMaxLength: 55; readonly descriptionMaxLength: 2000; readonly categoriesMin: 1; readonly categoriesMax: 5; readonly screenshotsMin: 3; readonly screenshotsMax: 5; readonly screenshotMinWidthPx: 1000; /** MCP tool names Claude will accept. */ readonly toolNameMaxLength: 64; }; readonly source: ClaudePolicySourceRef; }; /** * The submission profile: what a submitter DECLARES, as opposed to what the * wire shows. * * WHY AN INPUT AT ALL. Half of Anthropic's directory requirements are about * artifacts a wire probe cannot see — a listing name, a tagline, screenshots, * a privacy policy URL, seven attestations. A runner with no access to those * has exactly two honest options: report the lane `incomplete` and name the * input it lacks, or say nothing. What it must never do is infer them. * `serverInfo.name` is NOT the listing name, and treating it as a proxy would * grade a field the submitter never filled in. * * WHAT THIS BUYS. With the profile supplied, presence, lengths, URL shapes and * image type/dimensions become DETERMINISTIC — pass or fail, no judgement. What * stays `manual-review` is everything the bytes cannot settle: whether a * screenshot shows the product, whether the submitter owns the domain, whether * an attestation is true. Provenance on those findings is `declared`, so no * reader can mistake "the submitter said so" for "we verified it". * * Pure schema. Safe from the browser entry. */ /** * How the connector authenticates, as DECLARED. A wire probe can often infer * this, but not always — a static-header credential and a custom connection * flow both look like "no OAuth" from outside — so the declaration is what * makes those cases classifiable instead of failures. */ declare const CLAUDE_DECLARED_AUTH_MODES: readonly ["oauth-dcr", "oauth-cimd", "oauth-preregistered", "static-header", "authless", "custom-connection"]; /** What the connector does with user data, as declared on the submission form. */ declare const CLAUDE_DATA_HANDLING_MODES: readonly ["no-user-data", "processes-user-data", "stores-user-data", "shares-with-third-parties"]; /** * The attestations the submission form requires. * * All seven are represented explicitly rather than as a count, so a profile * that is missing one names WHICH one. Every one of them is a claim about the * world that no probe can verify, which is why the checks over them are * presence checks and the truth of them stays `manual-review`. */ declare const CLAUDE_ATTESTATIONS: readonly ["ownsOrIsAuthorizedForService", "accurateDataHandlingDisclosure", "compliesWithUsagePolicies", "noProhibitedContent", "maintainsSecurityPractices", "respondsToSecurityReports", "keepsListingAccurate"]; declare const claudeSubmissionProfileSchema: z.ZodObject<{ name: z.ZodString; tagline: z.ZodString; description: z.ZodString; categories: z.ZodArray; slug: z.ZodString; documentationUrl: z.ZodString; privacyPolicyUrl: z.ZodString; supportUrl: z.ZodString; iconUrl: z.ZodString; declaredAuthMode: z.ZodEnum<{ "oauth-dcr": "oauth-dcr"; "oauth-cimd": "oauth-cimd"; "oauth-preregistered": "oauth-preregistered"; "static-header": "static-header"; authless: "authless"; "custom-connection": "custom-connection"; }>; dataHandling: z.ZodArray>; screenshots: z.ZodArray>; attestations: z.ZodRecord, z.ZodBoolean>; }, z.core.$strip>; type ClaudeSubmissionProfile = z.infer; type ClaudeDeclaredAuthMode = (typeof CLAUDE_DECLARED_AUTH_MODES)[number]; type ClaudeDataHandlingMode = (typeof CLAUDE_DATA_HANDLING_MODES)[number]; type ClaudeAttestation = (typeof CLAUDE_ATTESTATIONS)[number]; /** * A profile that failed validation, kept rather than discarded. * * A caller who supplied a malformed profile has NOT supplied no profile, and * reporting the lane as "no input" would hide their mistake behind a status * that reads like our limitation. The issues are surfaced as findings instead. */ interface ClaudeSubmissionProfileParse { profile?: ClaudeSubmissionProfile; issues: string[]; } declare function parseClaudeSubmissionProfile(input: unknown): ClaudeSubmissionProfileParse; /** * OpenAI's own constants — the values a submission has to match, as opposed to * the requirements it has to satisfy. * * Kept apart from the checks so that a host-side change (a new well-known path, * a different archive ceiling) is one edit to a named constant rather than a * grep through check bodies for a number. * * EVERY NUMERIC LIMIT IN THIS PRODUCT LIVES HERE, EXACTLY ONCE. The portal * error catalog in `portal-errors.ts` REFERENCES these rather than restating * them, and a unit test asserts that correspondence: two copies of "5000 * entries" is two places to update when it becomes 6000, and the copy that * gets missed is the one a submitter is graded against. * * Pure data. Safe from the browser entry. */ /** * The archive the submission portal validates. * * Two size ceilings, not one, and they measure different things: a ZIP is * rejected on the bytes UPLOADED and again on the bytes it expands to, because * a small archive that expands to gigabytes is the classic decompression bomb. * A check that only knew the compressed limit would pass one. */ declare const OPENAI_ARCHIVE_LIMITS: { /** Bytes of the uploaded `.zip` itself. */ readonly maxCompressedBytes: number; /** Bytes of everything inside it, summed. */ readonly maxUncompressedBytes: number; /** Entries in the archive, directories included. */ readonly maxEntries: 5000; }; /** * Where the plugin manifest may live inside the package. * * `.codex-plugin/` is canonical. The other two are accepted and NORMALISED — * recorded as an assumption on the evidence rather than silently rewritten, * because a submitter whose package works only after a normalisation we applied * has not been told the truth about their package. */ declare const OPENAI_MANIFEST_LOCATIONS: { readonly canonical: ".codex-plugin/plugin.json"; readonly accepted: readonly [".agent-plugin/plugin.json", ".claude-plugin/plugin.json"]; }; /** Where a skill's own metadata lives, relative to its skill directory. */ declare const OPENAI_SKILL_METADATA_PATH = "SKILL.md"; /** The plugin-scoped OpenAI interface/policy document, per skill and per plugin. */ declare const OPENAI_AGENT_METADATA_PATH = "agents/openai.yaml"; /** * Listing and interface field bounds. * * Deterministic to check, which is exactly why they belong to the * directory-policy lane and not to a heuristic one. */ declare const OPENAI_FIELD_LIMITS: { readonly nameMaxLength: 100; readonly displayNameMaxLength: 100; readonly shortDescriptionMaxLength: 255; readonly descriptionMaxLength: 2000; readonly defaultPromptMaxLength: 255; /** MCP tool names the host will accept. */ readonly toolNameMaxLength: 64; /** Skill names, which double as directory names inside the package. */ readonly skillNameMaxLength: 64; }; /** * The directory's category enum. * * A closed list rather than free text: a submission naming a category outside * it is rejected, so a check that accepted any string would pass a submission * the portal will not. */ declare const OPENAI_LISTING_CATEGORIES: readonly ["productivity", "education", "developer-tools", "design", "writing", "research", "data-analysis", "marketing", "sales", "finance", "health", "travel", "entertainment"]; type OpenAIListingCategory = (typeof OPENAI_LISTING_CATEGORIES)[number]; /** * Listing image constraints. * * Square is a hard requirement, not a recommendation — the directory crops * nothing — which is why the check compares width to height rather than to a * ratio tolerance. */ declare const OPENAI_IMAGE_CONSTRAINTS: { readonly minEdgePx: 48; readonly maxEdgePx: 4096; readonly mustBeSquare: true; readonly maxBytes: number; /** Raster formats the portal accepts, plus SVG under its own rules. */ readonly acceptedMimeTypes: readonly ["image/png", "image/jpeg", "image/webp", "image/svg+xml"]; }; /** * The backgrounds a brand colour is composited against. * * BOTH, not either: ChatGPT renders light and dark, and a colour that is * legible on one and invisible on the other fails for half the users. The * threshold is a CONTRAST RATIO, so the check needs the actual pair. */ declare const OPENAI_BRAND_COLOR_CONTRAST: { readonly lightBackground: "#FFFFFF"; readonly darkBackground: "#212121"; /** * WCAG relative-luminance contrast ratio. 2:1 rather than the 4.5:1 of body * text because a brand colour is an accent, not something anyone reads. */ readonly minRatio: 2; }; /** * Caps on skills imported from an MCP server's `io.modelcontextprotocol/skills` * extension. * * Four different byte ceilings because they bound four different things, and * collapsing them would let a submission pass one gate while failing the one * that actually applies to it. */ declare const OPENAI_MCP_SKILL_LIMITS: { /** One skill's `SKILL.md` body. */ readonly maxSkillMarkdownBytes: number; /** One supporting page fetched via `skills/get`. */ readonly maxPageBytes: number; /** One skill's total footprint, its pages included. */ readonly maxSkillTotalBytes: number; /** Every imported skill on the server, summed. */ readonly maxImportedTotalBytes: number; readonly maxSkills: 5; readonly maxPagesPerSkill: 10; }; /** * The MCP extension an importable skill set is advertised under, and the * methods that read it. */ declare const OPENAI_MCP_SKILLS_EXTENSION = "io.modelcontextprotocol/skills"; declare const OPENAI_MCP_SKILLS_METHODS: { readonly list: "skills/list"; readonly get: "skills/get"; }; /** * Domain verification. * * Served over HTTPS at a fixed path on the plugin's own origin; the portal * compares the body to a token it issued. A preflight can verify the path * responds and the token matches WHAT THE SUBMITTER SAID it should be — it * cannot verify the portal issued that token, which is why the finding's * provenance stays honest about the declared half. */ declare const OPENAI_DOMAIN_VERIFICATION_PATH = "/.well-known/openai-apps-challenge"; /** * The MIME profile a plugin UI resource must declare. * * `text/html` alone is not it: the profile parameter is what tells the host the * payload is an app rather than a document, and a mismatch is a rendering * failure rather than a style note. */ declare const OPENAI_APP_HTML_MIME = "text/html;profile=mcp-app"; /** The MCP endpoint path the host expects a public submission to serve. */ declare const OPENAI_EXPECTED_MCP_PATH = "/mcp"; /** * The tool annotation hints the directory requires on EVERY tool. * * All three, on every tool, with no default: an unannotated tool is not * "assumed safe", it is unreviewable, and the portal treats it that way. */ declare const OPENAI_REQUIRED_TOOL_ANNOTATIONS: readonly ["readOnlyHint", "destructiveHint", "openWorldHint"]; /** * Test cases the submission form collects. * * Five that must succeed and three that must fail gracefully. The failing three * are the point of the requirement — anyone can demo a happy path — so they are * counted separately rather than as one total of eight. */ declare const OPENAI_SUBMISSION_TEST_CASES: { readonly successCount: 5; readonly failureCount: 3; }; /** * Version and snapshot semantics from the app-review lifecycle. * * These are not limits but RULES, and they are constants because * `release-contract` grades a diff against them: which changes force a rescan * and a fresh review, which force a new listing entirely, and which are allowed * to go live against an already-published version. */ declare const OPENAI_RELEASE_RULES: { /** * Changing the MCP server's ORIGIN — scheme, host or port — is a new plugin, * not a new version. A path change is an ordinary version bump. */ readonly originChangeRequiresNewPlugin: true; /** UI content served from an unchanged URI may change without a review. */ readonly uiContentChangesStayLive: true; /** How long the host may serve a cached UI resource. */ readonly uiContentCacheSeconds: 3600; }; /** * The whole profile as one object, with its provenance attached, so a surface * can render "graded against OpenAI's published plugin profile, snapshot * 2026-08-19" without reassembling it from loose constants. */ declare const OPENAI_HOST_PROFILE: { readonly archiveLimits: { /** Bytes of the uploaded `.zip` itself. */ readonly maxCompressedBytes: number; /** Bytes of everything inside it, summed. */ readonly maxUncompressedBytes: number; /** Entries in the archive, directories included. */ readonly maxEntries: 5000; }; readonly manifestLocations: { readonly canonical: ".codex-plugin/plugin.json"; readonly accepted: readonly [".agent-plugin/plugin.json", ".claude-plugin/plugin.json"]; }; readonly fieldLimits: { readonly nameMaxLength: 100; readonly displayNameMaxLength: 100; readonly shortDescriptionMaxLength: 255; readonly descriptionMaxLength: 2000; readonly defaultPromptMaxLength: 255; /** MCP tool names the host will accept. */ readonly toolNameMaxLength: 64; /** Skill names, which double as directory names inside the package. */ readonly skillNameMaxLength: 64; }; readonly listingCategories: readonly ["productivity", "education", "developer-tools", "design", "writing", "research", "data-analysis", "marketing", "sales", "finance", "health", "travel", "entertainment"]; readonly imageConstraints: { readonly minEdgePx: 48; readonly maxEdgePx: 4096; readonly mustBeSquare: true; readonly maxBytes: number; /** Raster formats the portal accepts, plus SVG under its own rules. */ readonly acceptedMimeTypes: readonly ["image/png", "image/jpeg", "image/webp", "image/svg+xml"]; }; readonly brandColorContrast: { readonly lightBackground: "#FFFFFF"; readonly darkBackground: "#212121"; /** * WCAG relative-luminance contrast ratio. 2:1 rather than the 4.5:1 of body * text because a brand colour is an accent, not something anyone reads. */ readonly minRatio: 2; }; readonly mcpSkillLimits: { /** One skill's `SKILL.md` body. */ readonly maxSkillMarkdownBytes: number; /** One supporting page fetched via `skills/get`. */ readonly maxPageBytes: number; /** One skill's total footprint, its pages included. */ readonly maxSkillTotalBytes: number; /** Every imported skill on the server, summed. */ readonly maxImportedTotalBytes: number; readonly maxSkills: 5; readonly maxPagesPerSkill: 10; }; readonly domainVerificationPath: "/.well-known/openai-apps-challenge"; readonly appHtmlMime: "text/html;profile=mcp-app"; readonly expectedMcpPath: "/mcp"; readonly requiredToolAnnotations: readonly ["readOnlyHint", "destructiveHint", "openWorldHint"]; readonly submissionTestCases: { readonly successCount: 5; readonly failureCount: 3; }; readonly releaseRules: { /** * Changing the MCP server's ORIGIN — scheme, host or port — is a new plugin, * not a new version. A path change is an ordinary version bump. */ readonly originChangeRequiresNewPlugin: true; /** UI content served from an unchanged URI may change without a review. */ readonly uiContentChangesStayLive: true; /** How long the host may serve a cached UI resource. */ readonly uiContentCacheSeconds: 3600; }; readonly source: OpenAIPolicySourceRef; }; /** * The submission portal's error catalog, typed. * * WHAT THIS IS. A transcription of the documented submission errors: one entry * per code, carrying the category it belongs to, whether it blocks a * submission, the limit it is enforcing (as a REFERENCE to `profile.ts`, never * a restated number), and where in the docs it is defined. Checks reference * catalog entries; findings GROUP them for presentation only, in * `details.portalIssues`. That split is the point: a reader wants five readable * findings, and an auditor wants every documented code preserved with nothing * collapsed away. * * WHAT THIS IS NOT. The portal's validator, or a prediction of its verdict. * The submission-errors page is an error CATALOG — the list of things the * portal can say — not the source the portal validates from. This module lets a * local preflight speak the portal's vocabulary when it finds a problem it can * see for itself. It cannot tell anyone their submission will pass, and no * finding built on it should imply otherwise. * * ON COMPLETENESS. The catalog is a transcription, so it is only as complete as * the page it was transcribed from at {@link OPENAI_POLICY_SNAPSHOT_DATE}. The * policy-drift job is what makes that honest over time: when * `deploy/submission-errors` moves, the run fails and this file is re-read. * * Pure data. Safe from the browser entry. */ /** * The families of check the portal runs. * * Grouping exists so a finding can say "your archive has four problems" rather * than raising four findings; it carries no grading weight of its own. */ declare const OPENAI_PORTAL_ERROR_CATEGORIES: readonly ["package-archive", "package-manifest", "package-skills", "package-assets", "listing-fields", "mcp-server", "mcp-skills", "review-materials", "exclusions"]; type OpenAIPortalErrorCategory = (typeof OPENAI_PORTAL_ERROR_CATEGORIES)[number]; /** * Whether the portal treats the code as blocking. * * `blocking` stops the submission; `advisory` is surfaced without stopping it. * The distinction is the portal's, not ours — a preflight that promoted an * advisory to a blocker would send a submitter to fix something nobody asked * them to fix. */ type OpenAIPortalErrorSeverity = "blocking" | "advisory"; interface OpenAIPortalErrorDefinition { /** Stable id used in `details.portalIssues` and in check evidence. */ id: string; category: OpenAIPortalErrorCategory; severity: OpenAIPortalErrorSeverity; /** One line, in the portal's own terms, describing what tripped. */ message: string; /** * The constant this code enforces, when it enforces one. * * A REFERENCE, so the catalog and the profile cannot disagree: the unit test * over this module asserts every numeric limit named here is the identical * value exported from `profile.ts`, and that no limit is restated as a * literal. */ limit?: { name: string; value: number; }; /** Where the requirement is stated. */ source: OpenAIPolicySourceRef; } /** * Every documented submission error, by id. * * Deliberately a flat array rather than a nested tree: presentation groups * these, and a tree here would bake one grouping into the data and make a * second one impossible. */ declare const OPENAI_PORTAL_ERRORS: readonly OpenAIPortalErrorDefinition[]; /** The catalog keyed by id, for a check that already knows which code it means. */ declare const OPENAI_PORTAL_ERRORS_BY_ID: Readonly>; type OpenAIPortalErrorId = string; /** * One occurrence of a catalog entry against a specific target. * * The reader's unit of evidence: which code, where it tripped, and what was * observed. Findings carry arrays of these in `details.portalIssues` so a * grouped finding never loses a single documented code. */ interface OpenAIPortalIssue { id: OpenAIPortalErrorId; category: OpenAIPortalErrorCategory; severity: OpenAIPortalErrorSeverity; message: string; /** Package path, tool name, field name — whatever the code is about. */ subject?: string; /** What was actually seen, next to what was required. */ observed?: string | number; expected?: string | number; } /** * Build an issue from a catalog id. * * Throws on an unknown id rather than fabricating an entry: a check that raises * a code the catalog does not define is a bug in the check, and the loudest * possible failure is the cheapest one to fix. It also keeps the invariant that * every `portalIssues` entry corresponds to a documented code. */ declare function openaiPortalIssue(id: OpenAIPortalErrorId, details?: Pick): OpenAIPortalIssue; /** Whether any of these issues is one the portal treats as blocking. */ declare function hasBlockingPortalIssue(issues: readonly OpenAIPortalIssue[]): boolean; /** Group issues by category, for a finding that reports one family at a time. */ declare function groupPortalIssues(issues: readonly OpenAIPortalIssue[]): Map; /** * The submission profile: what a submitter DECLARES, as opposed to what the * wire or the package shows. * * WHY AN INPUT AT ALL. Most of what the portal collects is invisible to a * probe — a listing description, eight test cases, a demo recording, a set of * attestations, a country list. A runner with no access to those has exactly * two honest options: report the lane `incomplete` and name the input it lacks, * or say nothing. What it must never do is infer them. A server's advertised * `serverInfo.name` is not a listing name, and treating it as a proxy would * grade a field the submitter never filled in. * * WHAT NEVER GOES IN HERE. Secrets. `demoCredentials` records that reviewer * credentials EXIST and how they are delivered — never a username, never a * password, never a token. A readiness profile is a document people paste into * issues and CI logs, and a schema that accepted a password would eventually be * handed one. * * WHAT THIS BUYS. With the profile supplied, presence, counts, lengths and URL * shapes become DETERMINISTIC — pass or fail, no judgement. What stays * `manual-review` is everything the bytes cannot settle: whether a test case * really fails gracefully, whether the submitter owns the domain, whether an * attestation is true. Provenance on those findings is `declared`, so no reader * can mistake "the submitter said so" for "we verified it". * * Pure schema. Safe from the browser entry. */ /** * The attestations the submission form requires. * * Represented explicitly rather than as a count, so a profile missing one names * WHICH one. Every one is a claim about the world that no probe can verify, * which is why the checks over them are presence checks and their truth stays * `manual-review`. */ declare const OPENAI_ATTESTATIONS: readonly ["ownsOrIsAuthorizedForService", "accurateDataDisclosure", "compliesWithUsagePolicies", "noProhibitedContent", "maintainsSecurityPractices", "respondsToSecurityReports", "keepsListingAccurate"]; type OpenAIAttestation = (typeof OPENAI_ATTESTATIONS)[number]; /** What the plugin does with user data, as declared on the submission form. */ declare const OPENAI_DATA_TYPES: readonly ["none", "account-identifiers", "user-content", "usage-analytics", "payment-information", "location", "health", "shared-with-third-parties"]; type OpenAIDataType = (typeof OPENAI_DATA_TYPES)[number]; /** How reviewer demo access is delivered. Never the credential itself. */ declare const OPENAI_DEMO_CREDENTIAL_DELIVERY: readonly ["in-submission-form", "shared-out-of-band", "not-required-authless"]; declare const openaiSubmissionProfileSchema: z.ZodObject<{ name: z.ZodString; shortDescription: z.ZodString; description: z.ZodString; categories: z.ZodArray>; privacyPolicyUrl: z.ZodString; supportUrl: z.ZodString; documentationUrl: z.ZodOptional; testCases: z.ZodObject<{ successful: z.ZodArray>; gracefulFailure: z.ZodArray>; }, z.core.$strip>; demoCredentials: z.ZodObject<{ provided: z.ZodBoolean; delivery: z.ZodEnum<{ "in-submission-form": "in-submission-form"; "shared-out-of-band": "shared-out-of-band"; "not-required-authless": "not-required-authless"; }>; }, z.core.$strip>; demoRecordingProvided: z.ZodBoolean; screenshots: z.ZodDefault>>; releaseNotes: z.ZodOptional; identityVerified: z.ZodBoolean; accountPermissions: z.ZodDefault>; availableCountries: z.ZodDefault>; dataResidencyEligible: z.ZodOptional; privacyPolicyDataTypes: z.ZodDefault>>; annotationJustifications: z.ZodDefault>; frameDomainExplanations: z.ZodDefault>; domainVerificationToken: z.ZodOptional; lastScanAt: z.ZodOptional; hasPublishedVersion: z.ZodOptional; attestations: z.ZodRecord & z.core.$partial, z.ZodBoolean>; }, z.core.$strip>; type OpenAISubmissionProfile = z.infer; /** * A profile that failed validation, kept rather than discarded. * * A caller who supplied a malformed profile has NOT supplied no profile, and * reporting the lane as "no input" would hide their mistake behind a status * that reads like our limitation. The issues surface as findings instead. */ interface OpenAISubmissionProfileParse { profile?: OpenAISubmissionProfile; issues: string[]; } declare function parseOpenAISubmissionProfile(input: unknown): OpenAISubmissionProfileParse; /** The counts the test-case requirement is actually about. */ declare function summarizeTestCases(profile: OpenAISubmissionProfile): { successful: number; gracefulFailure: number; meetsSuccessMinimum: boolean; meetsFailureMinimum: boolean; }; /** * Brand-colour validation: the hex shape, and the contrast rule. * * The contrast requirement is the interesting half. A brand colour is * composited against BOTH of ChatGPT's backgrounds, so a colour that is legible * on white and invisible on the dark surface fails for half the users — which * is why the check computes two ratios and takes the worse one rather than * picking a background. * * Pure. Safe from the browser entry. */ interface RgbColor { r: number; g: number; b: number; } /** * Parse a six-digit hex colour. * * Six digits only. The three-digit shorthand is valid CSS, and accepting it * here would pass a value the portal rejects — a preflight that is more lenient * than the thing it previews is worse than no preflight, because it sends a * submitter to upload something that will bounce. */ declare function parseHexColor(value: string): RgbColor | undefined; /** WCAG relative luminance: sRGB channels linearised, then weighted. */ declare function relativeLuminance(color: RgbColor): number; /** WCAG contrast ratio. Order-independent, between 1 and 21. */ declare function contrastRatio(a: RgbColor, b: RgbColor): number; interface BrandColorCheck { /** `false` when the value is not a six-digit hex colour at all. */ parsed: boolean; /** Ratio against the light background, when parsed. */ lightRatio?: number; /** Ratio against the dark background, when parsed. */ darkRatio?: number; /** The worse of the two — the one the requirement is actually about. */ worstRatio?: number; /** Whether the worse ratio clears the minimum. */ passes: boolean; } /** * Grade a brand colour against both backgrounds. * * Reports both ratios rather than only the verdict, because "1.4:1 on dark" * tells a designer which direction to move the colour and "fails" does not. */ declare function checkBrandColor(value: string): BrandColorCheck; /** * Image dimensions, read from the bytes. * * WHY HAND-DECODED. The listing rules are about pixels — a minimum edge, a * maximum edge, and squareness — and there is no way to check them without * knowing the dimensions. Every library that reads them is either Node-only or * a native binding, and this module has to run in a browser bundle: the * inspector validates a package the user dropped into a page, before anything * is uploaded. So the four accepted formats are decoded directly. Each is a * fixed-offset header read, which is why this is short rather than a parser. * * WHY IT REFUSES RATHER THAN GUESSES. Every decoder below returns `undefined` * with a REASON when the header is not there, and the reason becomes a * `not-evaluated` finding. A decoder that fell back to a plausible default * would produce a dimension check that passed on a file whose dimensions were * never read — the readiness equivalent of grading a page nobody opened. * * Pure. No `fs`, no `Buffer`, no DOM: `Uint8Array` in, dimensions out. Safe * from the browser entry. */ /** * The XML surface an SVG dimension read needs — two properties, no more. * * Deliberately structural rather than a DOM type. The whole point of injecting * a parser is that `@xmldom/xmldom` must never appear in the browser entry's * import graph (the SDK guards that: it is Node-only credential machinery that * drags crypto into a client bundle), while a browser already ships a `DOMParser` * that does the job natively. Naming the two properties this module reads keeps * both implementations honest and neither of them imported here. */ interface XmlElementLike { nodeName: string; getAttribute(name: string): string | null; } interface XmlDocumentLike { documentElement: XmlElementLike | null | undefined; } /** * Parse XML into something with a root element, or say why not. * * Returns a RESULT rather than throwing, and rather than returning a * half-built tree: "not well-formed" is one of the portal's own SVG rejections, * so it has to be data this module can report, not an exception a caller may or * may not catch. */ type XmlParseResult = { ok: true; document: XmlDocumentLike; } | { ok: false; reason: string; }; type XmlParseFn = (source: string) => XmlParseResult; interface ReadImageDimensionsOptions { /** * How to parse an SVG. Defaults to the platform `DOMParser`, which every * browser has and Node does not — so a Node caller that wants SVG support * passes `xmldomParseXml` from the Node entry. Absent both, an SVG is * REFUSED with a reason naming the missing parser, never graded as malformed: * a runtime with no XML parser is our limitation, not the submitter's defect. */ parseXml?: XmlParseFn; } interface ImageDimensions { widthPx: number; heightPx: number; /** The format the bytes were actually recognised as, not the declared MIME. */ format: "png" | "jpeg" | "webp" | "svg"; } type ImageDimensionsResult = { ok: true; dimensions: ImageDimensions; } | { ok: false; reason: string; }; /** * Read an image's dimensions from its bytes. * * The DECLARED MIME type is deliberately not consulted. A submitter who names * a `.png` that is really a JPEG has a problem the portal will find, and * trusting the label would make this decoder report "truncated PNG" for a * perfectly good JPEG — sending them to fix the wrong thing. */ declare function readImageDimensions(bytes: Uint8Array, options?: ReadImageDimensionsOptions): ImageDimensionsResult; /** The MIME type these bytes actually are, for contradicting a declared one. */ declare function sniffImageMimeType(bytes: Uint8Array, options?: ReadImageDimensionsOptions): string | undefined; /** * The character rules listing text has to satisfy. * * WHY THIS IS NOT "STRIP THE WEIRD CHARACTERS". A preflight that sanitised * would report a pass on text the portal rejects, because the portal validates * what was UPLOADED. So this only ever reports; the caller decides. * * WHY THE SEPARATORS ARE HERE AT ALL. U+2028 and U+2029 are the ones nobody * expects. They survive a copy-paste out of a design tool, they are invisible * in every editor, and they terminate a line in a JavaScript string literal — * which is exactly why a listing field carrying one breaks somewhere far away * from the field. * * Pure. Safe from the browser entry. */ /** One offending character, located so a submitter can find it. */ interface UnsupportedCharacter { /** Code point, as `U+XXXX`. */ codePoint: string; /** Index into the string, in code units. */ index: number; kind: "control" | "line-separator" | "paragraph-separator"; } /** Every unsupported character in `value`, in order. */ declare function findUnsupportedCharacters(value: string): UnsupportedCharacter[]; /** Whether `value` is free of unsupported characters. */ declare function isSupportedText(value: string): boolean; /** * Whether a string's leading or trailing whitespace would be trimmed. * * Reported rather than trimmed, for the same reason nothing here sanitises: the * portal compares what was uploaded, and a submitter whose name is * `"My Plugin "` should be told, not quietly fixed. */ declare function hasSurroundingWhitespace(value: string): boolean; /** * `agents/openai.yaml` — the plugin's OpenAI-facing interface and policy * document. * * WHY THE REAL YAML PARSER. The `yaml` dependency is already here, and the * flat-frontmatter subset parser that `plugin-bundle` uses is deliberately not * enough for this file: `dependencies.tools` is a list of maps, `policy.products` * is a sequence, and a subset parser handed a nested document does not fail — * it returns a shape that is missing things, which grades as "field absent" * when the truth is "we could not read it". Those two must never look alike. * * WHY IT REPORTS INSTEAD OF THROWING. A malformed document is a FINDING, not an * exception: the submitter did the work and got it wrong, and an exception here * would either crash a grading run or get swallowed into "no metadata", which * reads as our limitation rather than their mistake. * * Pure. Safe from the browser entry. */ /** One problem with the document, in terms the submitter can act on. */ interface OpenAIAgentMetadataIssue { /** Dotted path, e.g. `interface.display_name`. `(root)` for the document. */ path: string; message: string; } /** A declared MCP dependency, as written. */ interface OpenAIAgentToolDependency { type?: string; value?: string; transport?: string; url?: string; } interface OpenAIAgentInterface { displayName?: string; shortDescription?: string; iconSmall?: string; iconLarge?: string; brandColor?: string; defaultPrompt?: string; } interface OpenAIAgentPolicy { products?: string[]; allowImplicitInvocation?: boolean; } interface OpenAIAgentMetadata { interface: OpenAIAgentInterface; policy: OpenAIAgentPolicy; dependencies: { tools: OpenAIAgentToolDependency[]; }; } interface OpenAIAgentMetadataParse { /** Absent only when the document could not be read as a YAML mapping. */ metadata?: OpenAIAgentMetadata; issues: OpenAIAgentMetadataIssue[]; } /** * Parse and validate the document. * * Field NAMES are snake_case on the wire and camelCase in the DTO. The mapping * is explicit rather than a generic transform so an unrecognised key stays * unrecognised: a document with `displayName` instead of `display_name` has a * missing field, and a helpful transformer would silently accept a spelling the * portal does not. */ declare function parseOpenAIAgentMetadata(source: string): OpenAIAgentMetadataParse; /** * Cross-check the declared MCP dependencies against the servers the package * actually declares. * * Both directions are reported, because they are different mistakes: a * dependency naming a server that does not exist is a broken reference, and a * declared server no dependency names is a server the host will never be told * to connect to. Reporting only the first would leave the second invisible * until a reviewer noticed the plugin does nothing. */ declare function crossCheckToolDependencies(tools: readonly OpenAIAgentToolDependency[], declaredServerNames: readonly string[]): OpenAIAgentMetadataIssue[]; /** * Read an OpenAI plugin package into gradeable evidence. * * WHY NOT `parsePluginBundle`. The Agent Plugins parser next door reads the * same directory layout, and reusing it would have been the obvious move. It is * the wrong one for two independent reasons. Its issue codes and limits are a * PERSISTED backend contract — the Convex import path stores them — so bending * them toward OpenAI's rules would change data that is already written down. * And it is spec-strict in ways OpenAI is not: it rejects `.codex-plugin/` * outright, which is the location OpenAI documents as canonical. So this reader * borrows the parser's PRIMITIVES (the file source, path normalisation, the * frontmatter splitter, the hashers) and none of its policy. * * WHY RAW ENTRY NAMES COME FIRST. This is the subtle one. The portal rejects a * backslash separator, an empty or `.` path segment, outer whitespace, and any * `..` — and `normalizeBundlePath` REPAIRS the first three: it maps `\` to `/`, * drops empty and `.` segments, and NFC-folds the result. Running the portal's * path rules against normalised paths would therefore pass every archive whose * paths the portal is about to reject, because the evidence would no longer * contain the thing being rejected. Raw names are checked before anything * touches them; normalisation happens afterwards, for identity and reads. * * WHAT IT DOES NOT DO. Grade. Nothing here produces a finding or decides a * lane: it returns observations plus a list of typed portal issues, and the * check module turns those into findings. Keeping the two apart is what lets * the same evidence be graded under different submission modes. * * Pure aside from the caller's `PluginFileSource`. Safe from the browser entry. */ /** * Facts about the ARCHIVE that no file source can report. * * A `PluginFileSource` abstracts over "a ZIP" and "a folder on disk", and the * abstraction is exactly right for reading content — but compressed size, * encryption flags and the pre-normalisation entry names only exist for an * archive. A folder source genuinely has none of them. * * Every field is optional, and an ABSENT field means "not observed" rather than * "fine". The reader records a `notEvaluated` entry for each one it lacks, so a * check can report `not-evaluated` with a real reason instead of quietly * passing a limit nobody measured. */ interface OpenAIArchiveObservations { /** Bytes of the uploaded `.zip`, before expansion. */ compressedBytes?: number; /** Entries the archive marks encrypted; the portal cannot scan them. */ encryptedEntryPaths?: string[]; /** * Entry names EXACTLY as the central directory records them. * * The whole reason this field exists — see the module docblock. */ rawEntryNames?: string[]; } interface ReadOpenAIPluginPackageOptions { archive?: OpenAIArchiveObservations; /** * How to parse an SVG, for runtimes with no `DOMParser`. * * A browser has one natively and needs nothing here. Node does not, so a Node * caller passes `xmldomParseXml` from the Node entry — kept an argument * rather than an import so `@xmldom/xmldom` never enters the browser entry's * graph. Without it, SVG assets are recorded as a GAP rather than graded. */ parseXml?: XmlParseFn; } /** Where the manifest was found, and whether that required an assumption. */ interface OpenAIManifestLocation { path: string; canonical: boolean; /** * Set when the manifest was read from an accepted-but-not-canonical * directory. Recorded rather than silently accepted: a submitter whose * package works only because we normalised it has not been told the truth * about their package. */ normalizedFrom?: string; } interface OpenAIPackageManifest { location: OpenAIManifestLocation; /** The parsed document, or `undefined` when it was not valid JSON. */ raw?: Record; name?: string; version?: string; description?: string; /** Server names declared by the manifest or `.mcp.json`. */ mcpServerNames: string[]; } interface OpenAIPackageSkill { /** Canonical bundle-relative directory, e.g. `skills/weather`. */ directory: string; directoryName: string; skillFilePath: string; name?: string; description?: string; /** Frontmatter as parsed; flat only, which is all SKILL.md declares. */ frontmatter: Record; /** SHA-256 of the SKILL.md bytes. */ contentHash: string; /** Every byte under the skill directory. */ totalBytes: number; fileCount: number; /** This skill's own `agents/openai.yaml`, when it ships one. */ agentMetadata?: OpenAIAgentMetadataParse; } interface OpenAIPackageAsset { path: string; bytes: number; contentHash: string; /** Inferred from the file extension — what the submitter CLAIMS it is. */ declaredMimeType?: string; /** Inferred from the bytes — what it actually is. */ sniffedMimeType?: string; dimensions?: ImageDimensions; /** Why the dimensions could not be read, when they could not. */ undecodableReason?: string; } /** A surface present in the package that the plugin directory does not run. */ interface OpenAIPackageSurface { path: string; /** The surface's name as the migration guide refers to it. */ surface: string; } /** Something the reader could not look at, and why. */ interface OpenAIPackageGap { subject: string; reason: string; } interface OpenAIPackageEntryStats { /** Entries the source listed, directories included — what the limit counts. */ entryCount: number; fileCount: number; directoryCount: number; totalUncompressedBytes: number; /** Absent when the source is not an archive. */ compressedBytes?: number; } interface OpenAIPluginPackageEvidence { manifest?: OpenAIPackageManifest; /** The plugin-level `agents/openai.yaml`, when present. */ agentMetadata?: OpenAIAgentMetadataParse; skills: OpenAIPackageSkill[]; assets: OpenAIPackageAsset[]; /** Non-directory surfaces the package ships, by canonical path. */ surfaces: OpenAIPackageSurface[]; entryStats: OpenAIPackageEntryStats; /** Every documented portal code this package trips, in discovery order. */ issues: OpenAIPortalIssue[]; /** What could not be examined, so a check can say `not-evaluated` honestly. */ gaps: OpenAIPackageGap[]; } declare function readOpenAIPluginPackage(source: PluginFileSource, options?: ReadOpenAIPluginPackageOptions): Promise; /** * Error catalog: one entry per known error class. The single source of * truth for friendly title, one-line explanation, likely causes, next * steps, and the docs anchor every renderer deep-links to. * * Slugs are stable strings. Once published in the docs anchor URL they * should not change — adding a new slug is fine; renaming an existing * one is a docs-breaking change. */ /** * Who has to act — NOT a blame label. * * MCPJam is a debugger: pointing it at a broken server is the product * working, not an incident. This field exists so a failure that belongs to * the user's server or the user's configuration can be shown to them clearly * while never paging the MCPJam team, and so the failures that ARE ours stop * being lost in that noise. * * - `user_server` — we reached the user's server (or its authorization * server) and it failed, refused, or answered wrongly. * - `user_config` — the inputs we were given are wrong or incomplete: URL, * credentials, transport, capability toggles. * - `mcpjam` — MCPJam's own code or infrastructure. * - `ambiguous` — the evidence does not settle it. Deliberately distinct * from `mcpjam`: consumers surface these without paging. * * Some slugs are genuinely undecidable between the two `user_*` values — a * refused port is "nothing is running there" or "wrong port", and the wire * cannot tell you which. Nothing downstream depends on separating them: * capture policy treats both as never-page, and user-facing copy comes from * this entry's own `oneLine` / `likelyCauses` / `nextSteps`, which already * state the ambiguity in words. Do NOT write user-facing prose off `origin`. */ type ErrorOrigin = "user_server" | "user_config" | "mcpjam" | "ambiguous"; type ErrorCatalogEntry = { slug: string; title: string; oneLine: string; likelyCauses: string[]; nextSteps: string[]; /** * Path on the docs site (`docs.mcpjam.com`) that this entry deep-links * to. Anchor matches a `

` in `/troubleshooting/error-codes`. */ docsAnchor: string; severity: "info" | "warning" | "error"; /** * Optional in the type because `ErrorCatalogEntry` is published and an * external caller may still construct one; required in practice — a test * asserts every catalog slug carries an origin. Read it through * `originOf()`, which defaults a missing value to `ambiguous`. */ origin?: ErrorOrigin; }; declare const ERROR_CATALOG: Record; type ErrorCatalogSlug = keyof typeof ERROR_CATALOG; /** * One-entry-point error describer. Browser-safe. * * Resolution order: * 1. MCPError / MCPAuthError sentinel (by class name + code). * 2. Numeric JSON-RPC code (`Error & { code: number }`). * 3. Node errno (via `extractNodeErrno`). * 4. HTTP status pattern (401 / 403 / 5xx). * 5. OAuth response-body shape (prioritized-key extractor). * 6. Message-regex fallback (connection-error patterns). * 7. `internal/unknown` catch-all. * * Never throws. Always returns a `NormalizedError`. */ type NormalizedError = ErrorCatalogEntry & { /** * Original error message, redacted for bearer tokens / OAuth secrets / * provider keys. Safe to render verbatim in the UI. */ rawMessage: string; /** * Original numeric or string error code, if the source carried one. */ rawCode?: number | string; /** * Captured `.cause` chain head — only `name` + `message`, redacted. */ cause?: { name: string; message: string; }; }; /** * Shape guard for `NormalizedError` payloads received from any boundary * (response body, action dispatch, prop). Use this before trusting a * value that crossed a wire — `webPost` populates `WebApiError.normalized` * from any object in the response body without validation, and a partial * payload would crash the render path when it tries to read * `docsAnchor.startsWith` / `severity` / `rawMessage`. Callers that fail * this guard should fall back to `describeError(input)`, which always * produces a complete `NormalizedError`. * * Mirrored field-for-field with the rendered surface — keep in sync when * adding required fields to `NormalizedError`. */ declare function isNormalizedError(value: unknown): value is NormalizedError; type DescribeContext = { /** * Who owns the credential this request used. Omit when unknown; only * `"mcpjam"` changes anything, so callers on hosted paths that refresh * their own tokens or bill their own provider keys must pass it. */ credentialOwner?: "user" | "mcpjam"; }; declare function originOf(value: { origin?: unknown; } | null | undefined): ErrorOrigin; declare function describeError(error: unknown, context?: DescribeContext): NormalizedError; /** * Build a `NormalizedError` from an explicit catalog slug, wrapping the * raw error for `rawMessage` / `cause` capture. Use this when the caller * has context the generic `describeError` resolver does not — e.g. a chat * route that knows an HTTP 401 is from an LLM provider, not an MCP server, * and wants to attach `provider/auth_error` instead of the resolver's * `auth/http_401`. * * Unknown slugs fall back to `internal/unknown` (never throws). */ declare function describeAsSlug(slug: string, error?: unknown, context?: DescribeContext): NormalizedError; /** * Shared Node errno extractor. Pure object/string inspection — no Node * imports, browser-safe. Originally lived inline at `sdk/src/retry.ts` * (extractNodeErrorCode); hoisted here so the error describer can share * the exact same surface as `isRetryableTransientError`. * * Walks `error.cause` because Node's `fetch` (undici) typically wraps * connection failures as `TypeError("fetch failed")` whose `.cause` is * the real `SystemError` carrying `code: "ECONNREFUSED"` (or similar). * Without the walk, every fetch-side errno would classify as the generic * "fetch failed" slug instead of the specific transport slug. * * Bounded depth to avoid pathological cyclic-cause structures. */ declare function extractNodeErrno(error: unknown, depth?: number): string | undefined; /** * Retryable Node errno set — exposed so callers (including * `isRetryableTransientError`) can share one source of truth without * duplicating the literal list. Kept in this module because the catalog * also reads it. */ declare const RETRYABLE_NODE_ERROR_CODES: ReadonlySet; /** * Integrity primitives for `io.modelcontextprotocol/skills` (SEP-2640). * * SEP-2640 makes four checks MANDATORY for a host, and this module is the one * place each is implemented: * * 1. every fetched file is verified against its manifest `digest`; * 2. a read of a URI absent from the manifest is a FAILURE, not a fetch; * 3. the fetched SKILL.md's frontmatter is re-checked field-by-field against * the frontmatter the listing claimed; * 4. the URI's final path segment must equal `frontmatter.name`. * * ## Browser-safe by construction * * `crypto.subtle` (WebCrypto), never `node:crypto`. The SDK is imported by the * inspector's client bundle as well as its server, and a `node:crypto` import * here would break the browser build the same way the Ajv-backed dialect * validator does (which is why `tasks-ext-schemas.ts` uses zod rather than * it). WebCrypto's digest is async, so every verification helper here is too. * * ## Why refusal is the default everywhere * * Each helper returns a discriminated result rather than a boolean, because * every caller must be able to say WHAT failed: MCPJam is a debugger, and * "digest mismatch" with no expected/actual pair is not a diagnosis. Callers * that only need a gate can check `.ok`. */ /** * Digest algorithms this host will verify against. * * SEP-2640 writes digests as `:` and does not fix the * algorithm set. We accept only SHA-2 family members WebCrypto implements — * an unrecognised algorithm produces a REFUSAL, never a skipped check. That * is the whole point: a server could otherwise declare `digest: "none:"` and * disable verification for its own content. */ declare const SUPPORTED_DIGEST_ALGORITHMS: { readonly sha256: "SHA-256"; readonly sha384: "SHA-384"; readonly sha512: "SHA-512"; }; type SupportedDigestAlgorithm = keyof typeof SUPPORTED_DIGEST_ALGORITHMS; interface ParsedDigest { algorithm: SupportedDigestAlgorithm; /** Lowercased hex, already length-checked for the algorithm. */ hex: string; } /** * Parses a `:` digest, or returns `undefined` for anything * this host will not verify against (unknown algorithm, wrong hex length, * non-hex characters, missing separator). * * Case: the algorithm and hex are both lowercased before comparison — hex is * case-insensitive, and a server writing `SHA256:AB…` is not a threat, just * untidy. */ declare function parseDigest(raw: unknown): ParsedDigest | undefined; /** Hex SHA-256 of raw bytes. */ declare function sha256HexOfBytes(bytes: Uint8Array | ArrayBuffer): Promise; /** Hex SHA-256 of a UTF-8 string. */ declare function sha256HexOfText(text: string): Promise; type DigestVerification = { ok: true; algorithm: SupportedDigestAlgorithm; digest: string; } | { ok: false; reason: "unsupported_digest" | "digest_mismatch"; expected: string; actual?: string; }; /** * Verifies bytes against a manifest digest string. * * An UNSUPPORTED digest fails with its own reason rather than * `digest_mismatch`: they call for different messages ("this server used an * algorithm we do not verify" vs "this file is not what the server said it * was"), and conflating them would let an algorithm-downgrade read as * tampering, or vice versa. */ declare function verifyDigest(bytes: Uint8Array | ArrayBuffer, expectedDigest: string): Promise; /** * The manifest entry for `uri`, or `undefined` when the URI is NOT listed. * * SEP-2640: "hosts MUST treat reads of unlisted files as failures". This is * the lookup that makes that enforceable — a caller that fetches without * consulting it has bypassed the rule, so every read path in the SDK and the * app goes through here first. * * Matching is EXACT on the URI string. No normalization, no percent-decoding, * no trailing-slash tolerance: a normalizing comparison is precisely how an * unlisted path sneaks past a listed one. */ declare function findListedResource(entry: Pick, uri: string): SkillResourceRef | undefined; /** Whether `uri` appears in the entry's manifest. */ declare function isListedResource(entry: Pick, uri: string): boolean; /** * The final path segment of a skill URI — the value SEP-2640 requires to equal * `frontmatter.name`. * * Convention is `skill:///SKILL.md`, so the NAME is the segment * BEFORE the trailing `SKILL.md`, not the last segment outright. Non-`skill://` * schemes are supported (skill-ness comes from the listing, never the scheme), * so this works on the raw string rather than through a URL parser — a custom * scheme is not guaranteed to be WHATWG-parseable, and `new URL()` would * additionally percent-decode, which would let `%2F` masquerade as a segment * boundary. * * Returns `undefined` when no name segment can be derived; callers treat that * as an identity failure, never as "skip the check". */ declare function skillNameFromUri(uri: string): string | undefined; type FrontmatterIdentityCheck = { ok: true; name: string; description: string; } | { ok: false; reason: "not_an_object" | "missing_name" | "missing_description" | "name_uri_mismatch" | "field_drift"; /** The frontmatter field that failed, when the failure is field-scoped. */ field?: string; expected?: string; actual?: string; }; /** * Checks a skill's identity: the frontmatter carries a usable `name` / * `description`, and the `name` equals the URI's final path segment. * * This is the FIRST of the two identity checks. The second — that the * frontmatter the server ADVERTISED matches the frontmatter actually present * in the fetched SKILL.md — is {@link checkFrontmatterDrift}, which needs the * fetched bytes and therefore cannot run at listing time. */ declare function checkSkillIdentity(uri: string, frontmatter: unknown): FrontmatterIdentityCheck; /** * Field-by-field re-check of the ADVERTISED frontmatter against the * frontmatter parsed out of the FETCHED SKILL.md. * * SEP-2640 requires the two to be identical in content, so this compares them * in BOTH directions over the union of their keys. * * The one-directional version of this check — advertised ⊆ fetched — had a * hole: a field present only in the fetched SKILL.md passed verification * untouched. That is the interesting direction, not the harmless one. The * listing is what a user or model sees when approving a load, so a field the * fetched file adds is precisely the field nobody agreed to; `allowed-tools` * is the obvious example (MCPJam never honors it, but "we ignore it anyway" is * a property of today's consumer, not of this check). * * Comparison is on a canonical JSON serialization, so nested values (an * `allowed-tools` array, a metadata object) are compared structurally rather * than by reference. Key order within an object is normalized; array order is * NOT (order is semantic in every frontmatter list we know of). */ declare function checkFrontmatterDrift(advertised: unknown, fetched: unknown): FrontmatterIdentityCheck; /** * Deterministic JSON with object keys sorted, used for structural comparison * and for content-addressed version hashing. * * `undefined` serializes to the literal `"undefined"` sentinel rather than * disappearing, so "field absent" and "field present as null" stay distinct in * a drift comparison. */ declare function canonicalJson(value: unknown): string; /** * The content-addressed identity of one captured skill VERSION. * * Covers everything that makes a version distinct: the skill's URI, its * verbatim frontmatter, its complete (sorted) resource manifest, and the hash * of its SKILL.md body. Two captures agreeing on all four are the same * version, and an append-only version store keys on exactly this. * * The manifest is sorted by `uri` before hashing so a server that reorders its * `resources` array between listings does not mint a spurious new version; * within an entry, `uri` and `digest` are the only fields hashed, because a * `.loose()` passthrough key is not part of the skill's identity. */ declare function computeSkillVersionHash(args: { skillUri: string; frontmatter: unknown; resources: ReadonlyArray<{ uri: string; digest: string; }>; contentSha256: string; }): Promise; /** * Structured integrity failure. Thrown by the verified-load helpers so a * caller can render the specific violation (which file, which digest, which * frontmatter field) instead of a generic "load failed". */ declare class SkillIntegrityError extends Error { readonly skillUri: string; readonly kind: "unlisted_resource" | "digest_mismatch" | "unsupported_digest" | "frontmatter_drift" | "identity_mismatch" | "no_resources" | "fetch_failed" | "not_text"; readonly resourceUri?: string; readonly expected?: string; readonly actual?: string; readonly field?: string; constructor(args: { message: string; skillUri: string; kind: SkillIntegrityError["kind"]; resourceUri?: string; expected?: string; actual?: string; field?: string; }); } declare function isSkillIntegrityError(error: unknown): error is SkillIntegrityError; /** * Splits a SKILL.md into its YAML frontmatter block and body. * * Parses the complete YAML object with the browser-safe `yaml` package. SEP-2640 * requires the advertised and fetched objects to be identical in content, so a * line-oriented scalar parser is not sufficient: a block sequence such as * `allowed-tools:` must not disappear from the comparison. */ declare function splitSkillMarkdown(markdown: string): { frontmatter: Record | undefined; body: string; }; /** * Backward-compatible projection of advertised frontmatter. Earlier SDK * versions could compare only scalar YAML and reported complex fields as * unverifiable. The YAML parser now preserves those values structurally, so * every object field is comparable and no conforming field is silently * skipped. */ declare function splitAdvertisedFrontmatter(advertised: unknown): { comparable: Record; unverifiable: string[]; }; /** @deprecated All advertised YAML fields are now structurally comparable. */ declare function comparableAdvertisedFrontmatter(advertised: unknown): Record; /** * Verifies a fetched SKILL.md against its listing entry: digest, identity, and * frontmatter drift. * * Whether the entry has a manifest AT ALL is the CALLER's check — refusing a * resource-less skill is MCPJam policy (we decline the SEP's "MAY" to load * unverifiable content), and policy belongs at the call site that can explain * it to a user. What is enforced HERE is that a manifest which does exist * actually covers the SKILL.md: a manifest listing other files but omitting * `skill://…/SKILL.md` would otherwise let identity and drift pass with the * body never digest-checked. */ declare function verifySkillMarkdown(args: { entry: SkillEntry; markdown: string; bytes?: Uint8Array; }): Promise<{ frontmatter: SkillIdentityFrontmatter; body: string; }>; /** * Wire → lifecycle normalizers. * * The lifecycle engine is deliberately wire-blind: it schedules and remembers, * and it must not grow a per-wire branch. These adapters are the only place * that knows the difference between a 2025-11-25 in-core task and a SEP-2663 * extension task, and each keeps its own validator — the two wires are not * compatible and must never share one. * * What is preserved and what is not: * * - the raw payload is carried through verbatim, because a debugger has to be * able to show what the server actually sent; * - `ttlMs` distinguishes absent (leave the previous value) from `null` (the * server says: no expiry); * - a tool result with `isError: true` is a **completed** task. Only a * JSON-RPC error makes a task `failed` (`tasks.md:837`, `:891-892`). Getting * this backwards would report a tool's own error as a protocol fault. */ /** * Extension `tasks/get` result or `notifications/tasks` params → observation. * * Both carry the same `DetailedTask`, so they normalize identically; the caller * decides which validator produced the input and tags the observation source. */ declare function extensionTaskToObservation(task: DetailedTaskExt | GetTaskExtResult | TaskExtNotificationParams): TaskLifecycleObservation; /** * Legacy 2025-11-25 statuses. The in-core utility used the same five names, so * the normalized status needs no remapping — only the surrounding fields do. * * Exported because callers that validate a status before this normalizer ever * runs — a creation-time check, say — must test the SAME five names. A second * hand-written copy would be free to drift from the one the normalizer trusts. */ declare const LEGACY_TASK_STATUSES: ReadonlySet; /** * Legacy `tasks/get` result → observation. * * The legacy wire names its timing fields `ttl` / `pollInterval` (not `ttlMs` / * `pollIntervalMs`) and has no inline result — the result lives behind a * separate `tasks/result` call. Both differences are absorbed here so no * surface has to branch on the wire. * * An unrecognized status normalizes to `working` rather than being dropped: a * handle we cannot classify is still a handle we must keep polling, and * inventing a terminal state would strand it. * * There is deliberately no `inputRequests` here, and none is missing: the * 2025-11-25 `Task` shape carries no keyed request map at all * (`taskId`, `status`, `ttl`, `createdAt`, `lastUpdatedAt`, `pollInterval`, * `statusMessage` — nothing else). On that wire an `input_required` task's * request arrives out of band, as an elicitation stamped with `relatedTaskId`, * and is answered through the elicitation channel rather than `tasks/update` — * which the legacy wire does not have. `driveTaskToTerminal` recognizes the * resulting "input_required with an empty keyed snapshot" and reports * `input-required` rather than re-polling an unchanging state. */ declare function legacyTaskToObservation(task: MCPTask & Record): TaskLifecycleObservation; /** * `-32602` is how a server reports a task id it does not know * (`tasks.md:793-795`). * * The requirement level differs by method and the difference is load-bearing: * `MUST` for `tasks/get`, `SHOULD` for `tasks/update` and `tasks/cancel`. So * only a `tasks/get` `-32602` is proof; from update or cancel it is a hint that * has to be confirmed by a `tasks/get` before a handle is retired. Callers get * the raw predicate here and apply that rule themselves — see * `TaskLifecycleEngine.markExpired`. */ declare const UNKNOWN_TASK_ERROR_CODE = -32602; /** * The extension's "this operation requires the tasks declaration" code. This is * `MissingRequiredClientCapability`, so it follows the renumber the central * table already records: ext-tasks drafted `-32003`, a value that appears in no * core schema version, and corrected it to `-32021` to match SEP-2663, the core * 2026-07-28 schema and the TypeScript SDK. A server still answering `-32003` * is pre-final; `PRE_RENUMBER_DRAFT_ERROR_CODES` names that value. */ declare const TASKS_DECLARATION_REQUIRED_ERROR_CODE: -32021; declare function isUnknownTaskError(error: unknown): boolean; declare function isTasksDeclarationRequiredError(error: unknown): boolean; /** * Milliseconds from a `Retry-After` header value, which is either a delta in * seconds or an HTTP date. `undefined` for anything else — a malformed hint * must not become a zero-length backoff. */ declare function parseRetryAfterMs(value: string | number | null | undefined, now?: number): number | undefined; /** * Best-effort `Retry-After` recovery from a failed read's error object. * * The lifecycle engine has honored `Retry-After` as a scheduling floor since * it was built, but nothing ever fed one to it: the official client (as of * `2.0.0-beta.4`) does not attach response headers to the error it throws for * a non-2xx POST, so a 429's hint died in the transport. This probe is the * seam where the hint lands whenever ANY layer preserves it — an upgraded * upstream client, an MCPJam-side fetch wrapper, or a test fixture — checked * from most- to least-explicit shape: * * - `retryAfterMs` — already milliseconds, trusted verbatim * - `retryAfter` — seconds or HTTP date (the platform error convention) * - `headers` / `response.headers` — a `Headers` instance or plain record * carrying the raw header * * `undefined` when nothing matches, in which case the engine's ordinary error * backoff is the only pacing — the pre-existing behavior, not a new fallback. */ declare function retryAfterMsFromError(error: unknown, now?: number): number | undefined; interface RunServerDoctorInput { config: MCPServerConfig; target: TTarget; timeout: number; rpcLogger?: RpcLogger; retryPolicy?: RetryPolicy; /** Transport for the probe's requests. See `buildDoctorProbeConfig`. */ fetchFn?: typeof fetch; } type WithConnectedManager = (config: MCPServerConfig, fn: (manager: MCPClientManager, serverId: string) => Promise, options?: { timeout?: number; rpcLogger?: RpcLogger; retryPolicy?: RetryPolicy; }) => Promise; interface ServerDoctorDependencies { probeServer?: typeof probeMcpServer; withManager?: WithConnectedManager; } declare function runServerDoctor(input: RunServerDoctorInput, dependencies?: ServerDoctorDependencies): Promise>; declare function collectConnectedServerDoctorState(manager: MCPClientManager, serverId: string): Promise; declare const MCPJAM_LOGO_URI = "https://www.mcpjam.com/mcp_jam_2row.png"; declare const DEFAULT_MCPJAM_CLIENT_ID_METADATA_URL = "https://www.mcpjam.com/.well-known/oauth/client-metadata.json"; declare const MCPJAM_CLIENT_URI = "https://github.com/MCPJam/inspector"; declare function getBrowserDebugDynamicRegistrationMetadata(protocolVersion: OAuthProtocolVersion): Partial; declare const ID_JAG_GRANT_PROFILE = "urn:ietf:params:oauth:grant-profile:id-jag"; declare const TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange"; declare const JWT_BEARER_GRANT = "urn:ietf:params:oauth:grant-type:jwt-bearer"; declare const ID_JAG_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id-jag"; declare const ID_TOKEN_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:id_token"; declare const SAML2_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:saml2"; declare const XAA_DEBUG_CLIENT_ID_METADATA_URL = "https://app.mcpjam.com/.well-known/oauth/xaa-client-metadata.json"; declare const XAA_DEBUG_IDP_CLIENT_ID = "mcpjam-xaa-debugger"; declare function getXaaDebugClientMetadata(options: { tokenEndpointAuthMethod: "client_secret_post" | "none" | "private_key_jwt"; /** Public keys published for `private_key_jwt` (confidential CIMD). */ jwks?: { keys: JsonWebKey[]; }; }): Partial & { jwks?: { keys: JsonWebKey[]; }; }; /** XAA Connect's persistent DCR client metadata. Keep this derived from the * debugger profile so both surfaces advertise the same ID-JAG draft and grant * pair while retaining distinct client identities at the authorization server. */ declare function getXaaConnectClientMetadata(options?: { scope?: string; }): Partial; declare const XAA_CONFIDENTIAL_CIMD_ORIGIN = "https://app.mcpjam.com"; declare const XAA_CONFIDENTIAL_CIMD_PATH_PREFIX = "/.well-known/oauth/xaa-cimd/"; /** Build the confidential-CIMD document URL that publishes `publicJwk`. */ declare function buildConfidentialCimdUrl(publicJwk: JsonWebKey, origin?: string): string; /** * Decode and STRICTLY validate the URL-embedded public JWK (used by the * reflector). Returns a freshly reconstructed **public** EC P-256 JWK * (`kty/crv/x/y` + `alg/use`, and `kid` only if safe) — never the raw parsed * object. This rejects anything that is not an exact P-256 public key and, * critically, drops any private field (`d`) or unknown members an attacker may * have encoded, so the reflector can only ever publish a clean public key. * Returns null on anything malformed. */ declare function decodeConfidentialCimdKey(encoded: string): JsonWebKey | null; declare const UNVERIFIED_CONFIDENTIAL_CIMD_CLIENT_NAME = "Unverified XAA client (self-published key; not issued or endorsed by MCPJam)"; /** Build the reflector's `private_key_jwt` document body for a decoded public * key. Deliberately carries NO MCPJam branding (`client_uri`/`logo_uri`). */ declare function getConfidentialCimdReflectorMetadata(jwks: { keys: JsonWebKey[]; }): Partial & { jwks: { keys: JsonWebKey[]; }; }; type IdJagMetadataEvidence = "present" | "omitted" | "contradicted"; interface IdJagClientMetadataEvaluation { profile: IdJagMetadataEvidence; jwtBearerGrant: IdJagMetadataEvidence; tokenExchangeGrant: IdJagMetadataEvidence; /** Raw value; auth-method policy is the caller's. */ tokenEndpointAuthMethod: string | undefined; } /** * Classifies ID-JAG client-metadata evidence in a DCR response or a Client ID * Metadata Document. Evidence only - policy differs by caller: RFC 7591 lets * an AS ignore unknown request metadata, so a DCR response may treat * "omitted" as a warning, while a CIMD document is the authoritative client * metadata and "omitted" means the document is insufficient. */ declare function evaluateIdJagClientMetadata(metadata: Record): IdJagClientMetadataEvaluation; /** @deprecated Use {@link RegistrationStrategy} from `@mcpjam/sdk` — the * shared vocabulary for OAuth and XAA. Structurally identical. */ type OAuthRegistrationStrategy$1 = RegistrationStrategy2025_03_26 | RegistrationStrategy2025_06_18 | RegistrationStrategy2025_11_25; type OAuthProtocolMode = "auto" | OAuthProtocolVersion; /** @deprecated Use {@link RegistrationMode} from `@mcpjam/sdk` — the shared * vocabulary for OAuth and XAA. Structurally identical. */ type OAuthRegistrationMode = "auto" | OAuthRegistrationStrategy$1; interface AuthorizationDiscoverySnapshot { registrationStrategies?: Array<"preregistered" | "dcr" | "cimd">; authorizationServerMetadataUrl?: string; authorizationServerMetadata?: Record; resourceMetadataUrl?: string; resourceMetadata?: Record; discoveryError?: string; } interface AuthorizationPlanInput { serverUrl?: string; protocolMode?: OAuthProtocolMode; protocolVersion?: OAuthProtocolVersion; registrationMode?: OAuthRegistrationMode; registrationStrategy?: OAuthRegistrationStrategy$1; clientId?: string; clientSecret?: string; hasClientSecret?: boolean; clientIdMetadataUrl?: string; useRegistryOAuthProxy?: boolean; authMode?: OAuthAuthMode; discovery?: AuthorizationDiscoverySnapshot; /** * AUTO-mode preference order over registration strategies, derived from an * emulated client's `authModel` (HP-43). Consulted ONLY when the caller has * not pinned a `registrationMode` — an explicit mode always wins. * * Empty or absent leaves the built-in AUTO precedence untouched, so every * non-emulation caller resolves exactly as before. */ registrationPreference?: Array<"preregistered" | "dcr" | "cimd">; } interface AuthorizationPlanCapabilities { registrationStrategies: Array<"preregistered" | "dcr" | "cimd">; supportsCimd: boolean; supportsDcr: boolean; authorizationServerMetadataUrl?: string; resourceMetadataUrl?: string; discoveryError?: string; } type AuthorizationPlanBlockerCode = "PREREGISTERED_MISSING_CLIENT_ID" | "PREREGISTERED_MISSING_CLIENT_SECRET" | "CIMD_UNSUPPORTED_PROTOCOL" | "CIMD_UNSUPPORTED_AUTH_MODE" | "CIMD_NOT_ADVERTISED" | "DCR_NOT_ADVERTISED" | "AUTO_INCOMPLETE_PREREGISTERED_CREDENTIALS" | "AUTO_NO_CLIENT_CREDENTIALS_COMPATIBLE_FLOW" | "AUTO_NO_USABLE_REGISTRATION_FLOW"; interface AuthorizationPlanBlocker { code: AuthorizationPlanBlockerCode; message: string; } interface ResolvedAuthorizationPlan { protocolMode: OAuthProtocolMode; protocolVersion: OAuthProtocolVersion; registrationMode: OAuthRegistrationMode; registrationStrategy?: OAuthRegistrationStrategy$1; status: "ready" | "discovery_required" | "blocked"; blockerDetails: AuthorizationPlanBlocker[]; blockers: string[]; warnings: string[]; capabilities: AuthorizationPlanCapabilities; canonicalResource?: string; resourceIndicator?: ResourceIndicatorDecision; clientIdMetadataUrl?: string; summary: string; } declare function resolveRegistrationStrategies(protocolVersion: OAuthProtocolVersion, authServerMetadata: Record | undefined): Array<"preregistered" | "dcr" | "cimd">; declare function resolveAuthorizationPlan(input: AuthorizationPlanInput): ResolvedAuthorizationPlan; /** * Factory for creating protocol-specific OAuth state machines * * This factory selects the appropriate state machine implementation * based on the protocol version specified in the configuration. */ /** * A redaction sentinel reached the state machine as a live credential. * * Distinct error type so a caller can tell "MCPJam redacted its own live data" * apart from an authorization-server failure — the two look identical from the * outside (both end in `401 invalid_token`) and that is precisely what made * #3865 expensive to diagnose. */ declare class OAuthRedactedCredentialError extends Error { readonly field: string; constructor(field: string, target: string); } /** * Throw if an executor result carries a redaction sentinel where the flow * expects a credential; otherwise return the result unchanged (by identity). * * Inspects `result.body`, which is where the real executor puts response * fields — a check against `result.access_token` would look reasonable and * never fire. */ declare function assertOAuthResultCredentialsUnredacted(result: OAuthRequestResult, request: Pick): OAuthRequestResult; /** * Configuration for creating an OAuth state machine with protocol version selection */ interface OAuthStateMachineFactoryConfig extends BaseOAuthStateMachineConfig { protocolVersion: OAuthProtocolVersion; registrationStrategy: RegistrationStrategy2025_03_26 | RegistrationStrategy2025_06_18 | RegistrationStrategy2025_11_25 | RegistrationStrategy2026_07_28; /** * Permit outbound OAuth metadata fetches to loopback hosts (local-dev * reflectors). Defaults to `false` (secure): a hostile server must not be * able to steer metadata fetches at the user's own `127.0.0.1`/`localhost`. * Only an explicit local-dev surface should opt in. The guard blocks * LAN/link-local/reserved destinations regardless of this flag. */ allowLoopbackMetadataFetch?: boolean; } /** * Creates an OAuth state machine based on the specified protocol version * * @param config - Configuration including protocol version and registration strategy * @returns An OAuth state machine implementation for the specified protocol version * * @example * ```typescript * // Create a 2025-11-25 state machine with CIMD * const machine = createOAuthStateMachine({ * protocolVersion: "2025-11-25", * registrationStrategy: "cimd", * serverUrl: "https://mcp.example.com", * serverName: "Example MCP Server", * state: EMPTY_OAUTH_FLOW_STATE, * updateState: (updates) => setState(updates), * }); * * // Create a 2025-06-18 state machine with DCR * const legacyMachine = createOAuthStateMachine({ * protocolVersion: "2025-06-18", * registrationStrategy: "dcr", * serverUrl: "https://mcp.example.com", * serverName: "Legacy MCP Server", * state: EMPTY_OAUTH_FLOW_STATE, * updateState: (updates) => setState(updates), * }); * ``` */ declare function createOAuthStateMachine(config: OAuthStateMachineFactoryConfig): OAuthStateMachine; /** * Gets the default registration strategy for a given protocol version */ declare function getDefaultRegistrationStrategy(protocolVersion: OAuthProtocolVersion): string; /** * Gets the supported registration strategies for a given protocol version */ declare function getSupportedRegistrationStrategies(protocolVersion: OAuthProtocolVersion): ReadonlyArray; /** * Protocol version metadata for UI display. Labels come from * `protocolVersionLabel` so a consumer rendering this record cannot disagree * with the inspector's dropdowns about which revision is Latest. */ declare const PROTOCOL_VERSION_INFO: { readonly "2025-03-26": { readonly label: string; readonly description: "Original MCP OAuth specification with direct discovery"; readonly features: readonly ["Dynamic Client Registration (DCR) SHOULD be supported", "Direct RFC8414 discovery from MCP server base URL", "Fallback to default endpoints (/authorize, /token, /register)", "PKCE is REQUIRED for all clients", "No Protected Resource Metadata (RFC9728)"]; }; readonly "2025-06-18": { readonly label: string; readonly description: "MCP OAuth specification with resource metadata"; readonly features: readonly ["Dynamic Client Registration (DCR) SHOULD be supported", "Protected Resource Metadata (RFC9728) required", "RFC8414 discovery ONLY (no OIDC) with root fallback", "PKCE recommended but not strictly enforced"]; }; readonly "2025-11-25": { readonly label: string; readonly description: "MCP OAuth specification with CIMD support"; readonly features: readonly ["Client ID Metadata Documents (CIMD) SHOULD be supported", "Protected Resource Metadata (RFC9728) required", "RFC8414 OR OIDC discovery without root fallback", "PKCE strictly required and enforced", "Enhanced security with URL-based client IDs"]; }; readonly "2026-07-28": { readonly label: string; readonly description: "Newest MCP OAuth specification: 2025-11-25 discovery plus OIDC application_type"; readonly features: readonly ["Client ID Metadata Documents (CIMD) SHOULD be supported", "Protected Resource Metadata (RFC9728) required", "RFC8414 OR OIDC discovery without root fallback", "PKCE strictly required and enforced", "SEP-837: OIDC application_type sent on Dynamic Client Registration"]; }; }; type OAuthTraceStepStatus = "pending" | "success" | "error"; interface OAuthTraceStepSnapshot { step: OAuthFlowStep; title: string; status: OAuthTraceStepStatus; message?: string; error?: string; details?: Record; recovered?: boolean; recoveredAt?: number; recoveryMessage?: string; startedAt: number; completedAt?: number; } interface OAuthTraceSnapshot { version: 1; currentStep: OAuthFlowStep; steps: OAuthTraceStepSnapshot[]; httpHistory: HttpHistoryEntry[]; error?: string; } interface OAuthTraceProjectionContext { syntheticStepTimestamps: Partial>; lastSyntheticTimestamp: number; } declare function createOAuthTraceProjectionContext(): OAuthTraceProjectionContext; declare function projectOAuthTraceSnapshot(input: { state: OAuthFlowState; context?: OAuthTraceProjectionContext; /** * When true (default), redact tokens/secrets/PII from traces. SDK consumers * that are local dev tools may set false to show raw request/response data. */ sanitize?: boolean; }): OAuthTraceSnapshot; type OAuthAuthorizationRequestResult = { type: "authorization_code"; authorizationCode: string; } | { type: "redirect"; }; interface OAuthStateMachineRunConfig extends OAuthStateMachineFactoryConfig { maxSteps?: number; /** When true (default), trace snapshots redact secrets. Set false for local dev tooling. */ sanitizeTrace?: boolean; onAuthorizationRequest?: (input: { authorizationUrl: string; state: OAuthFlowState; }) => MaybePromise; onTraceUpdate?: (input: { trace: OAuthTraceSnapshot; state: OAuthFlowState; reason: "state_update" | "redirect" | "error" | "complete"; }) => void; } interface OAuthStateMachineRunResult { completed: boolean; redirected: boolean; authorizationUrl?: string; state: OAuthFlowState; error?: { message: string; }; } declare function runOAuthStateMachine(config: OAuthStateMachineRunConfig): Promise; /** * `deriveOAuthEmulation` — compile an evidence-backed OAuth profile into the * generic machine knobs and the client's authentication ladder (HP-43). * * Evidence rules, applied uniformly: * - `verified` → the value is used, field `modeled`. * - `refuted` → the envelope's value IS the true value (the refuted CLAIM * is what was disproven) → used identically, field `modeled`. * - `unverifiable` or absent → field `not_modeled`: the machine keeps * normal MCPJam behavior for that dimension. Never a guess. * * The compiler is pure and deterministic: same profile in, same knobs out. * The private backend resolves catalog rows to profiles and calls this; no * client name ever reaches this module. */ interface DerivedOAuthEmulation { /** * Which of the four state machines runs the ladder. `oauthSpecVersion` * SELECTS the machine — same-origin discovery (2025-03-26) is derived from * this one fact, never a separate flag. */ protocolVersion: OAuthProtocolVersion; /** The machine-facing knobs (`BaseOAuthStateMachineConfig.emulation`). */ emulation: OAuthEmulationConfig; /** * The client's authentication ladder, in profile order. Executed by * `runEmulatedOAuthPreflight`; a profile with no `authModel` evidence yields * a single OAuth attempt with no strategy preference (today's AUTO order). */ authAttempts: EmulatedAuthAttempt[]; /** * Redirect URIs observed in the real client's registration, in captured * order. Deliberately NOT copied into `emulation.dcrRedirectUris`: replaying * them alone would send the authorization code somewhere MCPJam cannot * receive it. The runner combines them with its own callback * (`planCompletionSafeRedirects`) and reports the difference. */ capturedRedirectUris?: string[]; /** Per-field enforcement status. */ coverage: OAuthEmulationCoverage; /** `complete` iff every field is `modeled`. Partial coverage can never * claim parity. */ coverageSummary: "complete" | "partial"; divergences: OAuthEmulationDivergence[]; } declare function deriveOAuthEmulation(profile: HostConfigOAuthProfile): DerivedOAuthEmulation; /** * Completion-safe redirect planning (HP-43 step 5). * * The emulator must do two things that pull in opposite directions: replay the * real client's registration body, and still finish the dance with a real * token. A real client registers callbacks MCPJam cannot receive on * (`cursor://…`, a vendor's hosted callback, a loopback port owned by another * process), so a byte-exact replay would strand the authorization code. * * The resolution, and its honesty rules: * * - The registration body carries the captured redirect URIs **in captured * order, plus MCPJam's callback appended**. The appended entry is a * DECLARED substitution — the registration step can never be presented as * byte-matched when it happens. * - The authorization and token legs **always** use MCPJam's callback, so * the code comes back to us and can be exchanged. * - If the server rejects that registration with a structured RFC 7591 * `invalid_redirect_uri`, and only then, the caller may re-register with * MCPJam's callback alone (see `isInvalidRedirectUriRejection`). That is * the second and last registration a preflight will ever perform. * * There is no exact-foreign-redirect mode: a run that cannot complete produces * no token, and a token is what the developer came for. */ interface CompletionSafeRedirectPlan { /** `redirect_uris` for the registration body. */ registrationRedirectUris: string[]; /** The callback the authorization + token legs use. Always MCPJam's. */ authorizationRedirectUri: string; /** Declared differences from the captured registration. */ divergences: OAuthEmulationDivergence[]; } declare function planCompletionSafeRedirects(input: { capturedRedirectUris?: string[]; callbackUrl: string; }): CompletionSafeRedirectPlan; /** * Is this registration response a STRUCTURED RFC 7591 `invalid_redirect_uri`? * * Deliberately narrow. Retrying registration creates a second client on the * authorization server, so the trigger must be the server explicitly naming * this error in the documented field — never a substring match on prose, a * generic 4xx, a timeout, or a body that failed to parse. Anything else is an * unrelated failure that a retry would neither diagnose nor fix. */ declare function isInvalidRedirectUriRejection(response: { status: number; body: unknown; }): boolean; interface OAuthStepInfo { title: string; summary: string; teachableMoments?: string[]; tips?: string[]; } declare function getStepInfo(step: OAuthFlowStep): OAuthStepInfo; declare function getStepIndex(step: OAuthFlowStep): number; interface DynamicClientRegistrationRequestInput { registrationEndpoint: string; redirectUri: string; dynamicRegistrationDefaults: Partial; /** Already-resolved scope value; scope sourcing is version/caller-specific. */ scope?: string; customHeaders?: Record; } /** * Builds the RFC 7591 registration POST exactly as the debug OAuth machines * historically did: caller-supplied metadata defaults win, with * authorization-code-flavored fallbacks for anything absent. Callers that are * not authorization-code clients (e.g. XAA jwt-bearer clients) must build * their own request instead of using these fallbacks. */ declare function buildDynamicClientRegistrationRequest(input: DynamicClientRegistrationRequestInput): OAuthHttpRequest; /** Minimal structural constraint so both the OAuth machines' HttpHistoryEntry * and the inspector's XAAHttpHistoryEntry fit; the helper never reads `step`. */ type BackfillableHistoryEntry = { timestamp: number; duration?: number; response?: OAuthHttpResponse; }; interface DynamicClientRegistrationCredentials { clientId?: string; clientSecret?: string; /** Raw token_endpoint_auth_method from the response. Legacy OAuth machines * normalize it via normalizeRegisteredClientAuthMethod at their call sites; * other callers validate the raw value against their own policy. */ tokenEndpointAuthMethod?: string; clientSecretExpiresAt?: number; } type DynamicClientRegistrationOutcome = { status: "registered"; /** Redacted; safe for diagnostic surfaces. */ response: OAuthHttpResponse; httpHistory: THistory[]; /** Raw response body. Operational data — callers must not log it. */ clientInfo: Record; /** Raw credentials. Operational data — callers must not log them. */ credentials: DynamicClientRegistrationCredentials; /** typeof clientInfo.client_id !== "string" — strict callers hard-fail. */ missingClientId: boolean; /** 2xx other than 201; RFC 7591 specifies 201 Created. */ nonCanonicalSuccessStatus: boolean; /** Credential-free record for info logs. */ infoLogData: Record; } | { status: "http_error" | "network_error" | "invalid_response"; /** Redacted (http_error/invalid_response) or synthesized (network_error). */ response: OAuthHttpResponse; httpHistory: THistory[]; error: string; fallbackNote: string; errorWithFallbackHint: string; }; interface ExecuteDynamicClientRegistrationInput { /** The exact pre-built request the caller has already recorded in its * lastRequest/history entry. Executed as-is so the displayed request and * the executed request cannot drift; the executor owns wire serialization. */ request: OAuthHttpRequest; requestExecutor: OAuthRequestExecutor; httpHistory?: THistory[]; } /** * Executes an RFC 7591 registration POST via the caller's executor, back-fills * a cloned last history entry with the redacted response, and classifies the * result. Never calls updateState — state transitions stay caller-owned. * Never throws: transport failures become a "network_error" outcome. */ declare function executeDynamicClientRegistration(input: ExecuteDynamicClientRegistrationInput): Promise>; /** * Client Identifier URL validation per draft-ietf-oauth-client-id-metadata-document-02. * * Client Identifier URLs are compared with simple string comparison (RFC 3986 * section 6.2.1), so validation MUST NOT normalize the input: `:443`, percent * escaping, and other distinguishable spellings are distinct client * identities. The validated original string is returned unchanged. */ /** RFC 8252 loopback hosts (for the gated local-dev CIMD carve-out). */ declare function isLoopbackHost(hostname: string): boolean; /** * True only for an `http://` URL whose host is an RFC 8252 loopback address — * the single shape the gated local-dev CIMD carve-out permits. Callers use this * to scope the carve-out to the URL actually being fetched, so a loopback opt-in * never relaxes the private-host / DNS-rebinding guard for a public URL. */ declare function isLoopbackClientMetadataUrl(url: string): boolean; declare function validateClientIdMetadataUrl(clientIdMetadataUrl: string, options?: { allowLoopback?: boolean; }): string; interface DecodedJwtParts { header: Record | null; payload: Record | null; signature: string; } declare function decodeJWTParts(token: string): DecodedJwtParts | null; declare function decodeJWT(token: string): Record | null; declare function formatJWTTimestamp(timestamp: number): string; /** JWKS `kid` the mock IdP signs with and publishes. */ declare const XAA_IDP_KID = "xaa-idp-1"; /** Client-authentication methods supported by an XAA CIMD client. */ declare const XAA_CLIENT_AUTH_METHODS: readonly ["none", "private_key_jwt"]; type XaaClientAuthMethod = (typeof XAA_CLIENT_AUTH_METHODS)[number]; declare const DEFAULT_XAA_CLIENT_AUTH: XaaClientAuthMethod; /** Narrow a persisted or wire value to a supported CIMD client-auth method. */ declare function normalizeXaaClientAuth(value: unknown): XaaClientAuthMethod | undefined; /** The tamper modes the debugger can apply when minting a broken ID-JAG. */ declare const NEGATIVE_TEST_MODES: readonly ["valid", "bad_signature", "wrong_audience", "expired", "missing_claims", "invalid_type_header", "wrong_issuer", "resource_mismatch", "client_id_mismatch", "unknown_kid", "unknown_sub", "scope_denial"]; type NegativeTestMode = (typeof NEGATIVE_TEST_MODES)[number]; declare const DEFAULT_NEGATIVE_TEST_MODE: NegativeTestMode; declare function isNegativeTestMode(value: unknown): value is NegativeTestMode; /** Input axis: the identity assertion format minted at `/authenticate`. */ declare const IDENTITY_ASSERTION_FORMATS: readonly ["oidc", "saml"]; type IdentityAssertionFormat = (typeof IDENTITY_ASSERTION_FORMATS)[number]; declare const DEFAULT_IDENTITY_ASSERTION_FORMAT: IdentityAssertionFormat; /** * Narrow an arbitrary persisted/wire value to a known assertion format. * Returns undefined for anything unrecognized so callers fall back to the * safe default rather than trusting the wire. */ declare function normalizeIdentityAssertionFormat(value: unknown): IdentityAssertionFormat | undefined; /** Output axis: how the ID-JAG identifies the subject to the target RAS. */ declare const SUBJECT_IDENTIFIER_FORMATS: readonly ["oauth-sub", "saml-nameid"]; type SubjectIdentifierFormat = (typeof SUBJECT_IDENTIFIER_FORMATS)[number]; declare const DEFAULT_SUBJECT_IDENTIFIER_FORMAT: SubjectIdentifierFormat; /** Like {@link normalizeIdentityAssertionFormat}, for the output axis. */ declare function normalizeSubjectIdentifierFormat(value: unknown): SubjectIdentifierFormat | undefined; declare const REGISTRATION_STRATEGIES: readonly ["preregistered", "cimd", "dcr"]; type RegistrationStrategy = (typeof REGISTRATION_STRATEGIES)[number]; /** * A strategy the user may leave to the flow to resolve: "auto" picks * pre-registered credentials, CIMD, or DCR after the server is probed (see * `resolveAuthorizationPlan`). Persisted config stores a mode; running flows * resolve it to a concrete {@link RegistrationStrategy}. */ type RegistrationMode = "auto" | RegistrationStrategy; declare const DEFAULT_REGISTRATION_STRATEGY: RegistrationStrategy; declare const DEFAULT_REGISTRATION_MODE: RegistrationMode; /** * Narrow an arbitrary persisted/wire value (Convex returns a bare string) to a * known concrete strategy. Accepts the legacy `pre_registered` spelling as an * alias. Returns undefined for anything unrecognized — including "auto" — so * callers can fall back to the safe default rather than trusting the wire. */ declare function normalizeRegistrationStrategy(value: unknown): RegistrationStrategy | undefined; /** * Like {@link normalizeRegistrationStrategy} but also admits "auto". Use for * persisted config; use the strategy normalizer where a concrete choice is * required (e.g. the XAA debugger's run setup). */ declare function normalizeRegistrationMode(value: unknown): RegistrationMode | undefined; /** * How a server authenticates at connect time. "auto" selects XAA when the * server is XAA-configured and OAuth otherwise — a selection made before the * flow starts, never a fallback after a failed attempt. */ declare const AUTH_METHODS: readonly ["auto", "oauth", "xaa", "bearer", "none"]; type AuthMethod = (typeof AUTH_METHODS)[number]; declare function normalizeAuthMethod(value: unknown): AuthMethod | undefined; /** True when acceptance and rejection are both valid under deployment policy. */ declare function isPolicyDependentNegativeTestMode(mode: NegativeTestMode): boolean; declare const NEGATIVE_TEST_MODE_DETAILS: Record; /** * Canonicalize an MCP server URL into the `resource` identifier: origin + * pathname + query, preserving a trailing slash and the query (both can * distinguish resources), with the origin normalized by `URL`. */ declare function canonicalizeMcpResource(serverUrl: string): string; /** The AS-metadata document types, probed OAuth-first then OIDC. */ declare const XAA_AS_METADATA_NAMES: readonly ["oauth-authorization-server", "openid-configuration"]; /** * RFC 9728 protected-resource-metadata candidates for a resource (the MCP * server URL). PRM names the authorization server(s) protecting the resource. */ declare function buildProtectedResourceMetadataCandidates(resource: string): string[]; /** * RFC 8414 / OIDC authorization-server-metadata candidates for an issuer, in * probe order: for each document type (OAuth AS, then OIDC), the insertion, * append, and root forms. */ declare function buildAuthorizationServerMetadataCandidates(issuer: string): string[]; /** * OIDC configuration candidates for an issuer, used to locate the `jwks_uri` * when verifying that a (self-)issuer actually publishes its signing key. */ declare function buildIssuerPublicationCandidates(issuer: string): string[]; /** Evidence for whether a capability/extension is advertised by the AS/server. */ type XaaCapabilityEvidence = "advertised" | "not_advertised" | "unknown"; /** JSON-RPC id sent on the XAA debugger's `initialize`; the response must echo it. */ declare const MCP_INIT_ID = "mcpjam-xaa-cli"; /** Protocol version advertised in the params and the MCP-Protocol-Version header. */ declare const MCP_PROTOCOL_VERSION = "2025-11-25"; /** The MCP Enterprise-Managed Authorization extension key. */ declare const XAA_MCP_EXTENSION = "io.modelcontextprotocol/enterprise-managed-authorization"; interface McpInitializeRequest { headers: Record; body: { jsonrpc: "2.0"; id: string; method: "initialize"; params: Record; }; } /** * Build the authenticated MCP `initialize` request: the bearer + protocol * headers and a JSON-RPC body that advertises the enterprise-managed * authorization extension. Some 2025-11-25 servers enforce the * MCP-Protocol-Version header, so it is sent alongside the params version. */ declare function buildMcpInitializeRequest(accessToken: string): McpInitializeRequest; /** * Evaluate an `initialize` response body. Returns a failure reason, or undefined * when the server returned a genuine JSON-RPC initialize result (right protocol * version, echoed id, object result). Handles SSE envelopes, top-level proxy * error strings, JSON-RPC errors, and non-MCP 2xx bodies. */ declare function evaluateMcpInitializeResponse(body: unknown): string | undefined; /** * Whether the `initialize` result advertises the enterprise-managed * authorization extension in its capabilities. */ declare function mcpInitializeExtensionEvidence(body: unknown): XaaCapabilityEvidence; /** mcpProfile.extensions key holding the policy object. */ declare const XAA_ENTERPRISE_POLICY_EXTENSION = "com.mcpjam/enterprise-managed-auth"; /** IdPs the policy can mint through. v1: only the MCPJam test IdP. `own` * (bring-your-own, e.g. Okta) joins later, mirroring the reserved * `authServerMode: "own"` slot on server configs. */ declare const XAA_ENTERPRISE_POLICY_IDPS: readonly ["mcpjam"]; type XaaEnterprisePolicyIdp = (typeof XAA_ENTERPRISE_POLICY_IDPS)[number]; /** The stored/wire policy object. */ interface XaaEnterprisePolicy { idp: XaaEnterprisePolicyIdp; } /** * Three-state read. `invalid` is deliberately NOT `off`: a malformed object * or an unsupported `idp` (e.g. a future `"okta"` read by an older build) * means an admin believes enforcement is on — treating it as off would * silently un-enforce the policy. Connect surfaces turn `invalid` into an * explicit configuration error. */ type XaaEnterprisePolicyState = { kind: "off"; } | { kind: "on"; policy: XaaEnterprisePolicy; } | { kind: "invalid"; reason: string; }; /** Read the policy state from a host config's `mcpProfile` value. */ declare function readXaaEnterprisePolicy(mcpProfile: unknown): XaaEnterprisePolicyState; /** * Turn the policy on. Preserves every sibling profile field and extension; * an existing VALID policy value is kept as-is, an invalid one is repaired * to the default (the toggle is the recovery path for a broken value). * Accepts an absent profile and materializes the minimal envelope. */ declare function withXaaEnterprisePolicy(mcpProfile: Record | undefined): Record; /** * Turn the policy off. Touches ONLY the policy key: sibling extensions * survive, a now-empty `extensions` container is dropped, and a profile * left with nothing meaningful (only `profileVersion`) collapses to * `undefined` so pre-feature configs keep hashing byte-identically — * absence is semantic in the content-addressed hostConfig store. */ declare function withoutXaaEnterprisePolicy(mcpProfile: Record | undefined): Record | undefined; type XAAVendor = "okta" | "auth0" | "workos" | "stytch" | "keycloak" | "unknown"; type XAAVendorVerdict = "native" | "unknown" | "unsupported"; interface XAAVendorHint { vendor: XAAVendor; verdict: XAAVendorVerdict; note: string; } type XAACheckStatus = "pass" | "fail" | "warn" | "unknown"; interface XAACompatibilityCheck { id: "jwt_bearer_grant" | "token_endpoint" | "id_jag_profile" | "cimd_supported"; label: string; status: XAACheckStatus; detail: string; } type XAACompatibilityVerdict = "pass" | "fail" | "warn"; interface XAACompatibilityReport { overall: XAACompatibilityVerdict; checks: XAACompatibilityCheck[]; vendor: XAAVendor; vendorHint?: XAAVendorHint; } declare function detectVendor(issuer: string | undefined): XAAVendor; declare function analyzeAsCompatibility(authzMetadata: XAAFlowState["authzMetadata"]): XAACompatibilityReport | null; /** Flat ID-JAG-profile / jwt-bearer-grant advertisement evidence derived from * authorization-server metadata. Single source shared by the engine and the * CLI's result projection so the two never drift. */ declare function deriveCapabilityEvidence(authzMetadata: XAAFlowState["authzMetadata"]): { idJagProfile: XaaCapabilityEvidence; jwtBearerGrant: XaaCapabilityEvidence; }; /** Select the token-endpoint client-auth method for the jwt-bearer redemption. * Precedence: an explicit choice wins; with no client secret the client is * public (`none`); otherwise prefer the advertised method * (basic → post → none); when metadata is absent, default to * `client_secret_post`. Single source shared by the engine (pre-registered * discovery) and the CLI result projection. */ declare function selectTokenEndpointAuthMethod(explicit: XaaTokenEndpointAuthMethod | undefined, clientSecret: string | undefined, advertised: unknown): XaaTokenEndpointAuthMethod; type XAAFlowStep = "idle" | "discover_resource_metadata" | "received_resource_metadata" | "discover_authz_metadata" | "received_authz_metadata" | "request_client_registration" | "received_client_credentials" | "fetch_client_metadata_document" | "received_client_metadata" | "user_authentication" | "received_identity_assertion" | "token_exchange_request" | "received_id_jag" | "inspect_id_jag" | "jwt_bearer_request" | "received_access_token" | "authenticated_mcp_request" | "complete"; /** Token-endpoint client-auth methods the debugger can actually redeem. */ type XaaTokenEndpointAuthMethod = "client_secret_post" | "client_secret_basic" | "none" | "private_key_jwt"; type XaaRegistrationWarningCode = "public_client" | "profile_metadata_not_echoed" | "grant_types_not_echoed" | "token_exchange_grant_not_echoed" | "auth_method_not_echoed" | "missing_secret_expiry" | "non_201_success" | "non_json_content_type" | "missing_no_store"; /** Non-blocking readiness/conformance finding from DCR or CIMD setup. * Never carries credential material. */ interface XaaRegistrationWarning { code: XaaRegistrationWarningCode; message: string; } /** DCR-minted credentials. Live only in the target-scoped in-memory session * cache — never in XAAFlowState, storage, history, or logs. */ interface XaaEphemeralDcrCredentials { clientId: string; clientSecret?: string; tokenEndpointAuthMethod: XaaTokenEndpointAuthMethod; /** RFC 7591 client_secret_expires_at (seconds since epoch; 0 = never). */ clientSecretExpiresAt?: number; registrationEndpoint: string; } interface XaaDcrCredentialCache { get(key: string): XaaEphemeralDcrCredentials | undefined; set(key: string, value: XaaEphemeralDcrCredentials): void; delete(key: string): void; } /** Build the stable, target-scoped key used for session-only DCR credentials. */ declare function buildXaaDcrCredentialCacheKey(args: { targetKey: string; registrationEndpoint: string; scope?: string | null; }): string; /** True only when a confidential DCR credential has a finite, elapsed expiry. */ declare function isXaaDcrClientSecretExpired(credentials: Pick, nowSeconds?: number): boolean; interface XAAJWTInspectionIssue { section: "header" | "payload" | "signature"; field: string; label: string; expected: string; actual: string; } interface XAADecodedJwt { header: Record | null; payload: Record | null; signature: string; issues: XAAJWTInspectionIssue[]; } interface XAAInfoLogEntry { id: string; step: XAAFlowStep; label: string; data: any; timestamp: number; level: InfoLogLevel; error?: LogErrorDetails; } interface XAAHttpHistoryEntry { step: XAAFlowStep; timestamp: number; duration?: number; request: { method: string; url: string; headers: Record; body?: any; }; response?: { status: number; statusText: string; headers: Record; body: any; }; error?: LogErrorDetails; } interface XAAFlowState { isBusy: boolean; currentStep: XAAFlowStep; serverUrl?: string; resourceUrl?: string; resourceMetadataUrl?: string; resourceMetadata?: { resource?: string; authorization_servers?: string[]; bearer_methods_supported?: string[]; scopes_supported?: string[]; }; authzServerIssuer?: string; authzMetadata?: { issuer: string; token_endpoint?: string; registration_endpoint?: string; grant_types_supported?: string[]; response_types_supported?: string[]; scopes_supported?: string[]; token_endpoint_auth_methods_supported?: string[]; authorization_grant_profiles_supported?: string[]; client_id_metadata_document_supported?: boolean; }; tokenEndpoint?: string; /** The mock IdP issuer expected in the ID-JAG `iss` claim. */ issuerBaseUrl?: string; negativeTestMode: NegativeTestMode; /** Input axis: assertion format the mock IdP mints and the exchange * presents. Sticky like negativeTestMode — the UI resets the flow to * change it. */ identityAssertionFormat: IdentityAssertionFormat; /** Output axis: whether the ID-JAG carries a saml-nameid `sub_id`. Sticky * like negativeTestMode. Independent of the input axis. */ subjectIdentifierFormat: SubjectIdentifierFormat; /** Structured subject metadata from a SAML `/authenticate` response, for * UI display — the client never parses assertion XML. */ identityAssertionSubject?: { issuer: string; nameid: string; nameidFormat?: string; spNameQualifier?: string; }; userId?: string; email?: string; clientId?: string; /** Test-credential secret for the jwt-bearer grant; never rendered — the * logged copy of any request carrying it is masked. */ clientSecret?: string; scope?: string; identityAssertion?: string; idJag?: string; idJagDecoded?: XAADecodedJwt | null; accessToken?: string; tokenType?: string; expiresIn?: number; /** The `scope` the authorization server actually granted on the jwt-bearer * token response, when it returned one. Distinct from `scope` (requested): * a narrower grant is how a RAS downscopes a subject per its own policy. */ grantedScope?: string; lastRequest?: { method: string; url: string; headers: Record; body?: any; }; lastResponse?: { status: number; statusText: string; headers: Record; body: any; }; httpHistory?: Array; infoLogs?: Array; error?: string; /** Outcome of a deliberately-broken (negative-mode) run once it reaches the * authorization server. `rejected` is the success case — the server caught * the broken assertion; `accepted` is the security risk — it issued a token * anyway. Unset for the happy-path (valid) flow. */ negativeProbe?: { outcome: "rejected" | "accepted"; status?: number; }; compatibilityReport?: XAACompatibilityReport; /** How this run's client identity is established. Authoritative once the * machine is initialized — the UI must reset the flow to change it. */ registrationStrategy: RegistrationStrategy; /** How the jwt-bearer redemption authenticates at the token endpoint. * Unset = legacy body-post behavior. */ tokenEndpointAuthMethod?: XaaTokenEndpointAuthMethod; /** True when a DCR run reused this browser session's earlier registration * instead of POSTing again. */ dcrRegistrationReused?: boolean; /** Non-blocking readiness/conformance findings from DCR/CIMD setup. */ registrationWarnings?: XaaRegistrationWarning[]; /** Set after a DCR POST whose outcome left no reusable registration: a * retry may create a second remote client and needs explicit confirmation. */ dcrRetryMayCreateDuplicate?: boolean; /** Managed-IdP policy ruling for this run's ID-JAG mint. Set only when the * run carried a managed context (config `policyMode` present) — legacy and * unmanaged-context-free runs never fabricate policy state. Carries codes * and scope strings only — never tokens. */ idpPolicy?: { outcome: "granted" | "downscoped" | "denied"; /** Allowlisted OAuth error code from a policy denial (or evaluator * outage). Anything else the issuer returns is dropped, not echoed. */ errorCode?: "access_denied" | "invalid_target" | "invalid_client" | "invalid_scope" | "temporarily_unavailable"; /** Short value-free reason enum from the issuer's `error_description` * (truncated); e.g. `not_assigned`, `identity_suspended`. */ reasonCode?: string; requestedScope?: string; grantedScope?: string; }; } interface XAARequestResult { status: number; statusText: string; headers: Record; body: any; ok: boolean; } interface XAAExternalRequestOptions { /** Enforce the private-host / DNS-resolution SSRF guard for this request at * fetch time, regardless of the executor's `httpsOnly` default. Used for the * caller-influenced CIMD document fetch, which must always target a public * host — closing the DNS-rebinding window a one-time upstream check leaves. */ enforcePublicHost?: boolean; } interface XAARequestExecutor { internalRequest: (path: string, init?: RequestInit) => Promise; externalRequest: (url: string, init?: RequestInit, options?: XAAExternalRequestOptions) => Promise; } interface BaseXAAStateMachineConfig { /** Initial state. Optional — prefer `getState` so the machine never holds a * stale snapshot read during render. */ state?: XAAFlowState; getState?: () => XAAFlowState; updateState: (updates: Partial) => void; serverUrl: string; issuerBaseUrl: string; /** Path prefix for the mint endpoints (`/authenticate`, `/token-exchange`), * e.g. `/o/` for the hosted org-scoped issuer. Never applied to the * token proxy, which has no scoped variant. Defaults to "" (unscoped). */ mintPathPrefix?: string; /** LOCAL runs only: "hosted" adds `issuerMode`/`organizationId` to the mint * request bodies so the local server forwards them to the hosted issuer. */ issuerMode?: "local" | "hosted"; organizationId?: string | null; /** Managed-IdP policy context for org-registered resource-app runs. When * set, the ID-JAG mint carries it (headers on the spec `/token` form, body * fields on the legacy JSON mint) so the org-scoped issuer can enforce * per-person policy. Keyed on PRESENCE, independent of `issuerMode` — a * direct hosted run keeps `issuerMode` "local". Unset ⇒ legacy behavior, * byte-identical mint requests. */ policyMode?: "managed" | "unmanaged"; /** The org synthetic person this managed run acts as. */ testIdentityId?: string; /** The registered resource app the managed run targets. */ resourceAppId?: string; /** Scoped issuer flavor for hosted forwards: "org" (/o/, signed-in * members) or "anonymous" (/g/, the anonymous test issuer a * RAS must explicitly allowlist). Defaults to "org". */ issuerKind?: "org" | "anonymous"; /** Whether the issuer accepts the RFC 8693 grant at `/token`. Hosted * unscoped issuers use the JSON mint fallback. Defaults to true. */ specTokenEndpointAvailable?: boolean; requestExecutor: XAARequestExecutor; scheduleAutoAdvance?: (next: () => void) => void; negativeTestMode?: NegativeTestMode; /** Input axis (see XAAFlowState.identityAssertionFormat). */ identityAssertionFormat?: IdentityAssertionFormat; /** Output axis (see XAAFlowState.subjectIdentifierFormat). */ subjectIdentifierFormat?: SubjectIdentifierFormat; userId?: string; email?: string; clientId?: string; clientSecret?: string; scope?: string; authzServerIssuer?: string; /** Hosted registration-backed runs: sent to the token proxy instead of an * inline client secret; the server resolves the stored secret and forces * the outbound URL to the registration's stored token endpoint. */ registrationId?: string; /** Server-target confidential runs: sent to the token proxy instead of an * inline client secret or token endpoint. The server resolves the stored * secret AND discovers the token endpoint from the server's own config, so * neither the secret nor the destination rides in from the browser. */ serverId?: string; projectId?: string; /** Client-identity strategy. Forced to "preregistered" whenever * registrationId or serverId is set (those paths skip AS discovery). */ registrationStrategy?: RegistrationStrategy; /** Target-scoped in-memory session cache for DCR-minted credentials. The * UI passes a useRef-backed cache so machine recreation neither loses nor * re-exposes the secret; unit tests may pass a Map-backed one. */ dcrCredentialCache?: XaaDcrCredentialCache; /** Stable key for the current target; combined with the discovered * registration endpoint to key the credential cache. */ dcrCacheTargetKey?: string; /** CIMD: the Client ID Metadata Document URL to present as the client_id. * Defaults to the hosted XAA debugger document. Validated, never normalized. */ clientIdMetadataUrl?: string; /** Local-dev-only opt-in: permit an http:// loopback CIMD document URL and * skip the fetch-time public-host guard for it. Never affects remote URLs. */ allowLoopbackClientMetadata?: boolean; } interface XAAStateMachine { state: XAAFlowState; updateState: (updates: Partial) => void; proceedToNextStep: () => Promise; /** Drive every remaining step until the flow completes or a step fails. */ runAll: () => Promise; resetFlow: () => void; } declare const EMPTY_XAA_FLOW_STATE: XAAFlowState; declare function createInitialXAAFlowState(overrides?: Partial): XAAFlowState; type ConformanceSuiteId = "protocol" | "apps" | "oauth" | "tasks"; interface ConformanceSupport { /** Whether the suite can run against the given server config. */ supported: boolean; /** Human-readable reason surfaced when `supported` is false. */ reason?: string; } /** * Narrow an arbitrary MCPServerConfig (or absent config) to the HTTP variant. * Accepts `null`/`undefined` so callers can feed values from loading states * without pre-checking — those just return `false`. */ declare function isHttpServerConfig(config: MCPServerConfig | null | undefined): config is HttpServerConfig; /** * Centralises the "which conformance suite supports which transport" rules so * UI guards, server routes, and CLI commands can't drift. The check is pure — * it inspects config shape only, not network state. `null`/`undefined` configs * are treated as unsupported with a friendly reason instead of throwing. */ declare function canRunConformance(suite: ConformanceSuiteId, config: MCPServerConfig | null | undefined): ConformanceSupport; /** * The conformance PROFILE: the frozen set of check ids a score is computed * over, plus the identity stamp that says which set a given number came from. * * WHY A PROFILE AND NOT JUST A VERSION NUMBER. * * `MCPConformanceResult` used to record `protocolVersion` and the check * results, and nothing else. That makes two scores from two builds * incomparable BY CONSTRUCTION, for two independent reasons: * * 1. The DENOMINATOR floats with the server's advertised surface. A server * that starts advertising `resources` makes more checks applicable, so * `13/13` and `15/15` are both "100%" of different questions. Nothing in * the result said which questions were asked. * 2. The check INVENTORY grows. Adding a MUST check is the correct response * to reading the spec more carefully, but it silently re-grades every * server that was previously green — the score moved because we learned * something, not because the server changed. `protocolVersion + * checkerVersion` cannot separate those two: both change for both * reasons. * * A profile fixes (2) and makes (1) legible. It is a per-revision manifest of * the check ids that COUNT toward a score. A check outside the manifest still * runs, still appears in the report, and still shows a real verdict — but it * lands in the {@link ConformanceScore.pending} bucket and is excluded from * `applicable`, so it can neither move a number nor fail a build. It becomes * scored only when a NEW profile version says so, which is a deliberate, * reviewable act rather than a side effect of merging a check. * * That is what makes a run comparable to the previous one: same profile * version ⇒ same questions asked ⇒ the delta is the server's. * * SELF-DESCRIBING ON PURPOSE. The stamp carries `pendingCheckIds`, so a * serialized result partitions the same way for any reader — including a * stored report opened by a build that never heard of this profile version. * The registry below is the authority at WRITE time; the stamp is the * authority at READ time. A result with no stamp has no pending bucket at all, * which is byte-identical to the pre-profile behavior. * * Pure data reasoning — no MCP client, no transport, no Node built-ins — so it * is exported from the browser entry alongside the score and the catalog. */ /** * The build that produced a result. Distinct from the profile version on * purpose: a patch that fixes a check's ASSERTION moves this and not the * profile, and that difference is exactly what a reader needs to tell "the * server changed" from "we fixed a false positive". */ declare const CONFORMANCE_CHECKER_VERSION: string; /** * Profiles are per-SUITE, because the suites have independent inventories and * independent reasons to move. Pooling them under one id would make a tasks * check addition bump the protocol denominator. */ declare const CONFORMANCE_PROFILE_IDS: readonly ["mcp-protocol", "mcp-tasks"]; type ConformanceProfileId = (typeof CONFORMANCE_PROFILE_IDS)[number]; interface ConformanceProfile { id: ConformanceProfileId; /** * Monotonic, human-readable revision. Dated rather than a bare integer so a * report says WHEN the requirement set was frozen without a lookup table. */ version: string; /** * The frozen manifest: every check id this profile version SCORES. Order is * irrelevant to membership but is canonicalized for the digest. */ scored: readonly string[]; } declare function conformanceProfile(id: ConformanceProfileId): ConformanceProfile; declare function conformanceProfileDigest(profile: ConformanceProfile): string; /** * The identity a run stamps onto its result: which questions were asked, by * which build, against which revisions and schemas. * * Every field is additive over the pre-profile result shape, and the whole * object is optional — a consumer that predates it (the CLI reporters, the * inspector panels, the hand-mirrored backend summary) keeps working unchanged, * and a stored report written before this existed still renders. */ interface ConformanceProfileStamp { profileId: ConformanceProfileId; profileVersion: string; /** @see {@link conformanceProfileDigest} */ manifestDigest: string; /** @see {@link CONFORMANCE_CHECKER_VERSION} */ checkerVersion: string; /** The revision the run judged against, when it established one. */ protocolVersion?: string; /** * Extension id → the version the run negotiated, for extensions that were in * play. An extension changes what a result is allowed to look like, so a * score computed with one negotiated is not the same measurement as one * without. */ extensionVersions?: Record; /** * Digest of the wire schemas the run validated against. Set by the * wire-schema check; absent when the run performed no schema validation. */ schemaDigest?: string; /** * Check ids present in the result that this profile version does NOT score. * Read at scoring time so the partition is reproducible from the result * alone. Empty array ⇒ every reported check was scored. */ pendingCheckIds: string[]; } /** The minimum a check must expose to be partitioned against a profile. */ interface ProfileCheckLike { id: string; } /** * Which of `checks` this profile scores, and which are pending. * * Used at WRITE time (the runner, which holds the manifest). Read-time callers * use {@link partitionByStamp}, which needs only the serialized result. */ declare function partitionByProfile(checks: readonly T[], profile: ConformanceProfile): { scored: T[]; pending: T[]; }; /** * Which of `checks` a stamped result scores, and which are pending. * * An absent stamp means the result predates profiles: everything is scored and * nothing is pending, which reproduces the pre-profile arithmetic exactly. */ declare function partitionByStamp(checks: readonly T[], stamp: Pick | undefined): { scored: T[]; pending: T[]; }; /** * Build the stamp for a run. `checks` is whatever the run reported, so the * pending list names the ids that were actually present rather than every id * the inventory could produce. */ declare function buildConformanceProfileStamp(input: { profile: ConformanceProfile; checks: readonly ProfileCheckLike[]; protocolVersion?: string; extensionVersions?: Record; schemaDigest?: string; }): ConformanceProfileStamp; /** Every check id the profile's inventory can produce that it does not score. */ declare function unscoredCheckIds(profile: ConformanceProfile): string[]; declare const MCP_CHECK_CATEGORIES: readonly ["core", "protocol", "tools", "prompts", "resources", "security", "transport"]; type MCPCheckCategory = (typeof MCP_CHECK_CATEGORIES)[number]; declare const MCP_CHECK_IDS: readonly ["server-initialize", "ping", "logging-set-level", "completion-complete", "capabilities-consistent", "tools-list", "tools-input-schemas-valid", "tools-x-mcp-header-declarations-valid", "prompts-list", "resources-list", "protocol-invalid-method-error", "localhost-host-rebinding-rejected", "localhost-host-valid-accepted", "server-sse-polling-session", "server-accepts-multiple-post-streams", "server-sse-streams-functional", "notification-post-accepted", "get-stream-or-405", "session-id-visible-ascii", "post-response-content-type", "modern-client-handshake", "modern-server-discover", "modern-result-type-present", "modern-cacheable-result-hints", "modern-cache-hint-coverage", "modern-cache-hint-values-valid", "modern-cache-scope-stable-across-pages", "modern-protocol-version-header-mismatch", "modern-method-header-mismatch", "modern-name-header-mismatch", "modern-unsupported-version-error", "modern-missing-method-header-rejected", "modern-header-names-case-insensitive", "modern-undeclared-capability-error", "modern-no-session-id", "modern-removed-methods-not-found", "modern-resource-not-found-invalid-params", "modern-resource-read-no-empty-contents", "modern-tool-output-schema-conformant", "modern-logs-require-log-level", "modern-subscription-ack-precedes-notifications", "modern-subscription-filter-and-tagging", "modern-subscription-graceful-close", "wire-schema-valid"]; type MCPCheckId = (typeof MCP_CHECK_IDS)[number]; type MCPCheckStatus = "passed" | "failed" | "skipped"; /** @see {@link ConformanceSkipReason} — the vocabulary is shared by every suite. */ type MCPCheckSkipReason = ConformanceSkipReason; /** @see {@link ConformanceRunOutcome} — the vocabulary is shared by every suite. */ type MCPRunOutcome = ConformanceRunOutcome; /** * Protocol era a conformance run targets. Derived once in * {@link NormalizedMCPConformanceConfig} from the pinned `protocolVersion` * (via `isStatelessProtocolVersion`): a stateless/2026-era pin ⇒ `"modern"`, * an absent or stateful pin ⇒ `"legacy"`. An absent pin therefore reproduces * byte-identical legacy behavior. */ type MCPCheckEra = "legacy" | "modern"; /** * Era membership for a check: a NON-EMPTY list. Typing it as a non-empty tuple * is the second half of the §15.1 exhaustiveness guarantee — `CHECK_ERAS` is * already total over `MCPCheckId`, and this makes `[]` (a check that silently * applies to no era, i.e. is dead everywhere) a compile error too. */ type MCPCheckEras = readonly [MCPCheckEra, ...MCPCheckEra[]]; /** * Era of every protocol version the SDK can be pinned to — the version half of * the §15.1 registry. * * `satisfies Record` makes this map TOTAL over * `MCP_PROTOCOL_VERSIONS`: adding a supported protocol version is therefore a * compile-time obligation to state which era's check set it runs, instead of * silently inheriting "unknown ⇒ legacy". Derivation from * `isStatelessProtocolVersion` is deliberately NOT used here — that predicate * answers "not on the stateful list", which is the wrong default for a version * nobody has classified yet. */ declare const PROTOCOL_VERSION_ERAS: { readonly "2025-03-26": "legacy"; readonly "2025-06-18": "legacy"; readonly "2025-11-25": "legacy"; readonly "2026-07-28": "modern"; }; /** Every classified protocol version, for exhaustiveness assertions. */ declare const MCP_PROTOCOL_VERSION_ERA_IDS: McpProtocolVersion[]; /** * Single source of truth mapping each check to the eras it applies to. * Consumed by BOTH tracks — the client-backed checks (via `eraGate` in the * runner) and the raw-HTTP checks (protocol/security/transport runners) — * so era membership is never duplicated or allowed to drift between them. * Phase 7 (modern MUST checks) reuses this map rather than re-deriving. * * Classification rationale: * - Legacy-only checks assert 2025-era wire mechanics that do not exist in * the sessionless 2026 era: the `initialize` handshake * (`server-initialize`), the stateful session id + concurrent POST/SSE * stream semantics (`server-sse-*`, `server-accepts-multiple-post-streams`), * and consistency/health probes (`capabilities-consistent`, `ping`) whose * modern equivalents are Phase 7 work. * - Both-era checks either self-skip on an unadvertised capability * (`logging-set-level`, `completion-complete`) or assert primitive * surface / generic JSON-RPC behavior that is era-agnostic * (`tools-list`, `tools-input-schemas-valid`, `prompts-list`, * `resources-list`, `protocol-invalid-method-error`). * - `tools-x-mcp-header-declarations-valid` is modern-only WITHOUT a * `modern-` prefix: it belongs to the tools family (it judges tool * definitions a `tools/list` already returned, sending no probe), but * `x-mcp-header` has no meaning before 2026-07-28, so asserting it on a * 2025 run would invent a requirement the revision never stated. * * The two `localhost-host-*` security checks are deliberately legacy-only for * now: their raw modern host-header probe could not be validated against the * dual-era fixture (which leaves DNS-rebinding protection disabled), so per * the Phase 3 safety valve they are downgraded to a safe skip on a modern run * rather than shipped as a fragile probe. * * Phase 7 additions: * - The `modern-*` checks are modern-only. On a legacy run they era-skip, so * a legacy report keeps exactly the statuses it had before this phase. * - `capabilities-consistent` is promoted to BOTH eras: the requirement * ("what you advertise is what you expose") is unchanged by the era, only * the transport that carries the advertisement is (initialize ⇒ * server/discover), so the id is preserved rather than renamed. * - `server-initialize` and `ping` STAY legacy-only: `initialize` and `ping` * were removed from the 2026 wire (a modern server answers -32601), so on * a modern run they are era-skipped and `modern-client-handshake` / * `modern-removed-methods-not-found` carry the equivalent evidence. * - The three `modern-subscription-*` checks are modern-only for the * strongest possible reason: `subscriptions/listen` does not exist on the * 2025 wire at all, so there is nothing for them to assert on a legacy * run. They observe ONE real listen stream (see `raw-listen.ts`) and skip * — never fail — when the server advertises nothing subscribable, refuses * to open a stream, or keeps the subscription open past the observation * window (a graceful close is server-initiated and cannot be induced by a * client-side probe). */ declare const CHECK_ERAS: Record; interface MCPCheckResult { id: MCPCheckId; category: MCPCheckCategory; title: string; description: string; status: MCPCheckStatus; /** Always set when `status` is `"skipped"`. */ skipReason?: MCPCheckSkipReason; durationMs: number; error?: { message: string; details?: unknown; }; details?: Record; } interface MCPConformanceConfig { serverUrl: string; accessToken?: string; customHeaders?: Record; checkTimeout?: number; categories?: MCPCheckCategory[]; checkIds?: MCPCheckId[]; fetchFn?: typeof fetch; clientName?: string; /** * Pinned MCP protocol version. Absent ⇒ legacy era, byte-identical to the * pre-era-awareness behavior. A known stateless value (e.g. `"2026-07-28"`) * selects the modern era: the client connects through the official Client's * version negotiation and era-scoped checks apply per {@link CHECK_ERAS}. * Validated at normalization via `isKnownProtocolVersion`. */ protocolVersion?: McpProtocolVersion; /** * Tool the `modern-undeclared-capability-error` check may call to make the * server attempt an `input_required` round trip (MCP 2026-07-28 §12). * * Opt-in on purpose: the check has to actually EXECUTE a tool, and no * advertised metadata says which tool will ask for input, so guessing would * mean firing arbitrary side-effecting tools at the server under test. * Absent ⇒ the check reports a skip explaining what it needs. */ inputRequiredProbe?: { toolName: string; arguments?: Record; }; /** * Tool the `modern-logs-require-log-level` check may call to make the server * actually EMIT log records. Opt-in for the same reason as * {@link MCPConformanceConfig.inputRequiredProbe}: no metadata says which * tool logs, and the check must not fire arbitrary side-effecting tools. * * Absent ⇒ the check still asserts the MUST against an ordinary request * (any log record on a level-less request is a violation), but it cannot * show the server logs at all, so the evidence is weaker. */ logProbe?: { toolName: string; arguments?: Record; }; /** * Operator-supplied primitives that are SAFE TO EXECUTE, generalizing the * opt-in pattern {@link MCPConformanceConfig.inputRequiredProbe} and * {@link MCPConformanceConfig.logProbe} already use. * * WHY THIS IS OPT-IN AND STAYS OPT-IN. A whole family of requirements can * only be observed on a result the server produces by DOING something — a * tool's declared `outputSchema` binds its `structuredContent`, and * `CallToolResult` / `GetPromptResult` have shapes no listing can show. A * default run cannot reach any of it, because nothing in a tool's advertised * metadata says whether calling it charges a card or deletes a row, and a * conformance run that guessed would be an outage waiting to happen. * * So the operator names what is safe. Absent ⇒ the fixture-gated checks * report a skip that says exactly what they need, and the default run * behaves as it always has: no arbitrary tool is ever called. * * The probes also flow into the run-wide wire record, so supplying them * widens `wire-schema-valid`'s coverage to `CallToolResult` and * `GetPromptResult` — result shapes an unfixtured run never sees at all. */ fixtures?: MCPConformanceFixtures; } /** @see {@link MCPConformanceConfig.fixtures} */ interface MCPConformanceFixtures { /** `tools/call` targets the operator declares safe to execute. */ toolCalls?: Array<{ toolName: string; arguments?: Record; }>; /** `prompts/get` targets the operator declares safe to render. */ promptGets?: Array<{ promptName: string; arguments?: Record; }>; } /** * What the client phase found on the server, handed to the raw track and the * readiness pass so neither has to re-discover it (and so a raw probe targets * a REAL primitive instead of a guessed name). */ interface MCPServerSurfaceSnapshot { tools: Tool[]; toolNames: string[]; promptNames: string[]; resourceUris: string[]; resourceTemplateUris: string[]; serverCapabilities?: Record; } /** Readiness advice ids (Phase 7 §15.4). Never part of the pass/fail verdict. */ declare const MCP_READINESS_IDS: readonly ["readiness-tool-order-deterministic", "readiness-metadata-quality", "readiness-deprecated-feature-use", "readiness-cache-ttl-useful", "readiness-oauth-iss-advertised", "readiness-x-mcp-header-declarations", "readiness-parse-error-handling", "readiness-session-termination", "readiness-protocol-version-header-required", "readiness-resource-error-echoes-uri"]; type MCPReadinessId = (typeof MCP_READINESS_IDS)[number]; /** * How strongly the spec states the advice. Everything on the readiness channel * is SHOULD/RECOMMENDED/MAY strength by construction — a MUST belongs in * {@link MCP_CHECK_IDS}, where it can fail the run. */ type MCPReadinessSpecStrength = "SHOULD" | "RECOMMENDED" | "MAY"; interface MCPReadinessWarning { id: MCPReadinessId; title: string; /** Always `"warning"`: the readiness channel has no failure severity. */ severity: "warning"; specStrength: MCPReadinessSpecStrength; message: string; details?: Record; /** * Report this observation without deducting from the score. * * The readiness channel normally costs points, because a SHOULD a server * ignores is a real (if non-fatal) shortfall. Some observations are not that: * the behavior they describe is either EXPLICITLY PERMITTED by the spec (a * MAY the server is entitled to take) or rests on a non-normative example. * Deducting for those invents a requirement — the same over-strictness this * program exists to remove, moved from the verdict onto the number. * * Absent ⇒ scored, so every existing warning keeps its deduction. */ informational?: boolean; } interface MCPConformanceResult { /** * True ONLY when `outcome` is `"passed"`: every selected check either ran and * passed or was inapplicable to this server. A check that could not run keeps * this false, so a skip can never add up to a green run. */ passed: boolean; outcome: MCPRunOutcome; /** * Present when `outcome` is `"incomplete"`: which checks did not run and what * the caller has to change to make them run. */ incompleteReason?: string; serverUrl: string; /** * The revision this run was judged against: the caller's pin, or the * version the server negotiated when the run connected without one. Absent * when an unpinned run never connected (raw-only selection) — the run * established no version, and a score label must not invent one. */ protocolVersion?: McpProtocolVersion; checks: MCPCheckResult[]; summary: string; durationMs: number; categorySummary: Record; /** * Interoperability advice observed during the run (Phase 7 §15.4). These are * NON-MUST findings: they are reported so a server author can improve * real-world interop, and they NEVER affect `passed` or any check status. */ readiness: MCPReadinessWarning[]; /** * WHICH QUESTIONS THIS RUN ASKED, and which build asked them — the frozen * scored-check manifest, the checker version, the revisions and (from the * wire-schema check) the schema digest. See `conformance-profile.ts`. * * Without it, `protocolVersion` + the check list were the only identity a * result carried, and two scores from two builds were incomparable by * construction: the check inventory grows, and a growing inventory silently * re-grades servers that never changed. * * Optional so every existing consumer and every stored report still reads; * a result without it has no pending bucket, which is exactly the * pre-profile behavior. */ profile?: ConformanceProfileStamp; } interface MCPConformanceSuiteConfig { name?: string; serverUrl: string; defaults?: Partial>; runs: Array> & { label?: string; }>; } interface MCPConformanceSuiteResult { name: string; serverUrl: string; passed: boolean; results: Array; summary: string; durationMs: number; } declare const MCP_APPS_CHECK_CATEGORIES: readonly ["tools", "resources"]; type MCPAppsCheckCategory = (typeof MCP_APPS_CHECK_CATEGORIES)[number]; declare const MCP_APPS_CHECK_IDS: readonly ["ui-tools-present", "ui-tool-metadata-valid", "ui-tool-input-schema-valid", "ui-listed-resources-valid", "ui-resources-readable", "ui-resource-contents-valid", "ui-resource-meta-valid"]; type MCPAppsCheckId = (typeof MCP_APPS_CHECK_IDS)[number]; type MCPAppsCheckStatus = "passed" | "failed" | "skipped"; /** @see {@link ConformanceSkipReason} — the vocabulary is shared by every suite. */ type MCPAppsSkipReason = ConformanceSkipReason; /** @see {@link ConformanceRunOutcome} — the vocabulary is shared by every suite. */ type MCPAppsRunOutcome = ConformanceRunOutcome; interface MCPAppsCheckResult { id: MCPAppsCheckId; category: MCPAppsCheckCategory; title: string; description: string; status: MCPAppsCheckStatus; /** Always set when `status` is `"skipped"`. */ skipReason?: MCPAppsSkipReason; durationMs: number; error?: { message: string; details?: unknown; }; details?: Record; warnings?: string[]; } type MCPAppsConformanceConfig = MCPServerConfig & { checkIds?: MCPAppsCheckId[]; }; type MCPAppsConformanceSuiteDefaults = Partial>; type MCPAppsConformanceSuiteRun = Partial> & { label?: string; }; interface MCPAppsResourceReadOutcome { uri: string; referencedByTools: string[]; listed: boolean; result?: MCPReadResourceResult; error?: unknown; } interface MCPAppsConformanceResult { /** * True ONLY when `outcome` is `"passed"`: every selected check either ran and * passed or was inapplicable to this server. A check that could not run keeps * this false, so a skip can never add up to a green run. */ passed: boolean; outcome: MCPAppsRunOutcome; /** * Present when `outcome` is `"incomplete"`: which checks did not run and what * the caller has to change to make them run. */ incompleteReason?: string; target: string; /** * The revision the run's connection negotiated (or the caller pinned). * Absent when the connection never succeeded — the run established no * version, and a score label must not invent one. */ protocolVersion?: string; checks: MCPAppsCheckResult[]; summary: string; durationMs: number; categorySummary: Record; discovery: { toolCount: number; uiToolCount: number; listedResourceCount: number; listedUiResourceCount: number; checkedUiResourceCount: number; }; } interface MCPAppsConformanceSuiteConfig { name?: string; target: MCPServerConfig; defaults?: MCPAppsConformanceSuiteDefaults; runs: MCPAppsConformanceSuiteRun[]; } interface MCPAppsConformanceSuiteResult { name: string; target: string; passed: boolean; results: Array; summary: string; durationMs: number; } declare const MCP_TASKS_CHECK_CATEGORIES: readonly ["dispatch", "creation", "lifecycle"]; type MCPTasksCheckCategory = (typeof MCP_TASKS_CHECK_CATEGORIES)[number]; declare const MCP_TASKS_CHECK_IDS: readonly ["tasks-wire-resolvable", "tasks-declaration-hygiene", "tasks-result-type-discipline", "tasks-undeclared-creation-refused", "tasks-undeclared-capability-rejected", "tasks-ttl-shape", "tasks-inline-result", "tasks-mcp-name-routing", "tasks-invalid-task-id-rejected", "tasks-status-payload-shape", "tasks-cancel-ack-shape", "tasks-input-required-update-completes", "tasks-ttl-integer-shape", "tasks-undeclared-capability-names-requirements"]; type MCPTasksCheckId = (typeof MCP_TASKS_CHECK_IDS)[number]; type MCPTasksCheckStatus = "passed" | "failed" | "skipped"; /** * Why a selected check produced no verdict. Every `skipped` check carries one, * and the two are NOT interchangeable: * * - `"not-applicable"` — the check cannot apply to THIS server, so there is * nothing to establish: an extension-only check on a legacy connection, the * `Mcp-Name` check on stdio, any task check on a connection with no tasks * wire. A run may still pass with these. * - `"could-not-run"` — the check DOES apply and was selected, but the run * could not exercise it: no probe tool resolved, the probe tool is not * listed, no task was created, the task never became readable. The run is * then `incomplete`, never `passed` — "did not run" must never be read as * "conformed". */ /** @see {@link ConformanceSkipReason} — the vocabulary is shared by every suite. */ type MCPTasksSkipReason = ConformanceSkipReason; /** * A run's verdict. * * - `"passed"` — every selected, applicable check ran and passed. * - `"failed"` — at least one check produced a violation. * - `"incomplete"` — nothing failed, but at least one selected check could * not be run, so the run does not establish conformance. */ /** @see {@link ConformanceRunOutcome} — the vocabulary is shared by every suite. */ type MCPTasksRunOutcome = ConformanceRunOutcome; interface MCPTasksCheckResult { id: MCPTasksCheckId; category: MCPTasksCheckCategory; title: string; description: string; status: MCPTasksCheckStatus; /** Always set when `status` is `"skipped"`. */ skipReason?: MCPTasksSkipReason; durationMs: number; error?: { message: string; details?: unknown; }; details?: Record; warnings?: string[]; } type MCPTasksConformanceConfig = MCPServerConfig & { checkIds?: MCPTasksCheckId[]; /** * Tool used to provoke a task. Without it the runner picks the first tool * whose `execution.taskSupport` is `required`, then the first with * `optional` — 2025-11-25 metadata that the 2026-07-28 `ToolSchema` strips, * so auto-selection cannot work on the extension wire. When no probe tool * resolves, every task-dependent check reports `could-not-run` and the run * is `incomplete`; it never passes on skips. */ toolName?: string; toolArguments?: Record; /** Upper bound on polling a created task to a terminal status. */ pollTimeoutMs?: number; /** * Responses to submit when the probed task reports `input_required`, keyed by * `inputRequests` key. * * OPT-IN, for the same reason the protocol suite's fixtures are: what a task * is asking for is server-defined, and inventing an answer would submit * arbitrary content into somebody's workflow. Absent ⇒ * `tasks-input-required-update-completes` reports a skip naming what it * needs, and the run stops polling as soon as the task parks on * `input_required` (further polls cannot advance it) rather than burning * `pollTimeoutMs`. */ inputResponses?: Record; }; interface MCPTasksConformanceResult { /** * True ONLY when `outcome` is `"passed"`: every selected check either ran and * passed or was inapplicable to this server. A check that could not run keeps * this false, so a skip can never add up to a green run. */ passed: boolean; outcome: MCPTasksRunOutcome; /** * Present when `outcome` is `"incomplete"`: which checks did not run and what * the caller has to change to make them run. */ incompleteReason?: string; target: string; checks: MCPTasksCheckResult[]; summary: string; durationMs: number; categorySummary: Record; /** * Which questions this run asked, and which build asked them. Same meaning * and same mechanism as the protocol suite's stamp, drawn from a SEPARATE * manifest — see `conformance-profile.ts` for why the two suites do not * share one. */ profile?: ConformanceProfileStamp; discovery: { protocolVersion?: string; wire: TasksWire; toolCount: number; taskCapableToolCount: number; probedTool?: string; createdTaskId?: string; }; } type OAuthConformanceCheckId = "oauth_dcr_http_redirect_uri" | "oauth_invalid_client" | "oauth_invalid_authorize_redirect" | "oauth_invalid_token" | "oauth_invalid_redirect" | "oauth_token_format" | "oauth_unauthenticated_challenge" | "oauth_resource_metadata_challenge" | "oauth_stale_session_rejection"; /** A step in a conformance result: either a real flow step or a post-flow check. */ type ConformanceStepId = OAuthFlowStep | OAuthConformanceCheckId; declare const CONFORMANCE_CHECK_METADATA: Record; type OAuthRegistrationStrategy = RegistrationStrategy2025_03_26 | RegistrationStrategy2025_06_18 | RegistrationStrategy2025_11_25; type OAuthPublicClientMetadata = OAuthDynamicRegistrationMetadata; type OAuthConformanceAuthConfig = { mode: "interactive"; openUrl?: (url: string) => Promise; } | { mode: "headless"; } | { mode: "client_credentials"; clientId: string; clientSecret: string; }; interface OAuthConformanceClientConfig { preregistered?: { clientId: string; clientSecret?: string; }; dynamicRegistration?: Partial; clientIdMetadataUrl?: string; } interface OAuthConformanceConfig { serverUrl: string; protocolVersion: OAuthProtocolVersion; registrationStrategy: OAuthRegistrationStrategy; auth?: OAuthConformanceAuthConfig; client?: OAuthConformanceClientConfig; scopes?: string; customHeaders?: Record; redirectUrl?: string; fetchFn?: typeof fetch; stepTimeout?: number; verification?: OAuthVerificationConfig; oauthConformanceChecks?: boolean; /** Optional callback for progress messages during the OAuth flow. */ onProgress?: (message: string) => void; } /** * Why a skipped step produced no verdict. The two are NOT interchangeable, and * a score built on these must treat them differently: * * - `"not-applicable"` — the requirement cannot apply to THIS server, so * nothing is left unverified. Authorization is OPTIONAL in every MCP * revision ("Authorization is **OPTIONAL** for MCP implementations. When * supported:" — identical text in 2025-03-26 through 2026-07-28), so a * server that never requires it has no authorization obligations to * violate. These must never count against a server. * - `"could-not-run"` — the requirement DOES apply here but the run could * not exercise it. The obligation is untested, so this must never be * summed into a passing verdict. */ /** @see {@link ConformanceSkipReason} — the vocabulary is shared by every suite. */ type OAuthSkipReason = ConformanceSkipReason; /** * Suite-level verdict. `passed` stays a boolean for existing consumers and is * true ONLY for `"passed"` — but a `"not-applicable"` run is not a failure * either, which is exactly why a third value is needed: a public server used * to be reported as a hard OAuth failure. * * `"incomplete"` is the fourth value, aligning OAuth with the other three * suites' {@link ConformanceRunOutcome}: a completed flow whose applicable * steps include one that COULD NOT RUN established nothing about that * obligation, and calling it "passed" is how a two-of-eight run once reported * success elsewhere. OAuth keeps `"not-applicable"` on top because a whole * RUN can be inapplicable (authorization is OPTIONAL), which no other suite * expresses. */ type OAuthRunOutcome = "passed" | "failed" | "incomplete" | "not-applicable"; interface StepResult { step: ConformanceStepId; title: string; summary: string; status: "passed" | "failed" | "skipped"; /** Always set when `status` is `"skipped"`. */ skipReason?: OAuthSkipReason; durationMs: number; logs: InfoLogEntry[]; http?: HttpHistoryEntry; httpAttempts: HttpHistoryEntry[]; error?: { message: string; details?: unknown; }; /** Non-fatal evidence recorded on a passing step (e.g. a spec-preferred but * not mandated behavior was missed). Never present on a failed step — * failures use `error`. */ warnings?: string[]; teachableMoments?: string[]; } interface ConformanceResult { /** True only when `outcome` is `"passed"`. */ passed: boolean; outcome: OAuthRunOutcome; /** * Present when `outcome` is `"incomplete"`: which steps never ran and what * has to change for them to run. */ incompleteReason?: string; protocolVersion: OAuthProtocolVersion; registrationStrategy: OAuthRegistrationStrategy; serverUrl: string; steps: StepResult[]; summary: string; durationMs: number; credentials?: OAuthConformanceCredentials; verification?: VerificationResult; } interface OAuthConformanceCredentials { clientId?: string; clientSecret?: string; accessToken?: string; refreshToken?: string; tokenType?: string; expiresIn?: number; } interface NormalizedOAuthConformanceConfig { serverUrl: string; serverName: string; protocolVersion: OAuthProtocolVersion; registrationStrategy: OAuthRegistrationStrategy; auth: OAuthConformanceAuthConfig; client: OAuthConformanceClientConfig; scopes?: string; customHeaders?: Record; redirectUrl?: string; fetchFn: typeof fetch; stepTimeout: number; verification: OAuthVerificationConfig; oauthConformanceChecks: boolean; onProgress: (message: string) => void; } interface TrackedRequestOptions { redirect?: RequestRedirect; } type TrackedRequestFn = (request: OAuthHttpRequest, options?: TrackedRequestOptions) => Promise; interface AuthorizationCodeResult { code: string; } interface ClientCredentialsResult { tokenResponse: OAuthRequestResult; accessToken: string; refreshToken?: string; tokenType?: string; expiresIn?: number; } /** Optional post-auth verification: connect to the MCP server and exercise tools. */ interface OAuthVerificationConfig { /** After successful OAuth, connect and call tools/list. Default: false. */ listTools?: boolean; /** Also call the named tool with the given params after listing. */ callTool?: { name: string; params?: Record; }; /** Timeout for verification steps in ms. Default: 30_000. */ timeout?: number; } interface VerificationResult { listTools?: { passed: boolean; toolCount?: number; durationMs: number; error?: string; }; callTool?: { passed: boolean; toolName: string; durationMs: number; error?: string; }; } /** Shared default fields — all optional so they can be selectively overridden. */ type OAuthConformanceSuiteDefaults = Partial>; /** Per-flow config — may omit fields provided by defaults. */ type OAuthConformanceSuiteFlow = Partial> & { /** Optional label for this flow (used in reporting). */ label?: string; }; /** Config for running multiple conformance flows against one server. */ interface OAuthConformanceSuiteConfig { /** Human-friendly name for the suite run. */ name?: string; /** The MCP server URL. Shared across all flows. */ serverUrl: string; /** Shared defaults applied to each flow unless overridden. */ defaults?: OAuthConformanceSuiteDefaults; /** Each entry defines one flow in the matrix. Properties override defaults. */ flows: OAuthConformanceSuiteFlow[]; } /** Result for the entire suite run. */ interface OAuthConformanceSuiteResult { name: string; serverUrl: string; passed: boolean; results: Array; summary: string; durationMs: number; } /** * The conformance score: one number, 0–100, computable for any suite and for * all of them pooled. * * The shape is grounded in the spec's own doctrine rather than invented: * * - The spec (every version's `basic/index.mdx`) defines requirement levels * via BCP 14 / RFC 2119 and has no conformance clause beyond them — * conformance IS the MUSTs. So MUSTs own 95 of the 100 points. * - SEP-2484 (Final): "Checks for SHOULD-level requirements report as * warnings rather than failures. MAY requirements do not need rows." * Advice therefore factors into the number — the last 5 points — but can * never masquerade as a failure: a MUST-clean server scores ≥95 no matter * how much advice fired. * - SEP-1730 sets tier lines at 100% and 80%; the 95/5 split keeps advice * from ever demoting a MUST-clean server across either line. The 95–100 * band is reserved for runs that are MUST-clean and complete (a failed or * incomplete run caps at 94), so a violating server can never outrank a * clean-but-advised one and reading ≥95 always means the same thing. * - RFC 2119 makes RECOMMENDED the SHOULD tier ("SHOULD — this word, or the * adjective RECOMMENDED") and MAY the weakest, hence −2 / −1. * * The denominator rules come from `conformance-outcome.ts` and are what make * the score honest: * * - `not-applicable` checks leave the denominator entirely — a server * without auth, a stdio server's HTTP checks, a legacy pin's modern * checks: none of them cost points. * - `could-not-run` checks stay IN the denominator as unearned points, so * an incomplete run can never display 100. * - `pending` checks — ones the run's conformance profile does not score yet * (see `conformance-profile.ts`) — leave the denominator AND the verdict. * A newly added MUST check must not silently re-grade every server that * was green before it existed; promoting it is a profile version bump. * * 100 therefore means exactly one thing: every applicable check ran and * passed, and nothing was left to advise. * * Pure data reasoning — no MCP client, no transport, no Node built-ins — so * it is exported from the browser entry alongside the catalog. */ /** Per RFC 2119, RECOMMENDED is the SHOULD tier; MAY is the weakest. */ declare const DEDUCTION_BY_TIER: { readonly should: 2; readonly may: 1; }; type ConformanceAdvisoryTier = keyof typeof DEDUCTION_BY_TIER; /** One piece of advice that costs points: a readiness warning, a check warning. */ interface ScoredAdvisory { id: string; tier: ConformanceAdvisoryTier; } interface ConformanceScore { /** * 0–100, or null when nothing was applicable — a server with no applicable * checks has nothing to claim a number ABOUT, and inventing one would let a * vacuous run outrank a real one. */ score: number | null; outcome: ConformanceRunOutcome; /** The denominator: passed + failed + couldNotRun. Always display it. */ applicable: number; passed: number; failed: number; couldNotRun: number; notApplicable: number; /** * Checks that ran and reported a verdict the active conformance profile does * NOT score yet (see `conformance-profile.ts`). Excluded from `applicable`, * from `passed`/`failed`/`couldNotRun`, and from the outcome — a check * arriving in the pool must not re-grade servers that were already green. * Always 0 for a result with no profile stamp. */ pending: number; advisories: ScoredAdvisory[]; /** Points the advisories cost, after the cap. */ advicePointsLost: number; /** The revision the score was judged against. Always display it. */ protocolVersion?: string; } /** * Score one suite's checks. `advisories` is whatever advice channel the suite * has (readiness warnings, per-check warnings) — pass an empty array for a * suite without one. */ declare function computeConformanceScore(checks: OutcomeCheckLike[], advisories?: ScoredAdvisory[], protocolVersion?: string, /** * The run's profile stamp, when it has one. Checks the profile does not score * are partitioned out BEFORE the counts and before the verdict — a pending * check must not move the number and must not fail the run. Absent ⇒ every * check is scored, which is the pre-profile arithmetic exactly. */ profileStamp?: Pick): ConformanceScore; /** * One number across suites. Counts are summed and the score re-derived, so a * suite with many checks cannot drown one with few through averaging — and a * 0-for-9 OAuth run stays visible inside 30 protocol passes. * * Parts with nothing applicable (score null — e.g. OAuth against a public * server) contribute only their `notApplicable` tally: they are excluded from * the denominator by construction, not by filtering. */ declare function pooledConformanceScore(parts: ConformanceScore[]): ConformanceScore; /** The one-line rendering every surface shares, so the wording cannot drift. */ declare function describeConformanceScore(score: ConformanceScore): string; /** * Protocol: checks verbatim; advice is the typed readiness channel, whose * `specStrength` already names the RFC 2119 tier. */ declare function scoreFromProtocolResult(result: MCPConformanceResult): ConformanceScore; declare function scoreFromAppsResult(result: MCPAppsConformanceResult): ConformanceScore; declare function scoreFromTasksResult(result: MCPTasksConformanceResult): ConformanceScore; /** * OAuth is the outlier twice over. * * Its `"not-applicable"` outcome (the server serves without auth, which the * spec makes OPTIONAL) means the whole suite has nothing to score: null, zero * applicable, every step tallied as not-applicable. That is the mechanism by * which "a server without auth shouldn't deduct points" holds. * * Otherwise its steps adapt structurally (`step` → `id`) and are counted * directly: the score reads the evidence, not the verdict — even now that the * runner derives its verdict from the same shared vocabulary, the score's * arithmetic stays anchored to the steps themselves. `teachableMoments` are * explanations of failures already costed at MUST weight, never advisories. */ declare function scoreFromOAuthResult(result: ConformanceResult): ConformanceScore; type ConformanceReportKind = "protocol-conformance" | "oauth-conformance" | "apps-conformance" | "tasks-conformance" | "claude-directory-readiness" | "openai-directory-readiness"; type ConformanceReportCaseStatus = "passed" | "failed" | "skipped"; /** * A statement that is REPORTED but never graded. * * Heuristics, recommendations, capability badges and things a human has to * look at all belong here. They exist because suppressing them would lose real * signal, and they are kept out of `cases` because a CI job that fails on an * LLM's opinion — or on a capability the server was never required to have — * teaches its owners to ignore the job. JUnit renders these as ``, * never as failed testcases. */ interface ConformanceReportAdvisory { id: string; title: string; /** Id of the {@link ConformanceReportGroup} this advisory was raised in. */ group: string; /** What kind of statement it is, e.g. a finding class. */ kind: string; /** The verdict, in the vocabulary of whatever produced it. */ status: string; message?: string; details?: unknown; } interface ConformanceReportCase { id: string; title: string; category: string; status: ConformanceReportCaseStatus; /** * Why a skipped case produced no verdict. CI needs this: a * `"not-applicable"` skip left nothing unverified, while a * `"could-not-run"` skip means an obligation went untested. */ skipReason?: ConformanceSkipReason; /** * The active profile does not score this check. It ran and its verdict is * reported verbatim, but it must not turn a build red: the frozen profile * exists so a MUST added this week cannot retroactively fail a server that * was green last week, and a reporter that emitted `` for it would * reopen exactly that hole on the CI channel while the exit code stayed 0. */ pending?: boolean; durationMs: number; description?: string; error?: string; details?: unknown; output?: string; } interface ConformanceReportGroup { id: string; title: string; target: string; passed: boolean; durationMs: number; summary?: string; cases: ConformanceReportCase[]; } interface ConformanceReport { schemaVersion: 1; kind: ConformanceReportKind; name: string; /** True only when `outcome` is `"passed"`. */ passed: boolean; /** * The three-value verdict. Absent only for suites that predate it, so * consumers should treat a missing value as `passed ? "passed" : "failed"`. */ outcome?: ConformanceRunOutcome; incompleteReason?: string; /** * The 0–100 conformance score (see `conformance-score.ts`). For a suite, * the pooled score over its runs. `score.score` is null when nothing was * applicable — e.g. OAuth against a server that serves without auth. */ score?: ConformanceScore; /** * Which questions the run asked, and which build asked them (see * `conformance-profile.ts`). Present for the protocol suite (`mcp-protocol`) * and the Tasks suite (`mcp-tasks`); absent for the suites that carry no * profile yet, and for reports produced before profiles existed. * * A reader comparing two reports must check this first, and must check the * ID as well as the version: two scores from different profile versions are * not the same measurement, and two scores from different profile IDs are not * even the same question set. */ profile?: ConformanceProfileStamp; durationMs: number; groups: ConformanceReportGroup[]; /** * Ungraded statements. Absent for the conformance suites, which have none; * present for readiness, whose experience-insights lane is entirely * advisory. Adding an OPTIONAL field keeps `schemaVersion` at 1 — an older * consumer reading a readiness report ignores it and still gets a valid * report, which is the property a version bump would otherwise be asserting. */ advisories?: ConformanceReportAdvisory[]; } type SupportedSingleConformanceResult = MCPConformanceResult | ConformanceResult | MCPAppsConformanceResult | MCPTasksConformanceResult | ClaudeReadinessResult | OpenAIReadinessResult; type SupportedSuiteConformanceResult = MCPConformanceSuiteResult | OAuthConformanceSuiteResult | MCPAppsConformanceSuiteResult; type SupportedConformanceResult = SupportedSingleConformanceResult | SupportedSuiteConformanceResult; declare function toConformanceReport(result: MCPConformanceResult): ConformanceReport; declare function toConformanceReport(result: MCPConformanceSuiteResult): ConformanceReport; declare function toConformanceReport(result: ConformanceResult): ConformanceReport; declare function toConformanceReport(result: OAuthConformanceSuiteResult): ConformanceReport; declare function toConformanceReport(result: MCPAppsConformanceResult): ConformanceReport; declare function toConformanceReport(result: MCPAppsConformanceSuiteResult): ConformanceReport; declare function toConformanceReport(result: MCPTasksConformanceResult): ConformanceReport; declare function toConformanceReport(result: ClaudeReadinessResult): ConformanceReport; declare function toConformanceReport(result: OpenAIReadinessResult): ConformanceReport; declare function toConformanceReport(result: SupportedConformanceResult): ConformanceReport; declare function renderConformanceReportJson(report: ConformanceReport): ConformanceReport; declare function renderConformanceReportJUnitXml(report: ConformanceReport): string; /** * Make a conformance report safe to hand to somebody else. * * A conformance run is a debugging artifact first: it keeps the raw HTTP * exchange so an engineer can see exactly what their server said. That is the * right default in the inspector, where the data never leaves the tab of the * person who produced it. It is the wrong default the moment a report is * PERSISTED BEHIND A LINK, because the OAuth suite completes a real * authorization — so the result carries a live access token, a refresh token, * the client secret, the `Authorization` headers of every request it made, and * the token-exchange bodies. Sharing that link would be sharing credentials for * the server being scored. * * The formatter has always redacted these values on their way to a terminal * (`redactSensitiveStrings`), which is the clearest possible statement that they * are sensitive. This module is the same judgement applied one layer earlier, to * the stored document itself: redaction at display time protects one reader, * redaction at write time protects everyone the link ever reaches. * * Two layers, deliberately overlapping: * * 1. STRUCTURAL — whole containers whose entire purpose is raw protocol * evidence (`httpAttempts`, `http`, `logs`) and the typed credential bag * (`credentials`) are dropped, not scrubbed. A shared report is a summary: * it needs each check's id, title, status and failure message, and nothing * it drops here is rendered by any result page. Dropping also keeps the * document small, which matters against the 1MB storage bound. * 2. KEY-NAME — everything that survives is walked and any value sitting under * a credential-shaped key is replaced, and any URL-shaped string has its * sensitive query parameters replaced. This is the backstop for shapes this * module does not know about, including ones added later. * * Layer 2 alone would be a blocklist, and a blocklist eventually misses. Layer 1 * is what makes that acceptable: the containers where unknown-shaped secrets * actually accumulate are gone before layer 2 runs. * * Pure data reasoning — no MCP client, no transport, no Node built-ins — so it * is exported from the browser entry alongside the scoring engine. */ declare const REDACTED = "[REDACTED]"; /** * Replace sensitive query-parameter VALUES while keeping the parameter names. * * The names are diagnostic — "this endpoint was called with an api_key" is * exactly the kind of thing a reader needs to know — and only the values are * dangerous. Returns the input unchanged when it is not a URL, so this is safe * to run over arbitrary strings. */ declare function redactUrlSecrets(value: string): string; /** * The scanned server's own URL, as stored and displayed on a shared page. * * MCP servers are routinely addressed with a key in the query string, and the * result page prints this back verbatim — so it gets the same treatment as any * other URL in the document. */ declare function redactSharedServerUrl(url: string): string; /** * Redact a whole multi-suite report for storage behind a shareable link. * * Structure-preserving: suites keep their keys, checks keep their ids, titles, * statuses and failure messages. What leaves is the raw protocol evidence and * anything credential-shaped. */ declare function redactConformanceReportForSharing(report: T): T; /** * Composite conformance report types. Browser-safe: no runners, no Node. * Directory readiness is absent by construction. */ declare const CONFORMANCE_RUN_SCHEMA_VERSION: 1; declare const CONFORMANCE_SUITE_KINDS: readonly ["protocol", "apps", "tasks", "oauth"]; type ConformanceSuiteKind = (typeof CONFORMANCE_SUITE_KINDS)[number]; declare const DEFAULT_CONFORMANCE_SUITES: ConformanceSuiteKind[]; interface ConformanceRunReportV1 { schemaVersion: typeof CONFORMANCE_RUN_SCHEMA_VERSION; requestedSuites: ConformanceSuiteKind[]; reports: Partial>; outcome: ConformanceRunOutcome; score: ConformanceScore; durationMs: number; protocolVersion?: string; engineVersion?: string; } declare function normalizeConformanceSuites(suites: readonly string[] | undefined): ConformanceSuiteKind[]; declare function buildConformanceRunReport(input: { requestedSuites: ConformanceSuiteKind[]; reports: Partial>; startedAt: number; engineVersion?: string; }): ConformanceRunReportV1; /** * Pure resolver for the host-side sandbox policy applied to UI resources * (MCP Apps per SEP-1865, plus any ChatGPT-Apps surface that mounts * untrusted UI in a sandbox iframe). * * Lives in `@mcpjam/sdk` rather than the inspector client so the same * resolver is consumed by both: * - `mcp-apps-renderer.tsx` (client-side MCP Apps rendering) * - any ChatGPT-Apps renderer / server-side route that builds CSP * headers for untrusted UI * * No DOM, no React, no Convex — pure JSON in / JSON out. Trivially unit- * testable. If the resolution lives in two places it can be bypassed by * mounting through the path that didn't get updated, which is exactly * the failure mode this single source of truth prevents. * * Precedence (mirror SEP-1865 + agreed plan): * * 1. `mode` picks the starting BASELINE: * - `"declared"`: baseline = resource's `_meta.ui.csp` declaration * - `"host-default"`: baseline = inspector's default CSP shape * - `"relaxed"`: baseline = permissive (local/dev). Hosted clamp * (step 4) still strips dangerous values. * 2. `restrictTo` INTERSECTS with the baseline. Never unions — * hosts MAY further restrict but MUST NOT allow undeclared domains. * Applies in EVERY mode, including `"declared"`. * 3. `deny` SUBTRACTS from the current effective set. Wins over * `restrictTo`, the baseline, and the resource declaration. * Applies in EVERY mode. * 4. HOSTED-MODE HARD CLAMP — strips dangerous values regardless of * profile (wildcards, localhost/private networks, unsafe schemes). * Built-in patterns are NOT configurable from `mcpProfile`. Callers * may pass an additional `hostedClampExtraDeny` set for app-specific * origins they want stripped (e.g. the inspector's own API origin — * this is the only place that protects against a hosted widget * declaring same-origin exfiltration targets in `connectDomains`). * * Result is a typed `EffectiveSandboxCsp` carrying the four directive * lists ready for header-string construction by a downstream helper * (e.g. `buildCspHeader` in `widget-helpers.ts`). */ /** * Host-config sandbox CSP mode. Mirrors * `mcpProfile.apps.sandbox.csp.mode` from the backend's * `HostConfigMcpProfileV1`. */ type SandboxCspMode = "host-default" | "declared" | "relaxed"; /** * Host-config permissions mode. Mirrors * `mcpProfile.apps.sandbox.permissions.mode`. */ type SandboxPermissionsMode = "resource-declared" | "deny-all" | "custom"; /** * Four parallel allow/deny lists keyed by CSP directive family. * Mirrors `CspDomainSet` in the backend's * `convex/lib/hostConfigV2.ts:46`. */ interface SandboxCspDomainSet { connectDomains?: string[]; resourceDomains?: string[]; frameDomains?: string[]; baseUriDomains?: string[]; } /** * The `apps.sandbox.csp` slice of the host-config mcpProfile envelope. * Optional everywhere: undefined fields mean "no host-level override at * this layer." Mirrors * `mcpProfile.apps.sandbox.csp` in the backend schema. */ interface SandboxCspPolicy { mode?: SandboxCspMode; restrictTo?: SandboxCspDomainSet; deny?: SandboxCspDomainSet; } /** * The `apps.sandbox.permissions` slice. Mirrors * `mcpProfile.apps.sandbox.permissions`. * * **Key shape:** `allow` keys and `deny` entries MUST use the same * names as the MCP `_meta.ui.permissions` declaration (SEP-1865 * §UIResourceMeta — camelCase: `camera`, `microphone`, `geolocation`, * `clipboardWrite`). The resolver does plain string-key matching * against `resourcePermissions`, so any kebab-case entries (`clipboard- * write`) will silently no-op — the spec's kebab form belongs at the * iframe `allow=` attribute layer, not here. */ interface SandboxPermissionsPolicy { mode?: SandboxPermissionsMode; allow?: Record; deny?: string[]; } /** * What the resource declared in its `_meta.ui.csp`. Same four directive * families. Undefined = the resource declared nothing (the SEP-1865 * "secure default" applies, which the baseline picker fabricates from * mode). */ interface ResourceDeclaredCsp { connectDomains?: string[]; resourceDomains?: string[]; frameDomains?: string[]; baseUriDomains?: string[]; } /** * Result of the resolver. Caller turns this into a real CSP header * string with whatever builder it uses (e.g. `buildCspHeader` for * inspector renderers). */ interface EffectiveSandboxCsp { /** Final effective CSP-directive lists after all 4 precedence steps. */ connectDomains: string[]; resourceDomains: string[]; frameDomains: string[]; baseUriDomains: string[]; /** Trace of how the resolver arrived at the result — for debug UI. */ trace: { /** Step 1 — baseline picked by `mode`. */ baseline: ResourceDeclaredCsp; /** Step 2 — after `restrictTo ∩ baseline`. */ afterRestrictTo: ResourceDeclaredCsp; /** Step 3 — after subtracting `deny`. */ afterDeny: ResourceDeclaredCsp; /** Step 4 — values the hosted-mode clamp stripped. Empty when not hosted. */ hostedClamp: { stripped: SandboxCspDomainSet; }; /** Which mode was applied (after default substitution). */ effectiveMode: SandboxCspMode; }; } /** * Sandbox permissions set after resolution. Boolean map keyed by * permission name — `true` means granted to the iframe (`allow=` * attribute), `false` (or absent) means not granted. */ interface EffectiveSandboxPermissions { granted: Record; trace: { declared: Record; afterMode: Record; deniedByProfile: string[]; deniedByHostedClamp: string[]; effectiveMode: SandboxPermissionsMode; }; } interface ResolveSandboxCspArgs { /** What the UI resource declared in `_meta.ui.csp`. */ resourceCsp?: ResourceDeclaredCsp; /** Host-config policy from `mcpProfile.apps.sandbox.csp`. */ policy?: SandboxCspPolicy; /** * Inspector's renderer default baseline for `mode: "host-default"`. The * resolver doesn't fabricate this — the caller supplies it because * "what counts as the inspector's default" is a UI concern. Today the * inspector uses `buildCspHeader` in `widget-helpers.ts` to compute * permissive vs widget-declared baselines; callers can synthesize * the resource-list shape from there or pass any shape they want. */ hostDefaultBaseline?: ResourceDeclaredCsp; /** * SEP-1865 "secure default" applied when the resource omits its CSP * declaration AND mode is `"declared"`. Defaults to the empty * record (no domains allowed) per the spec. Callers may override if * they need to allow specific protocol-mandated sources. */ secureDefault?: ResourceDeclaredCsp; /** * Whether the inspector is running in hosted mode. Drives the hosted * clamp (step 4). Callers detect this however they currently do — * the resolver doesn't sniff env or origin. */ hostedMode: boolean; /** * App-specific origins the hosted clamp MUST strip in addition to the * built-in `isHostedDangerousDomain` patterns. Only applied when * `hostedMode === true`. Supports the same wildcard-prefix matching as * `policy.deny` (`https://*.example.com` matches subdomains). * * Mirrors `ResolveSandboxPermissionsArgs.hostedClampDeny`. This is the * only place that protects against a hosted widget declaring app- * sensitive origins (e.g. the inspector's own API origin) in * `connectDomains` to exfiltrate data — `policy.deny` is profile- * configurable and therefore bypassable; this is not. */ hostedClampExtraDeny?: SandboxCspDomainSet; } interface ResolveSandboxPermissionsArgs { /** * Permissions the resource requested in its `_meta.ui.permissions`. * Boolean map keyed by permission name as declared in SEP-1865 * (camelCase: `camera`, `microphone`, `geolocation`, `clipboardWrite`). * The kebab-case forms (`clipboard-write`) are the browser * Permission-Policy spelling and belong at the iframe `allow=` * attribute layer — not here. The resource declaration is the * CEILING: the host can't grant a permission the resource didn't * request. */ resourcePermissions?: Record; policy?: SandboxPermissionsPolicy; /** * Whether the inspector is running in hosted mode. Drives the hosted * clamp for sensitive permissions. */ hostedMode: boolean; /** * Permission names the hosted-mode clamp must strip regardless of * profile. Defaults to the SEP-1865-sensitive set * (`camera`, `microphone`, `geolocation`). Callers may pass a * different list if their hosted-mode policy diverges. */ hostedClampDeny?: string[]; } /** * Resolve the effective sandbox CSP for a UI resource given the host's * profile and hosted-mode flag. See module docstring for precedence. * * @example * const csp = resolveSandboxCsp({ * resourceCsp: { connectDomains: ["api.example.com", "evil.com"] }, * policy: { mode: "declared", deny: { connectDomains: ["evil.com"] } }, * hostedMode: false, * }); * // csp.connectDomains === ["api.example.com"] */ declare function resolveSandboxCsp(args: ResolveSandboxCspArgs): EffectiveSandboxCsp; /** * Resolve sandbox permissions for a UI resource. Resource declaration is * the ceiling; `mode` chooses posture; `deny` wins; hosted clamp strips * sensitive permissions regardless. */ declare function resolveSandboxPermissions(args: ResolveSandboxPermissionsArgs): EffectiveSandboxPermissions; /** * Which sampling parameters a model accepts, keyed off the model id string. * * Deliberately import-free so it is safe from every entrypoint — the browser * bundle, the worker, and `HostRunner` on the request path all reach it without * pulling in a provider SDK. */ /** * True when the model rejects a `temperature` request field. Callers must omit * the field entirely rather than sending a default — a `temperature: undefined` * still serializes the key. */ declare const modelRejectsTemperature: (modelId: string) => boolean; export { type ClaudeObservationKind as $, AUTH_METHODS as A, type BaseXAAStateMachineConfig as B, CHECK_ERAS as C, CLAUDE_POLICY_SNAPSHOT_DATE as D, CLAUDE_READINESS_ENGINE_VERSION as E, CLAUDE_READINESS_LANES as F, CLAUDE_REQUIRED_LANES as G, CLAUDE_RUNNER_CAPABILITIES as H, CLAUDE_SUBMISSION_LIMITS as I, CONFORMANCE_CHECKER_VERSION as J, CONFORMANCE_CHECK_METADATA as K, CONFORMANCE_PROFILE_IDS as L, CONFORMANCE_RUN_SCHEMA_VERSION as M, CONFORMANCE_SUITE_KINDS as N, type ClaudeAttestation as O, type ClaudeCapabilityBadge as P, type ClaudeDataHandlingMode as Q, type ClaudeDeclaredAuthMode as R, type ClaudeEvidenceProvenance as S, type ClaudeExperienceObservations as T, type ClaudeFindingClass as U, type ClaudeFindingStatus as V, type ClaudeIntrusiveness as W, type XAAStateMachine as X, type ClaudeLaneCoverage as Y, type ClaudeLaneStatus as Z, type ClaudeObservationId as _, type XAAFlowStep as a, type EvidenceReuseExpectation as a$, type ClaudeObservationState as a0, type ClaudePolicyPage as a1, type ClaudePolicySourceEntry as a2, type ClaudePolicySourceRef as a3, type ClaudeReadinessAuthMode as a4, type ClaudeReadinessFinding as a5, type ClaudeReadinessLane as a6, type ClaudeReadinessLaneResult as a7, type ClaudeReadinessResult as a8, type ClaudeReadinessRunContext as a9, DIRECTORY_OBSERVATION_CONFIDENCE as aA, DIRECTORY_OBSERVATION_FINDING_CLASSES as aB, DIRECTORY_OBSERVATION_LIMITS as aC, DIRECTORY_OBSERVATION_REASONS as aD, DIRECTORY_OBSERVATION_STATUSES as aE, type DecodedJwtParts as aF, type DerivedOAuthEmulation as aG, type DescribeContext as aH, type DigestVerification as aI, type DirectoryObservation as aJ, type DirectoryObservationConfidence as aK, type DirectoryObservationEnvelope as aL, type DirectoryObservationFindingClass as aM, type DirectoryObservationReason as aN, type DirectoryObservationState as aO, type DirectoryObservationStatus as aP, type DynamicClientRegistrationCredentials as aQ, type DynamicClientRegistrationOutcome as aR, EMPTY_XAA_FLOW_STATE as aS, ERROR_CATALOG as aT, EVIDENCE_REUSE_REFUSALS as aU, type EffectiveSandboxCsp as aV, type EffectiveSandboxPermissions as aW, type ErrorCatalogEntry as aX, type ErrorCatalogSlug as aY, type ErrorOrigin as aZ, type EvidenceReuse as a_, type ClaudeRunnerCapability as aa, type ClaudeSubmissionProfile as ab, type ClaudeSubmissionProfileParse as ac, type CompletionSafeRedirectPlan as ad, type ConformanceAdvisoryTier as ae, type ConformanceProfile as af, type ConformanceProfileId as ag, type ConformanceProfileStamp as ah, type ConformanceReport as ai, type ConformanceReportCase as aj, type ConformanceReportGroup as ak, type ConformanceRunOutcome as al, type ConformanceRunReportV1 as am, type ConformanceScore as an, type ConformanceSkipReason as ao, type ConformanceSuiteId as ap, type ConformanceSuiteKind as aq, type ConformanceSupport as ar, DEFAULT_CONFORMANCE_SUITES as as, DEFAULT_IDENTITY_ASSERTION_FORMAT as at, DEFAULT_MCPJAM_CLIENT_ID_METADATA_URL as au, DEFAULT_NEGATIVE_TEST_MODE as av, DEFAULT_REGISTRATION_MODE as aw, DEFAULT_REGISTRATION_STRATEGY as ax, DEFAULT_SUBJECT_IDENTIFIER_FORMAT as ay, DEFAULT_XAA_CLIENT_AUTH as az, type XAAFlowState as b, OPENAI_MCP_SKILL_LIMITS as b$, type EvidenceReuseRefusal as b0, type FrontmatterIdentityCheck as b1, IDENTITY_ASSERTION_FORMATS as b2, ID_JAG_GRANT_PROFILE as b3, ID_JAG_TOKEN_TYPE as b4, ID_TOKEN_TOKEN_TYPE as b5, type IdJagClientMetadataEvaluation as b6, type IdJagMetadataEvidence as b7, type IdentityAssertionFormat as b8, type ImageDimensions as b9, type McpModelVisibleToolResultPolicy as bA, NEGATIVE_TEST_MODES as bB, NEGATIVE_TEST_MODE_DETAILS as bC, NOT_REQUESTED_OBSERVATIONS as bD, type NegativeTestMode as bE, type NormalizedError as bF, type OAuthAuthorizationRequestResult as bG, type OAuthConformanceCheckId as bH, type OAuthProtocolMode as bI, OAuthRedactedCredentialError as bJ, type OAuthRegistrationMode as bK, type OAuthRegistrationStrategy$1 as bL, type OAuthStateMachineRunConfig as bM, type OAuthStateMachineRunResult as bN, type OAuthTraceProjectionContext as bO, type OAuthTraceSnapshot as bP, type OAuthTraceStepSnapshot as bQ, type OAuthTraceStepStatus as bR, OPENAI_ARCHIVE_LIMITS as bS, OPENAI_ATTESTATIONS as bT, OPENAI_BRAND_COLOR_CONTRAST as bU, OPENAI_DATA_TYPES as bV, OPENAI_FIELD_LIMITS as bW, OPENAI_HEADLINE_STAGE as bX, OPENAI_HOST_PROFILE as bY, OPENAI_IMAGE_CONSTRAINTS as bZ, OPENAI_LISTING_CATEGORIES as b_, type ImageDimensionsResult as ba, JWT_BEARER_GRANT as bb, type MCPAppsCheckId as bc, type MCPCheckEra as bd, type MCPCheckId as be, MCPJAM_CLIENT_URI as bf, MCPJAM_LOGO_URI as bg, type MCPTasksCheckId as bh, MCP_APPS_CHECK_IDS as bi, MCP_CHECK_CATEGORIES as bj, MCP_CHECK_IDS as bk, MCP_DIRECT_IMAGE_MAX_BYTES as bl, MCP_IMAGE_MAX_MEDIA_PARTS as bm, MCP_IMAGE_MAX_TOTAL_BYTES as bn, MCP_INIT_ID as bo, MCP_LINKED_RESOURCE_MAX_READS as bp, MCP_PROTOCOL_VERSION as bq, MCP_TASKS_CHECK_IDS as br, MCP_UI_EXTENSION_ID as bs, MCP_UI_RESOURCE_MIME_TYPE as bt, type McpInitializeRequest as bu, type McpLinkedResourceReader as bv, type McpModelOutputContent as bw, type McpModelOutputContentPart as bx, type McpModelOutputOptions as by, type McpModelOutputWithLinkedResourcesOptions as bz, type AttributableEvidenceSource as c, type RunServerDoctorInput as c$, OPENAI_OBSERVATION_IDS as c0, OPENAI_OBSERVATION_KINDS as c1, OPENAI_OBSERVATION_SCHEMA_VERSION as c2, OPENAI_POLICY_MANIFEST as c3, OPENAI_POLICY_PAGES as c4, OPENAI_POLICY_SNAPSHOT_DATE as c5, OPENAI_PORTAL_ERRORS as c6, OPENAI_PORTAL_ERRORS_BY_ID as c7, OPENAI_PORTAL_ERROR_CATEGORIES as c8, OPENAI_READINESS_ENGINE_VERSION as c9, type OpenAIPortalErrorDefinition as cA, type OpenAIPortalIssue as cB, type OpenAIReadinessFinding as cC, type OpenAIReadinessLane as cD, type OpenAIReadinessLaneResult as cE, type OpenAIReadinessResult as cF, type OpenAIReadinessRunContext as cG, type OpenAIReadinessStage as cH, type OpenAIReadinessStageResult as cI, type OpenAIRunnerCapability as cJ, type OpenAISubmissionMode as cK, type OpenAISubmissionProfile as cL, type OpenAISubmissionProfileParse as cM, type OutcomeCheckLike as cN, PROTOCOL_VERSION_ERAS as cO, PROTOCOL_VERSION_INFO as cP, type ParsedDigest as cQ, type ProfileCheckLike as cR, REDACTED as cS, REGISTRATION_STRATEGIES as cT, RETRYABLE_NODE_ERROR_CODES as cU, type RegistrationMode as cV, type RegistrationStrategy as cW, type ResolveSandboxCspArgs as cX, type ResolveSandboxPermissionsArgs as cY, type ResolvedAuthorizationPlan as cZ, type ResourceDeclaredCsp as c_, OPENAI_READINESS_INPUTS as ca, OPENAI_READINESS_LANES as cb, OPENAI_READINESS_STAGES as cc, OPENAI_RUNNER_CAPABILITIES as cd, OPENAI_STAGE_LANES as ce, OPENAI_SUBMISSION_MODES as cf, OPENAI_SUBMISSION_MODE_SHAPES as cg, type OpenAIAgentMetadata as ch, type OpenAIAgentMetadataIssue as ci, type OpenAIAgentMetadataParse as cj, type OpenAIArchiveObservations as ck, type OpenAIAttestation as cl, type OpenAICapabilityBadge as cm, type OpenAIDataType as cn, type OpenAIExperienceObservations as co, type OpenAILaneCoverage as cp, type OpenAILaneStatus as cq, type OpenAIListingCategory as cr, type OpenAIObservationId as cs, type OpenAIObservationKind as ct, type OpenAIObservationState as cu, type OpenAIPluginPackageEvidence as cv, type OpenAIPolicyPage as cw, type OpenAIPolicySourceEntry as cx, type OpenAIPolicySourceRef as cy, type OpenAIPortalErrorCategory as cz, type AuthMethod as d, checkBrandColor as d$, SAML2_TOKEN_TYPE as d0, SUBJECT_IDENTIFIER_FORMATS as d1, type SandboxCspDomainSet as d2, type SandboxCspMode as d3, type SandboxCspPolicy as d4, type SandboxPermissionsMode as d5, type SandboxPermissionsPolicy as d6, type ScoredAdvisory as d7, type ServerDoctorDependencies as d8, SkillIntegrityError as d9, XAA_IDP_KID as dA, XAA_MCP_EXTENSION as dB, type XaaCapabilityEvidence as dC, type XaaClientAuthMethod as dD, type XaaDcrCredentialCache as dE, type XaaEnterprisePolicy as dF, type XaaEnterprisePolicyIdp as dG, type XaaEnterprisePolicyState as dH, type XaaEphemeralDcrCredentials as dI, type XaaRegistrationWarning as dJ, type XaaRegistrationWarningCode as dK, type XaaTokenEndpointAuthMethod as dL, analyzeAsCompatibility as dM, applyRuntimeClientCapabilities as dN, assertOAuthResultCredentialsUnredacted as dO, buildAuthorizationServerMetadataCandidates as dP, buildConformanceProfileStamp as dQ, buildConformanceRunReport as dR, buildDynamicClientRegistrationRequest as dS, buildIssuerPublicationCandidates as dT, buildMcpInitializeRequest as dU, buildOutcomeSummary as dV, buildProtectedResourceMetadataCandidates as dW, buildXaaDcrCredentialCacheKey as dX, canRunConformance as dY, canonicalJson as dZ, canonicalizeMcpResource as d_, type SubjectIdentifierFormat as da, type SupportedDigestAlgorithm as db, TASKS_DECLARATION_REQUIRED_ERROR_CODE as dc, TOKEN_EXCHANGE_GRANT as dd, UNKNOWN_TASK_ERROR_CODE as de, type UnsupportedCharacter as df, type XAACheckStatus as dg, type XAACompatibilityCheck as dh, type XAACompatibilityReport as di, type XAACompatibilityVerdict as dj, type XAADecodedJwt as dk, type XAAHttpHistoryEntry as dl, type XAAInfoLogEntry as dm, type XAAJWTInspectionIssue as dn, type XAARequestExecutor as dp, type XAARequestResult as dq, type XAAVendor as dr, type XAAVendorHint as ds, type XAAVendorVerdict as dt, XAA_AS_METADATA_NAMES as du, XAA_CLIENT_AUTH_METHODS as dv, XAA_DEBUG_CLIENT_ID_METADATA_URL as dw, XAA_DEBUG_IDP_CLIENT_ID as dx, XAA_ENTERPRISE_POLICY_EXTENSION as dy, XAA_ENTERPRISE_POLICY_IDPS as dz, type AuthorizationDiscoverySnapshot as e, mcpCallToolResultToModelOutputWithLinkedResources as e$, checkFrontmatterDrift as e0, checkSkillIdentity as e1, claudePolicySource as e2, claudeSubmissionProfileSchema as e3, comparableAdvertisedFrontmatter as e4, computeConformanceScore as e5, computeSkillVersionHash as e6, conformanceProfile as e7, conformanceProfileDigest as e8, createInitialXAAFlowState as e9, getXaaConnectClientMetadata as eA, getXaaDebugClientMetadata as eB, groupPortalIssues as eC, hasBlockingPortalIssue as eD, hasSurroundingWhitespace as eE, isDispositiveClaudeFinding as eF, isHttpServerConfig as eG, isInapplicableCheck as eH, isInvalidRedirectUriRejection as eI, isLaneApplicableInMode as eJ, isListedResource as eK, isLoopbackClientMetadataUrl as eL, isLoopbackHost as eM, isNegativeTestMode as eN, isNormalizedError as eO, isOpenAIPolicyCorpusVerified as eP, isOpenAIReadinessResult as eQ, isPolicyCorpusVerified as eR, isPolicyDependentNegativeTestMode as eS, isSkillIntegrityError as eT, isSupportedText as eU, isTasksDeclarationRequiredError as eV, isUnknownTaskError as eW, isUnrunCheck as eX, isXaaDcrClientSecretExpired as eY, legacyTaskToObservation as eZ, mcpCallToolResultToModelOutput as e_, createOAuthStateMachine as ea, createOAuthTraceProjectionContext as eb, decideConformanceOutcome as ec, decideLaneStatus as ed, decodeJWT as ee, decodeJWTParts as ef, deriveCapabilityEvidence as eg, deriveOAuthEmulation as eh, describeAsSlug as ei, describeConformanceScore as ej, describeError as ek, detectVendor as el, evaluateIdJagClientMetadata as em, evaluateMcpInitializeResponse as en, executeDynamicClientRegistration as eo, extensionTaskToObservation as ep, extractNodeErrno as eq, findListedResource as er, findUnsupportedCharacters as es, formatJWTTimestamp as et, getBrowserDebugDynamicRegistrationMetadata as eu, getDefaultClientCapabilities as ev, getDefaultRegistrationStrategy as ew, getStepIndex as ex, getStepInfo as ey, getSupportedRegistrationStrategies as ez, type AuthorizationPlanCapabilities as f, type MCPConformanceSuiteResult as f$, mcpInitializeExtensionEvidence as f0, mergeClientCapabilities as f1, modelRejectsTemperature as f2, normalizeAuthMethod as f3, normalizeClientCapabilities as f4, normalizeConformanceSuites as f5, normalizeIdentityAssertionFormat as f6, normalizeRegistrationMode as f7, normalizeRegistrationStrategy as f8, normalizeSubjectIdentifierFormat as f9, runOAuthStateMachine as fA, sameReadinessTarget as fB, scoreFromAppsResult as fC, scoreFromOAuthResult as fD, scoreFromProtocolResult as fE, scoreFromTasksResult as fF, selectTokenEndpointAuthMethod as fG, sha256HexOfBytes as fH, sha256HexOfText as fI, skillNameFromUri as fJ, sniffImageMimeType as fK, splitAdvertisedFrontmatter as fL, splitSkillMarkdown as fM, stageLanesFor as fN, summarizeLaneCoverage as fO, summarizeTestCases as fP, toConformanceReport as fQ, unscoredCheckIds as fR, validateClientIdMetadataUrl as fS, verifyDigest as fT, verifySkillMarkdown as fU, withSkillsExtensionCapability as fV, withXaaEnterprisePolicy as fW, withoutXaaEnterprisePolicy as fX, type MCPConformanceConfig as fY, type MCPConformanceResult as fZ, type MCPConformanceSuiteConfig as f_, normalizeXaaClientAuth as fa, openaiPolicySource as fb, openaiSubmissionProfileSchema as fc, originOf as fd, parseClaudeSubmissionProfile as fe, parseDigest as ff, parseHexColor as fg, parseOpenAIAgentMetadata as fh, parseOpenAISubmissionProfile as fi, parseRetryAfterMs as fj, partitionByProfile as fk, partitionByStamp as fl, planCompletionSafeRedirects as fm, pooledConformanceScore as fn, projectOAuthTraceSnapshot as fo, readImageDimensions as fp, readOpenAIPluginPackage as fq, readXaaEnterprisePolicy as fr, redactConformanceReportForSharing as fs, redactSharedServerUrl as ft, redactUrlSecrets as fu, resolveAuthorizationPlan as fv, resolveRegistrationStrategies as fw, resolveSandboxCsp as fx, resolveSandboxPermissions as fy, rollUpLaneStatus as fz, type AuthorizationPlanInput as g, type OAuthConformanceSuiteDefaults as g$, type TrackedRequestFn as g0, type ClientCredentialsResult as g1, type AuthorizationCodeResult as g2, type OAuthConformanceConfig as g3, type ConformanceResult as g4, type OAuthConformanceSuiteConfig as g5, type OAuthConformanceSuiteResult as g6, type MCPAppsConformanceConfig as g7, type MCPAppsConformanceResult as g8, type MCPAppsConformanceSuiteConfig as g9, LEGACY_TASK_STATUSES as gA, type MCPAppsCheckCategory as gB, type MCPAppsCheckResult as gC, type MCPAppsCheckStatus as gD, type MCPAppsConformanceSuiteDefaults as gE, type MCPAppsConformanceSuiteRun as gF, type MCPAppsResourceReadOutcome as gG, type MCPCheckCategory as gH, type MCPCheckEras as gI, type MCPCheckResult as gJ, type MCPCheckStatus as gK, type MCPConformanceFixtures as gL, type MCPReadinessId as gM, type MCPReadinessSpecStrength as gN, type MCPReadinessWarning as gO, type MCPServerSurfaceSnapshot as gP, type MCPTasksCheckCategory as gQ, type MCPTasksCheckStatus as gR, type MCPTasksSkipReason as gS, MCP_APPS_CHECK_CATEGORIES as gT, MCP_PRESERVE_RAW_RESULT_FOR_UI as gU, MCP_PROTOCOL_VERSION_ERA_IDS as gV, MCP_READINESS_IDS as gW, MCP_TASKS_CHECK_CATEGORIES as gX, type OAuthConformanceAuthConfig as gY, type OAuthConformanceClientConfig as gZ, type StepResult as g_, type MCPAppsConformanceSuiteResult as ga, type MCPTasksConformanceConfig as gb, type MCPTasksConformanceResult as gc, type MCPTasksCheckResult as gd, type MCPTasksRunOutcome as ge, type DirectoryCheckDefinition as gf, type DirectoryCheckStamp as gg, type DirectoryReadinessFinding as gh, type OpenAIReadinessAuthMode as gi, type XmlParseFn as gj, type XmlParseResult as gk, type NormalizedOAuthConformanceConfig as gl, type VerificationResult as gm, CLAUDE_APP_CONTENT_DOMAIN_HASH_LENGTH as gn, CLAUDE_LOOPBACK_REDIRECT_IGNORES_PORT as go, CLAUDE_OBSERVATION_CATALOG as gp, CLAUDE_OBSERVATION_SCHEMA as gq, type ConformanceReportAdvisory as gr, type ConformanceReportCaseStatus as gs, type ConformanceReportKind as gt, type ConformanceStepId as gu, type DirectoryObservationCatalog as gv, type DirectoryObservationMapping as gw, type DirectoryObservationParseFailure as gx, type DirectoryObservationParseResult as gy, type DirectoryObservationSchema as gz, type BrandColorCheck as h, runServerDoctor as h$, type OAuthConformanceSuiteFlow as h0, type OAuthVerificationConfig as h1, OPENAI_AGENT_METADATA_PATH as h2, OPENAI_APP_HTML_MIME as h3, OPENAI_DEMO_CREDENTIAL_DELIVERY as h4, OPENAI_DOMAIN_VERIFICATION_PATH as h5, OPENAI_EXPECTED_MCP_PATH as h6, OPENAI_EXTERNAL_POLICY_PAGES as h7, OPENAI_MANIFEST_LOCATIONS as h8, OPENAI_MCP_SKILLS_EXTENSION as h9, type OpenAIReadinessInputName as hA, type ReadOpenAIPluginPackageOptions as hB, type RgbColor as hC, type SupportedConformanceResult as hD, UNVERIFIED_CONFIDENTIAL_CIMD_CLIENT_NAME as hE, XAA_CONFIDENTIAL_CIMD_ORIGIN as hF, XAA_CONFIDENTIAL_CIMD_PATH_PREFIX as hG, buildConfidentialCimdUrl as hH, checkEvidenceReuse as hI, collectConnectedServerDoctorState as hJ, contrastRatio as hK, crossCheckToolDependencies as hL, decodeConfidentialCimdKey as hM, derivedFrom as hN, getConfidentialCimdReflectorMetadata as hO, mapClaudeObservationsToFindings as hP, mapObservationsToFindings as hQ, mapOpenAIObservationsToFindings as hR, observationFailure as hS, openaiPortalIssue as hT, parseClaudeExperienceObservations as hU, parseDirectoryObservationEnvelope as hV, parseOpenAIExperienceObservations as hW, relativeLuminance as hX, renderConformanceReportJUnitXml as hY, renderConformanceReportJson as hZ, retryAfterMsFromError as h_, OPENAI_MCP_SKILLS_METHODS as ha, OPENAI_OBSERVATION_CATALOG as hb, OPENAI_OBSERVATION_SCHEMA as hc, OPENAI_PLUGINS_CHANGELOG_URL as hd, OPENAI_PLUGINS_DOCS_BASE_URL as he, OPENAI_PLUGINS_LLMS_INDEX_URL as hf, OPENAI_PLUGINS_POLICY_PAGES as hg, OPENAI_RELEASE_RULES as hh, OPENAI_REQUIRED_TOOL_ANNOTATIONS as hi, OPENAI_SKILL_METADATA_PATH as hj, OPENAI_SUBMISSION_TEST_CASES as hk, type OpenAIAgentInterface as hl, type OpenAIAgentPolicy as hm, type OpenAIAgentToolDependency as hn, type OpenAIExternalPolicyPage as ho, type OpenAIManifestLocation as hp, type OpenAIPackageAsset as hq, type OpenAIPackageEntryStats as hr, type OpenAIPackageGap as hs, type OpenAIPackageManifest as ht, type OpenAIPackageSkill as hu, type OpenAIPackageSurface as hv, type OpenAIPluginsPolicyPage as hw, type OpenAIPolicyPageFormat as hx, type OpenAIPortalErrorId as hy, type OpenAIPortalErrorSeverity as hz, CLAUDE_APP_CONTENT_DOMAIN_SUFFIX as i, CLAUDE_APP_DESIGN_BUDGETS as j, CLAUDE_APP_HTML_MIME as k, CLAUDE_ATTESTATIONS as l, CLAUDE_CALLBACK_URLS as m, CLAUDE_DATA_HANDLING_MODES as n, CLAUDE_DECLARED_AUTH_MODES as o, CLAUDE_DOCS_BASE_URL as p, CLAUDE_EVIDENCE_PROVENANCE as q, CLAUDE_FINDING_CLASSES as r, CLAUDE_HOST_PROFILE as s, CLAUDE_INTRUSIVENESS_LEVELS as t, CLAUDE_LATENCY_BUDGETS as u, CLAUDE_OBSERVATION_IDS as v, CLAUDE_OBSERVATION_KINDS as w, CLAUDE_OBSERVATION_SCHEMA_VERSION as x, CLAUDE_POLICY_MANIFEST as y, CLAUDE_POLICY_PAGES as z };