ECL MCP Gateway Profile 1

Requirement-IDs: compiled Version: 1.0 Status: normative profile of ECL-1 Version 1.0

This profile places an ECL enforcement point between a governed AI system and the Model Context Protocol (MCP) servers it uses. The gateway receives every request that the governed AI system sends to an in-scope server. For each side-effecting request it builds a DCMP-2 decision request from a declared mapping, forwards the request only on a verified admit for exactly that request, returns a defined MCP error otherwise, and records the result in signed EBMP-2 evidence. The decision request binds the request's complete params by digest, and the gateway forwards the request exactly as it received it (MG1-004, MG1-005). Its release boundary is the write of the forwarded request to the server's transport: before that write it proves every obligation that the admit leaves to Enforcement (MG1-013), and it checks the authorization's validity, and the freshness of every proof, at a final observation of its clock taken after every step that can block (MG1-014). One gateway instance owns an evidence directory at a time, and recovery settles each journal once (MG1-016). The governed AI system holds no credential for the servers or for the systems their tools reach, so that, with the placement and path rules of MG1-001, the gateway is the only path to an effect. A change to a server's tools is a control change that waits for review.

The profile adds requirements to a claim that declares it. It removes none of the core requirements or of the requirements of DCMP-2, EBMP-2, DCMP2-HTTP-1 and TRUST-POLICY-1.

Requirement identifiers are allocated in the order requirements were added and never reused, so a later identifier appears among earlier ones where its subject belongs: MG1-013 to MG1-015 follow MG1-007, and MG1-016 and MG1-017 follow MG1-012.

Pinned sources

The machine-readable record of these pins is conformance/binding-sources.json.

Source Revision File SHA-256
MCP specification revision 2026-07-28, the revision that https://modelcontextprotocol.io/specification/latest names modelcontextprotocol/modelcontextprotocol tag 2026-07-28, commit 5f5440bb26a62e2cf3440b92da5a667efa03b267 schema/2026-07-28/schema.json ef70b61f99b6d2e5e3b46863822eab08dff6a45bedc7a08914e0e5b133f40203
Tools same commit docs/specification/2026-07-28/server/tools.mdx ed550806a58eb7744b858fb9f26001aa5e55dac9e2babe88317cc81d9d4c490d
Base protocol, error codes and _meta same commit docs/specification/2026-07-28/basic/index.mdx ce6b16e0b22544bb7efc201b09906b5f3efb6d55a83881766b36b8a71cfbeb95
Multi round-trip requests same commit docs/specification/2026-07-28/basic/patterns/mrtr.mdx a8671eb2a0d292c0dcbae19666ff4fc3d97f294ce8107e9f38259cc5a02a066b
Streamable HTTP transport same commit docs/specification/2026-07-28/basic/transports/streamable-http.mdx 22574bf11e004068493787203ce92be1162107cad717bcb805a15780d4fa69c9
Tasks extension (io.modelcontextprotocol/tasks) same commit docs/extensions/tasks/overview.mdx 7f33873a3d20d58ccfcd3162db21b073e21c5ab34d5242887703217ce7eeb62c
RFC 8785, JSON Canonicalization Scheme, for the digests of MCP content https://www.rfc-editor.org/rfc/rfc8785.txt rfc8785.txt 63d52294eb0e3f0014174288186d388b4ddbf2c67d1ce8af1d9726eb0c3ab240

In the pinned revision MCP has no protocol-level session and no initialize handshake; every request carries its protocol version in _meta. The only notification that the pinned schema defines for a client to send is notifications/cancelled (ClientNotification). A tools/call can return an InputRequiredResult, after which the client retries the call with inputResponses and requestState. Long-running work uses the tasks extension, in which tasks/update supplies input to a running task.

Scope and mapping

MG1-001§ — A claim of this profile MUST identify the MCP revision its gateway implements by the revision and digests that this profile pins, every in-scope MCP server, and the transports between the governed AI system and the gateway and between the gateway and each server; every request from the governed AI system to an in-scope server MUST pass through the gateway, and any other path from the governed AI system to the server or to a system its tools reach MUST be closed or disclosed in the Negative Boundary Annex (ECL1N-0008, ECL1N-0038). The gateway MUST NOT run in a process, container or account that the governed AI system can start, stop or configure, or in which it can execute code.

MG1-002§ — The gateway MUST treat as side-effecting, and send to the Decision Layer before forwarding, every tools/call request (including a retry that carries inputResponses or requestState), every tasks/update request for a task that a governed tool call created, and every other request that the Consequential Action Register maps to a consequential action class, such as a resources/read that discloses non-public data. It MUST refuse, with code -31007, every request whose method the claim neither maps to an action class nor lists as forwarded without a decision, and every tools/call for a tool that has no mapping. The claim MAY list server/discover, the list requests of the pinned revision (tools/list, resources/list, resources/templates/list and prompts/list) and subscriptions/listen as forwarded without a decision, unless the register classifies them as consequential, and MUST NOT list any other request. The claim MAY list for forwarding without a decision only notifications that the pinned revision defines for a client, and MUST NOT list one that the register maps to a consequential action class; the gateway MUST NOT forward any other notification from the governed AI system. As JSON-RPC 2.0 requires, it returns no response to a notification.

MG1-003§ — Each in-scope tool MUST have a mapping declared with the Consequential Action Register that names the server (identifier, name and version), the tool name, the ecl.digest.sha-256.v1 digest of the RFC 8785 canonical form of the tool definition as reviewed, the action class, the operation, the rule that derives the target from the call's arguments, and the evidence profile; the gateway MUST take the action class, operation and target of the decision request from that mapping, and never from the governed AI system's request or from the tool's own annotations.

MG1-004§ — The decision request MUST carry the call's arguments object, read from its RFC 8785 canonical form, as action.parameters, with action.parameters_digest its digest under DCMP2-007, and MUST carry the registered extension ecl.ext.mcp-binding.v1, marked critical, naming the server, the tool, the tool-definition digest and the mapping, and the digests of any inputResponses and requestState that the call carries; the gateway MUST refuse, and not forward, a call whose arguments, read from their RFC 8785 canonical form, are outside the restricted canonical JSON profile (IE1-001). A decision point that evaluates such a request MUST check that the mapping and the tool-definition digest it names are the reviewed ones that its register holds for that server and tool, and MUST NOT admit the request otherwise. The decision request MUST also carry, in ecl.ext.mcp-binding.v1, params_digest: the ecl.digest.sha-256.v1 digest of the RFC 8785 canonical form of the call's complete params object, _meta included. The gateway MUST refuse with -31007, and not forward, a request that has a member other than jsonrpc, id, method and params, or whose id or params holds a number that RFC 8785 does not represent exactly: a number of magnitude greater than 2^53 - 1, beyond the range [-(2^53)+1, (2^53)-1] that RFC 7493 section 2.2 gives for exact integers, or any other number whose RFC 8785 form does not have the value written.

Forwarding and refusal

MG1-005§ — The gateway MUST forward a side-effecting request only after it has verified a DCMP-2 admit for exactly that request under DCMP2-HTTP-1 and the claim's trust policy, only within its use policy, only after it has discharged every pre-release obligation of the admit (MG1-013), and only through the release order of MG1-014, at a final observation of its clock that is no earlier than the authorization's valid_from and that, with the release guard added, is earlier than its valid_until; it MUST forward exactly the call that the admitted request binds: the forwarded request's method and params MUST be the call's, params MUST have the digest params_digest, and the gateway MUST NOT add, remove or change any member of the call. The gateway counts the uses of an authorization in the instance that holds them, so a deployment MUST route every use of an authorization through one gateway instance, or share the use store among its instances by a mechanism that this profile does not specify, so that the count of ECL1N-0186 item 3 covers every instance.

MG1-006§ — For every request that it does not forward, the gateway MUST return a JSON-RPC error response with the code that the error table of this profile gives for the cause and a data.ecl member naming the cause and its registered reason, and MUST NOT return a result; the codes lie outside the range that JSON-RPC 2.0 reserves (-32768 to -32000), as the pinned MCP text asks of application-defined errors. When more than one cause holds, the gateway MUST return the code of the first that holds in this order: -31007, -31008, -31004, -31005, then the code of the decision's outcome (-31001, -31002 or -31003), then -31010, -31011 and -31006. A pre-release obligation that the gateway has not discharged when it refuses is unproven, so the refusal of an admit that carries one is -31010, whether or not its authorization is also unusable. A record cannot show that the gateway's journal failed (-31011, MG1-017), so a checker that reads a record whose refusal is -31011 checks only that no earlier cause holds.

MG1-007§ — When the decision service cannot be reached, does not answer within the declared total timeout, or answers with anything other than a verified decision response for the request, the gateway MUST NOT forward the request, MUST return the error for an unavailable or invalid decision, and MUST NOT retry within the decision attempt (DHB1-007, DHB1-008). A decision response that does not carry exactly one ecl.ext.decision-basis.v1, or whose decision basis fails DCMP2-053 with the record's decision_request as the request it answers, with the response clock-skew allowance of the reference verification profile, zero (DCMP2-047), is not a verified decision response, and a record that holds such a response as an answered decision MUST be reported with ecl.rule.decision-basis-one-way.v1. Each DCMP-2 message that the record holds, other than the response of a decision whose status is invalid, MUST satisfy the semantic rules of DCMP-2, and a record that holds one that fails a rule MUST be reported with that rule.

Release boundary and pre-release obligations

MG1-013§ — Before it releases a request, the gateway MUST discharge each obligation of the admit whose satisfaction is before_release and each witness_result of ecl.ext.consequence-coupling.v1 whose outcome is required_before_release (DCMP2-017). For each, it MUST obtain a proof that is authenticated under the claim's trust policy as issued by a principal that the claim accepts for that obligation, never the governed AI system, and, for an approval record, signed only by keys that the trust policy marks with ecl.purpose.approval.v1; that names this decision request by its message_id, with the request's digest or the digest of its action; that is fresh under the rule that the claim declares for the obligation's type, both when the gateway checks it and at the final observation of MG1-014; and that the gateway has not used for another release. It MUST check the proof with the verifier that the claim declares for the obligation's type. The claim MUST declare the obligation types its gateway discharges, with the verifier, the accepted issuers and the freshness rule of each. The gateway MUST refuse with -31010, and forward nothing, when the admit carries a pre-release obligation of a type that the claim does not declare, or when a proof is absent, not authenticated, bound to another request, stale or already used, or fails its check. It MUST NOT treat the recognition of an obligation's type, or the signed admit itself, as a proof. A deferred witness that an ecl.obligation.verify-witness.v1 obligation of the admit names is discharged with that obligation's proof, and any other deferred witness as an obligation of that type whose value is the witness's URI. The gateway MUST record the discharge in the execution_released event, in ecl.ext.obligation-discharge.v1, with an entry for each obligation and each deferred witness as EBMP-2 EBMP2-070 gives them, and in the record's obligation_discharge. Each entry MUST give fresh_until, the instant after which the proof no longer shows the obligation met, whenever the proof's type declares a freshness bound, and MUST NOT give one later than the proof declares: for an approval record, no later than the expires_at of the human_intervention event; for a witness result, the proof's observed_at plus the maximum age that the claim declares for the witness; a proof of a type that declares no bound has no fresh_until. The gateway MUST carry each proof that is an ECL object, a signed envelope of a registered type, in the evidence bundle of the exchange, a witness's verification event among its observations and any other proof among its envelopes, with the entry's reference naming that object and its digest the object's digest (EBMP2-070); a proof that is not an ECL object is recorded by its kind, reference and digest only.

MG1-014§ — The gateway's release boundary is the write of the forwarded request to the server's transport. Its effect boundary is the server's execution of the call, which comes after the release boundary and which the gateway does not observe, so a check at the release boundary does not show that the authorization was valid when the server acted (ECL1N-0185, ECL1N-0188). An admit whose obligation list names one obligation id more than once fails the gateway's reading of the decision: the gateway MUST refuse it with -31005 before step 1 and consume no use. On a verified admit the gateway MUST take these steps in this order, and MUST NOT write the request unless every step passes: (1) under a lock that orders it with every other use of the same authorization, after it holds the lock, read a monotonic clock and then its wall clock and, only when the wall reading is no earlier than the authorization's valid_from, the reading plus the release guard is earlier than its valid_until, and the use policy allows another use, consume one use; (2) record authority_consumed; (3) record release_prepared, which names the attempt; (4) discharge the pre-release obligations (MG1-013); (5) under the lock, read the monotonic clock first and the wall clock after it, so that a pause between the two readings shortens what remains of the window and never extends it; the wall reading is the final observation, and the gateway MUST release only when it passes the test of step 1 and, with the release guard added, is earlier than every fresh_until of the discharge, and MUST refuse with -31006 and the outcome reason ecl.error.clock-regressed.v1 when the final observation is earlier than the wall reading of step 1, or when the wall time elapsed since step 1 differs from the monotonic time elapsed since step 1 by more than the release guard; (6) record execution_released, whose observed_at is the final observation and whose release_guard_ms is the release guard, and then write the request. The release guard is the bound that the claim declares on the delay from the final observation to the write. Immediately before the write, after the connection to the server is open, the gateway MUST check that the time elapsed since the final observation, measured by the monotonic clock, is within the release guard and that the final observation plus that time is earlier than valid_until and than every fresh_until, and MUST NOT write the request otherwise. When it refuses after step 1, the gateway MUST record an execution_terminal event in state cancelled, with no side effect, a residual_state stating that the request was not written, and the registered reason of the refusal in ecl.ext.outcome-reason.v1, since a non-release state would contradict the consumed authority (EBMP2-053); it returns -31010 when a proof is stale at step 5, whether or not the authorization is also unusable, and -31006 when the authorization would not be valid at the write, when the write would not begin within the release guard, or when the clock regressed. A failure of the evidence sink at any of these steps ends the attempt as MG1-017 gives, and nothing is written to the server before execution_released is durable.

MG1-015§ — The forwarded_at of a record MUST be the final observation of MG1-014, the time that the execution_released event records: the last reading of the gateway's clock before the release, after which the release, the write of the request, begins within the declared release guard. The gateway MUST NOT record an earlier sample, such as the time of the check of step 1, as the time of the forward or of the release, and MUST record the guard it applied as release_guard_ms of the execution_released event and as release_guard_milliseconds of the record. Immediately after the write to the transport returns, the gateway MUST measure by the monotonic clock the time elapsed since the final observation and, when the final observation plus that time is at or after valid_until, MUST set released_after_valid_until to true in the ecl.ext.mcp-binding.v1 of the terminal event and mark the extension critical (EBMP2-071); it MUST NOT set that member from the time at which the server answered, and a checker MUST report a record whose terminal carries it set to true. When the server's answer arrives, whatever it is, the gateway MUST observe its clock and record that observation as forward_completed_at in the record's forwarded member and in the extension of the terminal event. A forward_completed_at at or after valid_until is a completion after the window: a verifier reports it beside its verdict, and it is not by itself a finding (EBMP2-072).

Change control and credentials

MG1-008§ — A tool added to or removed from an in-scope server's tool list, a change to any member of a tool definition, and a change of the server's name or version in io.modelcontextprotocol/serverInfo MUST be treated as a control change that requires review under ECL1N-0170: the gateway MUST compare the current definition, obtained from tools/list, with the mapped digest before it forwards a call, and MUST refuse calls to a changed or added tool, and to every tool of a server whose identity or version changed, until the change is reviewed and the mapping updated.

MG1-009§ — The governed AI system MUST NOT hold a credential, token, key or connection that reaches an in-scope server or a system that one of its tools reaches; the gateway, or a credential broker behind it, MUST hold that material, MUST use it only for forwarded requests, and MUST NOT pass a credential that the governed AI system presents to the gateway on to a server.

Evidence and records

MG1-010§ — The gateway MUST record EBMP-2 evidence for each side-effecting request as an enforcement point: for a forwarded request, authority_consumed, release_prepared, execution_released, which carries the discharge of MG1-013 when the admit carries a pre-release obligation, and an execution_terminal event that carries ecl.ext.mcp-binding.v1 with the ecl.digest.sha-256.v1 digest of the RFC 8785 canonical form of the response the server returned and the forward's completion (MG1-015); for a refused request, an execution_terminal event in the non-release state that EBMP2-051 gives, or in state cancelled when the gateway consumed the authorization before it refused (MG1-014). The response the gateway returns to the governed AI system for a forwarded request MUST be the response whose digest the evidence records, with one exception: the gateway MUST NOT return a server error that uses a code of the error table, and returns such an error with its error.code replaced by -32603 and every other member unchanged.

MG1-011§ — The gateway MUST authenticate the decision service and each in-scope server with TLS under trust anchors that its operator configures, with plain HTTP only on the loopback interface and only when explicitly enabled for testing; it MUST NOT follow redirects from either, and MUST apply the DCMP2-HTTP-1 limits to decision exchanges and declared limits to MCP messages before parsing them.

MG1-012§ — A gateway that retains exchange records MUST write each one as ecl_mcp_gateway_exchange_v1, valid against https://externalcontrollayer.ai/ns/schema/binding/mcp-gateway-exchange/1, and every value of ecl.ext.mcp-binding.v1 it emits MUST validate against https://externalcontrollayer.ai/ns/schema/extension/mcp-binding/1. A record member that holds MCP content (call, tool_definition, the request of forwarded, server_response and returned) MUST hold it as the JSON object itself when the content, read from its RFC 8785 canonical form, lies within the restricted canonical JSON profile (IE1-001), and otherwise as an object whose only member, rfc8785, is the canonical base64, with padding, of the content's RFC 8785 canonical bytes. A checker MUST treat a record as failing its schema when such a member does not decode to the RFC 8785 canonical form of a JSON object that lies outside the restricted profile, or when the content it decodes does not satisfy the definition that the record schema gives the same content held as itself (jsonRpcRequest for the call and the forwarded request, jsonRpcResponse for the server's response and the response returned, and toolDefinition for the tool definition), and otherwise MUST apply this profile's rules to the content it decodes.

MG1-016§ — A gateway that retains exchange records MUST record each EBMP-2 event of a side-effecting request in a journal, together with the exchange record and the signed decision messages once the decision is known, and MUST make each entry durable, by flushing the journal file and, when it creates the file, its directory to storage, before it takes the step that the entry evidences. The gateway MUST hold an exclusive lock on its evidence directory, taken before it reads, settles or writes any journal there and held for as long as it can write there, so that one instance at a time owns the directory; a gateway, a recovery command or a library caller that finds the lock held MUST stop before it reads or writes anything else in the directory. It MUST create the evidence directory and the directories of its journals, records and bundles accessible to its own account only, and MUST flush each new directory into its parent. One journal holds exactly one exchange: only the entry that opens the exchange creates its journal, and every later entry is appended to the journal that exists; when no journal exists for an entry before execution_released has been journaled, the gateway MUST refuse the request with -31011 and restore the use, and after execution_released it MUST report the condition and still record the terminal event from what it holds in memory; an opening entry for a journal that already exists MUST be refused with -31011 before any use is consumed. The gateway MUST keep an exchange's journal until the exchange's record and evidence bundle are durable, and MUST NOT delete a journal when it recovers. When it starts, under the lock, it MUST settle each journal that remains by what the journal holds, never by what it lacks, and MUST NOT retry the request. It MUST first truncate an incomplete final line, as the gateway also does before it appends to a journal. An exchange whose journal holds execution_released and no execution_terminal event has an unknown outcome, recorded by a terminal event in state unknown with no side effect known and a residual_state that states the uncertainty; an exchange whose journal holds no execution_released was not sent, and is recorded as cancelled after an admit and in the non-release state that the decision gives otherwise; and an exchange whose journal holds its terminal event keeps it. A journal that does not open with the entry that opens an exchange, that holds entries of more than one exchange, or that cannot be parsed beyond an incomplete final line is damaged: recovery MUST keep it in place, list it in its report with the reason, settle nothing from it and restore no use. For each journal that it settles, recovery MUST append to the journal, and make durable, an entry that records its decision, the outcome and the identifiers of the records it will write, before it writes any record or bundle, and an entry that records completion after it has written them; a journal with a recorded decision and no completion MUST be completed from the decision, and a journal with a recorded completion MUST be skipped. Record identifiers MUST derive from the journal, from the exchange's identity and the entry's kind, so that a repeated recovery writes the same files; a record that already exists under its identifier MUST be left as it is and never signed again, and recovery MUST NOT produce a second signed terminal event for one exchange. A terminal event that recovery records MUST carry recovered_at in its ecl.ext.mcp-binding.v1, and its record MUST carry recovery, with the time of the recovery and what the journal showed. The claim MUST declare which of these failures its journal survives: the exit of the gateway's process, a crash of its host, a failure of its storage, and the loss of an evidence service that it writes to.

MG1-017§ — When the gateway cannot make durable an entry that MG1-016 requires before the release, because a write or a flush fails, its storage is exhausted or its evidence sink fails in any other way, it MUST NOT release the request and MUST NOT take the step that the entry would evidence, and it MUST return -31011 unless an earlier cause of MG1-006 holds. The gateway MUST catch every error that the evidence sink raises, so that none ends the exchange with a use consumed and no terminal recorded. When the failure comes after the use was consumed, the gateway MUST restore the use and record the attempt as cancelled when the journal can take the entry; when it cannot, the journal shows release_prepared without execution_released, which recovery settles as not released. Once execution_released is durable, a failure of the sink MUST NOT stop the write: the gateway reports the failure, and the records that it could not write are produced by the next recovery from the journal. Only a release that was durably journaled and whose terminal event is missing is recorded as unknown on recovery.

Error table

The gateway returns these codes in the JSON-RPC error member, with data.ecl.code set to the name in the second column and data.ecl.reason set to a registered reason or error identifier. For a refusal that a decision carries, data.ecl.request_id names the DCMP-2 request, and data.ecl.reason is left out when the response carries no reason. The row of -31009 is not a refusal: it reports a forwarded request whose response never arrived. When several causes hold, MG1-006 gives the order: a request that has no mapping is refused as not mapped before anything else is checked, and a decision is asked for only when the tool and the server's identity still match the reviewed mapping. A server's own error never carries one of these codes to the governed AI system (MG1-010), so a code of this table always means what the table says.

Code data.ecl.code Cause data.ecl.reason
-31001 denied The decision is deny The response's first reason code, when it has one
-31002 held The decision is hold The response's first reason code, when it has one
-31003 challenged The decision is challenge The response's first reason code, when it has one
-31004 decision-unavailable The decision service is unreachable, silent past the timeout or answers with a status other than 200, or the signed decision request exceeds the envelope cap of DCMP2-HTTP-1 (DHB1-003) ecl.error.dependency-unavailable.v1
-31005 decision-invalid The response fails signature, purpose, principal, schema, semantic or binding checks, such as a decision basis that breaks DCMP2-053, or names one obligation id more than once (MG1-014) ecl.error.dependency-response-invalid.v1, ecl.error.integrity-invalid.v1 or ecl.error.request-binding-invalid.v1; ecl.error.dependency-response-invalid.v1 for a repeated obligation
-31006 authorization-unusable The admit is verified, but its authorization is not yet valid, has expired, would expire within the release guard of the gateway's observation or before the write of the request, or the write could not begin within the release guard after the final observation, or is used up, or the gateway's wall clock read earlier at its final observation than at its first check, or disagreed with its monotonic clock by more than the release guard (MG1-014) ecl.error.expired.v1, ecl.error.replay.v1 or, for a regressed clock, ecl.error.clock-regressed.v1
-31007 not-mapped No mapping for the tool or method, arguments that are not a JSON object, null included, or that lie outside the restricted canonical profile in their RFC 8785 form, arguments from which the mapping derives no target, a member of the request that no decision binds, or a number in its id or params that RFC 8785 does not represent exactly (MG1-004) ecl.error.binding-unsupported.v1
-31008 unreviewed-change The tool list, a tool definition or the server identity differs from the reviewed mapping ecl.error.control-change-unreviewed.v1
-31009 forward-failed The request was forwarded and no response arrived within the forwarding timeout, so its effect is unknown ecl.error.dependency-unavailable.v1
-31010 obligation-unproven The admit carries an obligation whose satisfaction is before_release, or defers a state witness, that the gateway did not discharge: its type is not one that the claim declares, or its proof is absent, not authenticated, bound to another request, stale when checked or no longer fresh at the final observation, already used, or fails its check (MG1-013, MG1-014) ecl.error.obligation-unproven.v1
-31011 evidence-unavailable The gateway cannot make durable an event that must precede the release: a write or a flush of its journal fails, its storage is exhausted or its evidence sink fails, or the exchange's journal is missing or already exists when the gateway opens it (MG1-016, MG1-017) ecl.error.evidence-unavailable.v1

Evidence states (informative)

Server response execution_terminal state
A result with resultType complete and isError false succeeded, as the server reports it (EBMP2-015)
A result with isError true, or a JSON-RPC error failed
An InputRequiredResult, or a task handle unknown, with residual_state naming the input request or task; the retry or the task's later result is a new request
No response within the forwarding timeout, or a broken stream unknown, with residual_state stating that the effect is uncertain
No write: the gateway consumed the authorization and then refused, for an unproven obligation or one whose proof was no longer fresh at the final observation, a failed final check, a regressed clock, a journal that failed or an evidence sink that failed (MG1-014, MG1-017) cancelled, with no side effect, residual_state stating that the request was not written and the refusal's reason in ecl.ext.outcome-reason.v1
Recovery: the journal holds execution_released and no terminal event (MG1-016) unknown, recovered, with residual_state stating that the outcome is not known
Recovery: the journal holds no execution_released (MG1-016) cancelled after an admit, otherwise the non-release state of the decision, recovered
Recovery: the journal is damaged, since it does not open with the exchange's first entry, holds more than one exchange or does not parse beyond a torn final line (MG1-016) None: the journal is kept and listed with its reason; nothing is settled and no use is restored

The digest in ecl.ext.mcp-binding.v1 covers the whole JSON-RPC response object, so an error response is bound as a result is. A number in the server's response that RFC 8785 does not represent exactly is bound, and recorded, as the nearest double. The evidence level of a terminal event is issuer-asserted unless a separately keyed observer corroborates it (EBMP2-061).

The digests of MCP content (tool definitions, input responses, request state and responses) are of its RFC 8785 canonical form, which keeps floating-point numbers. The decision request's action.parameters_digest is of the restricted canonical form (IE1-001), which has none, so a call whose arguments, read from their RFC 8785 form, hold a number with a fractional part, or an integer beyond 2^53 in magnitude, is refused with -31007 rather than rounded. A number written with a fraction or an exponent whose value is an integer, such as 2.0 or 1E+0, is that integer in RFC 8785 form, so a call that holds one is decided with the integer, which its record holds (MG1-004, MG1-012). Elsewhere in params, and in id, the gateway refuses a number that RFC 8785 does not represent exactly (MG1-004): it reads such a number as the nearest double, so the forward would carry another value than the call did, and params_digest, which is of the RFC 8785 form, would bind the rounded value. The gateway sees such a number only in the text it receives; an exchange record holds MCP content in RFC 8785 form, in which only a magnitude greater than 2^53 - 1 stays visible.

An exchange record is strict JSON (IE1-002), which also has no floating-point numbers. MG1-012 therefore holds MCP content outside the restricted profile in encoded form, so that a call refused for a fractional number in its arguments, a tool definition whose input schema holds a fractional bound, and a server's response that holds a floating-point number can all be recorded. The encoded bytes are the content's RFC 8785 canonical form, the same bytes that its digests cover. Content held in encoded form satisfies the same definition of the record schema as content held as itself, so that the encoding carries no call, request, response or tool definition that the schema would refuse, such as a call with a null id.

When a server answers a forwarded request with a JSON-RPC error whose code is one of the error table's, such as -31001 for an error of its own, the evidence binds the server's error as it arrived, and the governed AI system receives the same error with the code -32603 (MG1-010). Otherwise the governed AI system could read the server's error as a refusal by the gateway, although the request was forwarded and may have had an effect.

Release timing and durability (informative)

The final observation of MG1-014 is the last time the gateway reads its wall clock before it writes the request; the forwarded_at of the record and the observed_at of execution_released are that observation, and the event's release_guard_ms is the guard that applied (MG1-015). The observation is the recorded release time, and the release begins within the guard after it: between the two the gateway signs the release event, makes it durable and opens the connection to the server, with a TLS handshake when the server uses TLS. The gateway does not release when the final observation plus the guard reaches valid_until or the end of a proof's freshness, and immediately before the write it measures, with a monotonic clock, the time elapsed since the final observation, and does not write when that time exceeds the guard or when the authorization or a proof would have ended. The monotonic clock is read before the wall clock at step 1 and at step 5, so a pause between the two readings of step 5 only shortens what remains; a wall clock that reads earlier at step 5 than at step 1, or that disagrees with the monotonic clock by more than the guard, is refused as regressed, with the reason ecl.error.clock-regressed.v1. A pause of the process after the last measurement before the write, or a clock that is wrong in a way the two readings do not show, is not detected before the write. Immediately after the write returns, the gateway measures the elapsed time again and sets released_after_valid_until when the write may have come late; forward_completed_at is when the server's answer arrived (MG1-015). A late answer to a timely release is reported by a verifier as a completion after the window and is not a finding (EBMP2-072): the answer's time says when the server replied, not when the request was written.

A pre-send observation bounds the delay to the write only by the declared guard, and it does not establish when the server, or a system that the tool reaches, acted on the request: the server may queue it, and the effect may occur later in another system. Authority checked at the point of effect, by the server or by the system behind it, is outside this profile; a deployment that needs it binds the authorization to that point by other means.

A journal that meets MG1-016 can be one file per exchange in the evidence directory, to which the gateway appends each entry and which it flushes to storage before it takes the step that the entry evidences. The lock of MG1-016 is held by whatever writes the directory, for as long as it can write there: a gateway that serves requests, a recovery run or a library caller. A second holder is refused before it reads or writes anything else, so two writers in one process conflict as two processes do. Only the entry that opens an exchange creates its journal, and an opening entry for a journal that exists is refused. A failure or exhaustion of the storage before the release makes the gateway refuse with -31011, so no request is sent without its evidence; the gateway catches every error of its sink, and a failure after the use was consumed restores the use and ends the attempt as cancelled (MG1-017). After the release, such a failure leaves the journal as it stood: the gateway returns the server's response, since the request may have had its effect, and recovery produces the records. A deployment that places its evidence directory on networked storage, or writes to an evidence service, inherits the guarantees of that storage or service and declares them (MG1-016).

Recovery runs under the lock and settles each journal once. It truncates a torn final line, classifies the journal, appends an entry that records its decision, with the outcome and the identifiers of the records it will write, flushes it, writes the records and the bundle, and appends an entry that records its completion. A journal with a recorded decision and no completion is completed from the decision; one with a recorded completion is skipped. Record identifiers derive from the exchange's identity and the entry's kind, so a recovery run twice, or stopped and run again, writes the same files, and a record that exists is left as it is. A journal that does not open with the entry that opens an exchange, holds entries of more than one exchange, or fails to parse beyond a torn final line is listed as damaged with its reason and kept; nothing is settled from it and no use is restored. The write to the server and the local journal cannot be made one atomic step. A request whose release the journal holds and whose outcome it does not hold is recorded, after recovery, as unknown, and stays unknown until the server's records or another observation settle it; the gateway never retries it, since a retry could repeat an effect. A gateway that holds the use counts of authorizations in memory restores, on recovery, those that unsettled journals show, and a response binds one decision request (DCMP2-044, DCMP2-045), so it cannot serve a later call after a restart. Two instances that each hold a use count for one authorization do not see each other's uses, which is why MG1-005 routes every use of an authorization through one instance or a shared use store.

Exchange record (informative)

An ecl_mcp_gateway_exchange_v1 record holds one request that the gateway sent to the Decision Layer or refused; a request that the claim lists as forwarded without a decision needs no record, nor does a request refused for a number, in any of its members, that RFC 8785 does not represent exactly, which a record could not hold as it arrived, and a record that shows a forward is checked as a request that needed an admit. Its members are the in-scope server with the identity it reports; the call as received, and when (received_at); the tool's mapping and its current tool_definition when it has them; the decision_request and the decision, whose status is answered, unavailable or invalid; the uses of the authorization before this request (prior_uses); the gateway's release guard (release_guard_milliseconds); the release_prepared event (prepared), the discharge of the pre-release obligations (obligation_discharge) and the execution_released event (released), which carries release_guard_ms, when the gateway reached those steps; a forwarded member, with forwarded_at and forward_completed_at, only when the gateway released the request; the server_response; the response returned to the governed AI system, and when (returned_at); the terminal event; and, in a record that recovery wrote, recovery, which such a record holds in place of returned. The members that hold MCP content may be in the encoded form of MG1-012. The rules registered for this profile, whose names begin ecl.rule.mcp-, check a record after its schema.

Security considerations

The gateway's guarantee rests on two facts that an assessor checks: no other path reaches the servers or the systems behind them (MG1-001, MG1-009), and the gateway's decision path cannot be altered by the governed AI system, because the gateway runs outside everything that system can execute code in or control (MG1-001). Tool descriptions and annotations are text that a server controls and that a language model reads; a changed description can carry instructions, which is why a change waits for review (MG1-008) and why annotations never choose the action class. The call's complete params are bound by digest, so an admitted request cannot be forwarded with other arguments, with other _meta members or with a member that changes how the server runs it, such as a task, and the authorization's use policy prevents a second forward. A member outside params and a number that RFC 8785 would round reach no decision at all: the gateway refuses the request first (MG1-004). The error data names a cause and a registered reason only, so a refusal does not return policy details to the governed AI system. An obligation that the decision leaves to Enforcement, such as a state witness or a human approval, is proven before the release by a proof that names the request and that the governed AI system cannot issue (MG1-013): recognizing the obligation does not prove it. The release order checks the authorization after every step that can block, so a slow evidence store or a wait for a lock cannot carry an expired authorization to the server (MG1-014), and every event is durable before the step that it evidences (MG1-016). One instance owns an evidence directory at a time, so a second instance cannot settle a running instance's exchanges as not written, and recovery settles each journal once, so a repeated or interrupted recovery leaves one signed terminal event (MG1-016). The clock is read twice at step 5, monotonic first, so a wall clock stepped back between the first check and the release does not extend the window (MG1-014), and a proof's freshness is tested at the release, not only when the proof is obtained (MG1-013).

Conformance

Kind What the citation establishes
Vectors The mcp-gateway suite holds a passing record for each row of the error table, each with the error that row gives, and passing records of a refused request whose method the claim neither maps nor lists, of a call refused for the first of two causes, of a call whose arguments hold a number with a fractional part or lack the argument the target comes from, of a call whose arguments is null, refused although its mapping gives a fixed target, of a server error returned with -32603, of content held in the encoded form, and of a call whose id holds a Unicode noncharacter, which puts the call and both responses in the encoded form, and refusals of a call with a member that no decision binds and of a call whose _meta holds a number that RFC 8785 rounds. It refuses a forward while the decision service is unavailable, without an admit, after the authorization expired, a second time under a single-use authorization, with arguments, a method, a tool or an id other than the call's and the admit's, among them the id "1" for a call whose id is 1 and the id true, which the record schema refuses, or with a member that the call did not carry; a forward on an answered admit whose decision basis breaks DCMP2-053, or that carries no decision basis; a decision request for a call with a member outside params or a number of magnitude greater than 2^53 - 1, and one that declares a negative budget debit; a forward of an unlisted method, or to a tool that has no mapping or whose definition changed after review; a decision request whose class or parameters do not come from the mapping and the call, whose params_digest does not cover a _meta member of the call, or that lacks the binding extension; a refusal returned as a result, with another code, with the code of a later cause or with an id other than the call's, among them the id "1" for a call whose id is 1; a forwarded response that the evidence does not bind, among them one returned with 0 where the server's response holds false, that the governed AI system did not receive, that reaches it with a code of the error table, or whose state the evidence misreads; a result returned for a forward that the server never answered; and encoded content that is not canonical base64, not the RFC 8785 form of its content, content within the restricted profile, or content that the record schema would refuse when held as itself: a call with a null id, forwarded without one or refused with an error that has none, and a call forwarded with the jsonrpc 1.0. For the release path (MG1-013 to MG1-017) the suite holds passing records of a forward whose two obligations and deferred witness are discharged, of the release order, of a refusal at the first check of an obligation that no declared verifier checks, of cancelled attempts after an unproven obligation, a failed final check and a journal that failed, with and without a recorded prepared release, and of recovered records of an unknown outcome and of a request that was not released; it refuses a forward on an admit with obligations whose release records no discharge, or a discharge without the deferred witness or the approval; a refusal of an undischarged obligation with -31006 or -31011; a release within the release guard of the window's end; a forwarded_at earlier than the release that the evidence records; a send that completed after valid_until, marked or not; a cancelled refusal recorded as expired_before_release, and a refusal at the first check recorded as cancelled; a recovered record of an unknown outcome that claims success, or whose classification is not one that the schema lists; and a record without the response returned. The suite also holds a refusal, with -31005 and no use consumed, of an admit that names one obligation twice; cancelled attempts whose proof was no longer fresh at the final observation (-31010), whose clock regressed (-31006 with ecl.error.clock-regressed.v1) and whose write could not begin within the release guard after the final observation (-31006); a release whose event carries release_guard_ms; and a forward written inside its window whose answer arrived after valid_until, recorded with forward_completed_at after the window and without released_after_valid_until; it refuses a release event without release_guard_ms and a discharge entry whose fresh_until is not later than the final observation plus the guard
Deployment tests A gateway's tests of its release path, with faults injected at each step of the release order and of the journal: a run on the real clock, crashes at each step followed by recovery, failures and exhaustion of its storage, two instances on one evidence directory, two writers of one evidence directory in one process and threads on one authorization, damaged journals of each class, a recovery stopped after its decision, run twice and run concurrently, a torn last line, a stale approval and an over-age witness at the release, a clock stepped back between the first check and the final observation and between the two readings of step 5, the evidence sink failing at each step, a full and a read-only directory, and a late answer inside the window; docs/bindings.md lists what each of these tests must observe
Assessor procedure Path enumeration and the absence of credentials in the governed AI system, transport configuration and change review; the declared release guard, obligation types and their verifiers, the failures that the journal survives, and the statement that every use of an authorization passes through one instance or a shared use store (MG1-005, MG1-013, MG1-014, MG1-016; the assessment area mcp-release-order-and-journal-declarations of conformance/requirement-evaluations.json)