A protocol schema is not a permission

A valid MCP tool request proved that arguments had the expected shape; V0M3 still had to establish identity, authority, resource scope, current state, approval, and recoverable effect semantics.

The request was perfectly valid.

It named an available tool. Every required field was present. The destination matched the declared type. The document revision was an integer. The protocol client could serialize it and the server could parse it.

The revision had never been approved for export.

Nothing in the schema was wrong because permission was never one of its promises. A schema can establish the shape of a request. Treating that as authority turns interoperability into an accidental access-control system.

V0M3 inserted a product decision between valid call and executable consequence.

Shape answered a narrow question

The remote tool accepted something like:

deliver({ revision, destination, format })

Its schema could say revision was an integer, destination one of several strings, and format an allowed value. It could reject a missing field or unknown enum.

It could not know whether the number referred to a V0M3 document the requester could access. It could not know whether that revision was current, whether the destination belonged to the workspace, or whether a human had reviewed the exact exported bytes.

Validation made malformed input less likely. It did not create the facts absent from the message.

The model proposed capability use

V0M3 treated a model-emitted tool call as a candidate request. It entered durable state with provider attempt, task, target, discovered schema, and raw safe arguments.

No adapter ran yet.

The orchestrator resolved the candidate to a reviewed product capability, normalized its arguments, applied task and policy constraints, and decided whether it could proceed automatically, needed approval, or must be denied.

This state was visible in the trace: proposed, normalized, policy checked, awaiting approval, executable, executing, and completed or unresolved.

A proposal could be syntactically excellent and remain permanently forbidden.

Before authorization, V0M3 established which person or service was making the product request. The model itself was not the principal.

A browser session, API credential, or scheduled workflow resolved to a requester identity under server control. Client-supplied names and model assertions were ignored. The task also carried its initiating identity and any explicit delegation.

If an asynchronous task outlived the original session, execution used durable delegated authority with scope and expiry rather than assuming the old browser user remained present.

Authentication answered who stood behind the request. It still did not answer what that identity could do now.

Authorization joined several facts

V0M3's authorization decision considered requester, workspace membership, task allowance, product capability, resource scope, document state, policy revision, destination, data classification, and approval status.

No one Boolean on the user record captured that combination. A document owner might edit a draft but lack permission to export a source with restricted material. A delegated reviewer might approve claims and be unable to publish. A task allowed to retrieve sources might have no delivery capability at all.

The policy engine returned allow, deny, or require approval with a bounded reason. It did not mutate the request or ask the model to find a looser interpretation.

Permission was relational and current.

The server tool called itself deliver. V0M3 did not expose that broad verb directly. A reviewed adapter implemented deliver_approved_artifact with narrower arguments and preconditions.

The model received the product capability description only in tasks eligible to prepare an export. It selected an opaque artifact handle and approved destination handle rather than arbitrary document IDs and paths.

The adapter resolved those handles after authorization and constructed the remote request. A guessed handle, wrong task, expired capability, or changed artifact failed locally.

The protocol schema remained useful at the final translation. Local meaning determined whether translation could happen.

Opaque handles were scoped capabilities

An artifact handle encoded no meaningful identifier for the model to manipulate. Server state bound it to requester, task, artifact digest, export profile, allowed destination class, capability revision, and expiry.

It was single-purpose. A handle valid for preview could not be passed to delivery. A handle copied into another workspace failed. A handle whose artifact had been superseded could still identify historical bytes but lost delivery eligibility under current policy.

Resolution returned the exact internal objects only after the surrounding request passed validation.

Opaque did not mean secure by obscurity. The unpredictable token referenced an enforced capability record with narrow semantics.

Normalization happened before approval

Remote schemas often allowed friendly aliases, optional defaults, relative paths, or flexible strings. Approving those raw values would let later adapter behavior change the consequence.

V0M3 resolved destination aliases, selected explicit format settings, canonicalized paths, rejected traversal, filled local defaults, and prepared the exact artifact first. The review displayed both human meaning and normalized values where the transformation mattered.

Approval bound the normalized product request and artifact digest. If a default or alias mapping changed, a previously signed decision could not acquire a new destination silently.

The server schema checked the final argument shape. The author approved the already resolved consequence.

An approval receipt named requester, task, capability, normalized arguments digest, artifact digest, target state, policy revision, adapter mapping, expiry, and nonce. It was not a general “user said yes” flag.

Execution consumed the receipt atomically with creation of the effect intent. Reuse returned the existing intent or failed if arguments differed. Revocation made an unused receipt non-consumable. Expiry was evaluated by the server's clock.

Changing any bound input required preparation and review again. Reauthentication could confirm identity but could not refresh agreement to changed bytes.

The receipt turned one decision into one bounded opportunity to act.

Policy ran again immediately before execution

Time passed between proposal, review, approval, and worker execution. Workspace membership could change. A destination could be disabled. The artifact could become restricted. A connection or tool mapping could require review.

V0M3 rechecked current policy and world preconditions at the execution boundary. The historical approval remained valid evidence of a past decision and insufficient authority when current conditions no longer allowed it.

A denial paused or canceled the intent according to policy. It did not rewrite the trace as if approval never existed.

This closed a race that schema validation could not even represent: a once-permitted call becoming impermissible while waiting.

Effect semantics controlled retries

The same well-formed request might be safe to retry for a read and dangerous for delivery. V0M3's capability registry recorded whether an effect was idempotent, queryable, compensable, reversible, and externally visible.

For delivery, one stable effect identity crossed every attempt. If the remote tool accepted an idempotency key, the adapter supplied it. If response delivery failed, the workflow queried destination state or paused as unknown before considering another call.

A server error message recommending retry did not override local effect policy. A protocol reconnect did not create a new semantic intent.

Permission covered whether an effect could occur. Recovery semantics governed how many attempts could pursue it safely.

An optional destination with a server-defined default seemed convenient. It was also a way for behavior to change outside V0M3 review.

The adapter rejected consequential ambiguity. Product defaults were explicit and versioned; remote defaults were ignored or normalized into a known value before approval. Unknown properties failed rather than being forwarded for future compatibility.

Examples, enum descriptions, and error text remained untrusted metadata. They could help integration review but could not alter policy or scope.

Strict mapping made upgrades slightly slower and prevented a schema extension from becoming an authorization extension.

Schema changes invalidated the mapping

V0M3 pinned the discovered tool schema digest and adapter revision. If the server added a required field, broadened an enum, or changed a description in a meaning-bearing way, the mapping entered review required.

In-flight intents retained their original evidence. A worker did not submit an old approved product request through a newly interpreted remote schema. The adapter either completed under a compatible pinned mapping or stopped with a migration outcome.

After fixture tests and review, a new mapping revision became available to new preparations. Existing approvals were re-evaluated according to whether normalized consequence truly remained identical.

Protocol evolution became controlled application migration.

Server authorization remained necessary

V0M3's checks did not justify an overprivileged server. The MCP server or underlying service still needed to authenticate its caller and enforce its own resource permissions.

Application mistakes happen. A compromised client could bypass UI and orchestration code. Least-privilege remote credentials and server-side scope limited the damage.

Where a server accepted unrestricted paths or used one administrator credential for every tool, V0M3 documented the residual risk and kept sensitive mappings disabled. A clean protocol interface could not compensate for an unsafe authority model behind it.

Defense existed at both boundaries because neither implementation was assumed perfect.

A handle might have been minted while a source or destination was allowed and used after its classification changed. V0M3 did not treat handle possession as timeless permission.

Resolution verified current requester, resource state, policy, mapping, and expiry. The handle's original scope placed an upper bound on what it could do; current policy could narrow that bound further.

For immutable prepared artifacts, bytes remained identifiable even after eligibility changed. The system could display and archive the artifact while refusing external delivery.

This separated identity from authority. Knowing exactly which object a token named did not imply permission to act on it.

A tool call might be allowed while one argument was not. A public source query could include a private paragraph. An approved destination could receive a prohibited attachment. A filename could reveal confidential project context.

V0M3 classified prepared inputs and applied data-flow policy before exposing them to a server. Redaction occurred deterministically where meaning remained valid; otherwise the request was denied. The approval view showed what would cross the boundary.

Logs stored digests and safe summaries by default, not full arguments. Diagnostic access followed separate authorization and retention.

Schema type string said nothing about the data's sensitivity.

Tool results did not inherit permission to act

A retrieval tool could return text saying another tool should be called. A server could include a follow-up link, suggested command, or prefilled arguments.

V0M3 ingested those values as untrusted result data. Any follow-up became a new candidate capability request under the current task. It received no authority from being recommended by a previously allowed server.

The model could reason about the suggestion if it was relevant. Product policy still decided whether the capability was offered, the resource in scope, and approval required.

Permission did not propagate transitively through content.

Denials were useful but bounded

A bare forbidden response made replanning difficult. A detailed policy dump could reveal capabilities and resources the requester was not allowed to know.

V0M3 returned a bounded denial category: capability unavailable for this task, target outside approved scope, current state changed, approval required, or policy no longer permits delivery. It could suggest a safe product action such as prepare a public artifact, request review, or continue manually.

It did not expose hidden destinations, broader roles, credential details, or exact policy internals. The trace retained a protected diagnostic reason.

A denial helped the next plan without becoming a map around the boundary.

Authors reviewed document revision, artifact contents, destination, visibility, expiry, and recovery limits. They did not have to interpret a remote tool schema to understand consent.

Technical detail remained inspectable: server identity, adapter mapping, normalized arguments, schema digest, and protocol request could be opened when diagnosing or auditing.

This layering supported two audiences without weakening either. The normal decision was understandable. The implementation evidence remained exact.

If a product cannot explain an effect without showing raw JSON, its capability boundary is probably underspecified.

Time of check and time of use stayed visible

The prepared patch passed review at one moment. Execution began later. The trace recorded both times, relevant revision checks, policy digest, connection state, and destination observation.

If a precondition changed between them, the worker produced a stale-precondition outcome instead of quietly preparing a new request. The author could inspect what changed and decide again.

V0M3 never claimed to eliminate the gap between check and use. It made the gap explicit and placed another deterministic check at its narrowest point.

The protocol call occurred only after that check, with the exact normalized arguments already bound.

One fixture let the server complete delivery and drop the connection before returning its receipt. The request remained schema-valid and authorized. V0M3 still could not honestly call it failed or successful.

The effect intent moved to outcome unknown. A query capability attempted to locate the artifact by effect identity. If the destination lacked query support, the interface paused with the known evidence and warned against blind retry.

A human could inspect the destination or choose a compensating plan. The trace retained the original approval and attempt.

Authorization determines whether an action may occur. It cannot manufacture knowledge about whether a distributed action did occur.

An unused approval could be revoked. A credential could be disabled. A tool mapping could be retired. Those changes prevented future execution through V0M3.

They did not delete an artifact already delivered. Correcting or withdrawing that artifact was another effect with its own capability, permission, and receipt. Where no such capability existed, the interface said so.

This kept capability lifecycle separate from external-state lifecycle. The revoke button did not promise time travel.

Permissions control opportunities to act. They are not universal rollback mechanisms.

Tests asserted forbidden consequences

Happy-path schema fixtures were the easiest part. The more valuable cases used valid messages with wrong authority: another workspace's handle, an expired approval, a changed policy, a broadened schema, duplicate delivery, hostile result content, and a target that changed after review.

Tests asserted that no server invocation occurred when policy denied. Replay mode failed if any fixture attempted a live connection. Unknown outcomes could not transition to retry without the required receipt or query evidence.

I also tested that safe reads remained usable after a consequential tool was disabled. A narrow denial should not collapse the entire writing workflow.

Security lived in negative paths as much as input parsing.

Audit linked permission to consequence

The durable trace connected requester identity, task, product capability, normalized argument digest, policy decision, approval, effect intent, adapter and schema revision, remote request evidence, result digest, receipt, and resulting world observation.

This let an investigation answer two separate questions: why was the call allowed, and what evidence says it happened?

A protocol log alone answered neither fully. An authorization log without remote and product outcome evidence could show a correct decision followed by a broken effect.

Trustworthy execution required the chain, not one green event.

In the original fixture, the model had selected a real tool and produced valid arguments for an unapproved revision. V0M3 normalized the request, resolved the artifact, found no matching approval receipt, and denied execution before contacting the server.

The author could open the document, prepare an export, inspect exact bytes and destination, and approve it deliberately. That created a new request with a different artifact handle and bound consequence.

The first call did not become valid retroactively. It remained evidence that syntax and intent can look reasonable while authority is absent.

This was the boundary I wanted the integration to preserve.

Standards need applications to say no

A protocol succeeds when independently built systems can communicate. That makes more capabilities reachable and gives mistakes a more efficient route too.

The answer is not to distrust schemas or avoid interoperability. It is to assign them the right job. Let the schema reject malformed messages. Let the adapter preserve semantics. Let authentication establish identity. Let policy decide scope. Let approval bind a human decision. Let effect records and receipts govern recovery.

Each layer contributes evidence the others cannot provide.

The request can be well formed, understandable, and useful. It can still be unauthorized.

A mature client must be able to say that plainly and stop before the protocol turns possibility into consequence.