Portal-Managed MCP Tool Access Control
Status
- Decision state: Proposed
- Owners: Light Portal and Light Gateway maintainers
- Design date: 2026-08-23
- Scope: Caller authorization and response filtering for Portal-managed MCP
tools published to
light-gateway
Purpose
Portal users can manage access rules and permissions for API endpoints from
API Admin, but a standalone tool created in Tool Admin has no equivalent
access-control workflow. This forces operators to either maintain gateway-local
configuration or add the tool name to access-control.skipPathPrefixes.
Neither is an acceptable steady state. Local files override config-server snapshots, and a skipped MCP tool bypasses both request authorization and response filtering.
This design adds first-class Tool Access Control to Light Portal while keeping the existing Light Gateway policy format and enforcement behavior.
Current Contracts
Gateway Tool Identity
Every published tool has an authorization endpoint key. An explicit tool
endpoint is used when present; otherwise the gateway derives the key as
{path}@{method}. It does not derive the key from the tool name. Because
path defaults to an empty string, a workflow-backed tool with neither
endpoint nor path resolves to @call; multiple such tools therefore
collapse onto the same policy key. Managed tools must not rely on that default.
Explicit logical identities may use names such as:
customer_360@call
workflow_mcp_smoke@call
The same endpoint key is used for:
tools/callrequest authorization;- MCP response filtering;
- optional
tools/listvisibility filtering; - policy logs, metrics, and audit context.
The endpoint key, not toolId or stableToolRef, is the runtime lookup key in
rule.endpointRules.
Three adjacent tool identities must remain visible and distinct:
endpointKeyselects the access-control endpoint rule;authorizationToolName, when configured intoolMetadata, is the tool name used by call authorization, response filtering, and CEL list authorization;endpointNameis the downstream MCP backend operation name.
The public tool name remains the lookup name for tools/call and is currently
used by permission-mode tools/list authorization. Portal preview must display
all four values so an operator can see any divergence.
Gateway Policy Format
The gateway already accepts tool policies through the standard access-control snapshot:
rule.endpointRules:
customer_360@call:
req-acc:
- req-access-light-portal.lightapi.net
permission:
roles: admin customer-service-agent
No new gateway policy document or tool-specific runtime handler is required.
tools/call remains the final enforcement point even when tools/list
visibility filtering is enabled.
Portal API Access Management
Endpoint Access Overview manages rules, permissions, and response filters for
an api_endpoint_t record. The current permission and filter projections are
therefore API-endpoint-centric. API access publication compiles those records
into rule.endpointRules and rule.ruleBodies for a target gateway instance.
Portal Tool Management
A standalone Tool Admin record can have no endpointId. Workflow-backed tools
created directly in Tool Admin commonly have this shape. They can be published
into mcp-router.tools, but they cannot enter Endpoint Access Overview and
cannot contribute access rules to the gateway snapshot.
Workflow Tool Access is a separate security boundary. It grants a workflow definition permission to use a pinned dependency. It does not authorize an end user or agent to invoke a gateway-published MCP tool.
Current Storage Reality
rule.endpointRules and rule.ruleBodies are registered as map properties.
Portal currently contains more than one resolution implementation, so the
physical table layout alone does not determine effective behavior.
The canonical portal-db create_snapshot procedure copies only active
instance, instance-API, instance-app, instance-app-API, association, and lower
inheritance rows. It combines the four instance-scoped sources into one
InstancePool; map properties are merged with jsonb_object_agg and recorded
as source level instance_merged. The runtime-config query used by the deployed
Portal service likewise reads active rows from all four instance scopes, and
the config-server assembler merges map values before generating values.yml.
ConfigPersistenceImpl.insertEffectiveConfigSnapshot, however, contains an
alternate Java resolver that omits active filters and applies
MAX(effective_value) across per-API groups. That path would select one whole
JSON value lexicographically rather than perform the canonical cross-API merge.
It must be removed, delegated to create_snapshot, or brought under the same
contract before carrier migration is considered portable across deployments.
Its presence is a resolver-parity defect; it is not evidence that the current
loc gateway is dropping 41 API policies.
On 2026-08-23, the running gateway’s host, service ID, and loc environment
tag resolved to portal-bff-loc. That instance had 42 active instance-API rows
for each rule property. The per-API endpoint maps contributed 831 distinct keys
and the instance row contributed one more. Both the running gateway cache and
the latest database snapshot contained all 832 keys; the snapshot reported
source level instance_merged. The loc deployment therefore has multiple
contributors but is not currently suffering the Java MAX winner loss.
The same deployment explicitly sets access-control.enabled: true and
defaultDeny: true, then skips workflow_mcp_smoke and customer_360. It is a
protected instance with two local bypasses, not an instance masked by the
shipped disabled default.
The current API access compiler is scoped to one (hostId, apiId, apiVersion)
and replaces the complete endpoint-rules and rule-bodies values on its selected
instance_api_property_t rows. Whole-property replacement is therefore a
hazard within one contributor row, while the canonical snapshot/runtime merge
retains unrelated API contributors. It still lacks explicit endpoint ownership,
duplicate-key conflict rejection, and a publication event describing which
source owns each merged key. Those are pre-existing API publication gaps, not
hazards introduced only by Tool Access.
The canonical database can mechanically merge instance-pool maps, but that is
not sufficient ownership control: it cannot reject two sources claiming the
same endpoint key, and a standalone tool has no natural instance-API carrier.
The map branch also calls jsonb_object_agg without ORDER BY. PostgreSQL
keeps one value for a duplicate key, so two contributors claiming the same
endpoint can produce an order-dependent winner. Portal must reject that
collision before it reaches the resolver and compute the publication digest
from its own canonical, key-sorted pre-merge; digest determinism must not be
inherited from database input order.
As defense in depth, the database merge should use a documented total order.
Ordering only by update_ts is insufficient when timestamps tie; the order
must also include stable source rank and source identity. That deterministic
tie-break still does not establish ownership or make duplicate keys valid.
Portal must therefore merge every active API and tool contribution with
explicit ownership before writing one canonical property value.
The existing mcp-router.tools publisher is the precedent: it performs an
identity-keyed mergeExistingTools, writes the authoritative result to
instance_property_t, and retires legacy per-API property rows.
Problem Statement
The control plane has two independently working halves:
- Tool publication creates the MCP catalog entry.
- API access publication creates endpoint access rules.
There is no Portal-owned connection between a standalone tool and the access rules for its published endpoint key. As a result:
- Tool Admin cannot assign roles, groups, positions, attributes, or users;
- Tool Admin cannot attach
req-accorres-filrules; - policy publication cannot prove that a protected tool has a matching rule;
- tool lifecycle changes can leave manually maintained policy keys stale;
- operators may use
skipPathPrefixes, which bypasses enforcement; tools/listvisibility andtools/callauthorization can drift.
Goals
- Add an Access Control action to Tool Admin.
- Reuse the established Endpoint Access Overview user experience.
- Give API endpoints and tools one logical access-target contract.
- Publish tool rules into the existing
rule.endpointRulesandrule.ruleBodiesproperties. - Use the exact endpoint key produced by tool publication.
- Keep
defaultDeny: trueas the safe fallback. - Support roles, groups, positions, attributes, users, request rules, response filters, and list visibility.
- Make create, update, retirement, replay, and republish deterministic.
- Keep tenant, instance, environment, and publication ownership explicit.
- Remove the need for gateway-local tool bypasses.
Non-Goals
- Replacing Light Gateway’s access-control runtime.
- Combining caller authorization with Workflow Tool Access grants.
- Changing workflow binding, digest, or environment validation.
- Making MCP authentication optional.
- Creating a second tool-only gateway policy format.
- Treating
tools/listfiltering as a substitute fortools/callauthorization. - Requiring users to model every standalone tool as a real HTTP API.
Proposed Control-Plane Model
Access Target
Introduce a Portal access-target abstraction:
AccessTarget
hostId
accessTargetId
targetType API_ENDPOINT | TOOL
targetId endpointId | toolId
endpointKey exact Light Gateway policy key
sourceVersion source aggregate version
active
An access target is a control-plane identity. The gateway continues to receive
only endpoint-keyed rules and does not need to understand targetType or
targetId.
For an API endpoint:
targetType = API_ENDPOINT
targetId = api_endpoint_t.endpoint_id
endpointKey = the published path-or-logical-operation key
For a tool:
targetType = TOOL
targetId = tool_t.tool_id
endpointKey = the authorization endpoint from the compiled tool publication
accessTargetId should be stable and host-scoped. It must not change when the
display name or description changes.
Endpoint-Key Authority
The tool publication compiler is authoritative for endpointKey. Tool Access
Control must consume the compiled authorization endpoint rather than
independently reconstructing it from a mutable display name.
Portal should require a stable explicit endpoint key before a tool becomes
access-policy-ready. Migration must read the endpoint key from the compiled
tool publication; it must never reconstruct it from a name. Portal may offer
to pin a non-empty compiled {path}@{method} value after showing it in preview.
It must not auto-pin @call when the path is empty, because that value is not
tool-unique.
Publication must reject:
- an empty endpoint key;
- an endpoint key without the expected operation suffix;
- exact duplicate keys in one gateway instance;
- a prefix or path-template rule that would shadow the endpoint key;
- a tool and API endpoint claiming the same key with different policy owners;
- a policy whose source version does not match the selected tool publication.
stableToolRef remains the immutable tool identity used by workflow bindings
and grants. It must not be substituted for the endpoint key in gateway policy.
Permissions and Rules
Access-target assignments should represent the existing principal dimensions:
- roles;
- groups;
- positions;
- attributes and attribute values;
- users.
Rule bindings should support:
req-accrequest authorization;res-filresponse row filtering;res-filresponse column filtering;- list visibility derived from the call permission block for protected tools; and
- one canonical, audited allow-all
req-accrule for public tools.
An explicit visibility block short-circuits claim evaluation in both
tools/list and the early claim-only stage of tools/call. The first release
therefore must derive visibility from permission rather than accept an
independently broader value. If independent visibility is added later, the
publisher must prove visibility is a subset of permission for every
principal dimension under the same claim mappings.
The logical model should be shared by API endpoints and tools. A staged schema migration may preserve the existing endpoint-specific tables while generic access-target projections are introduced, but new Tool Admin behavior must not create a second gateway policy representation.
Portal User Experience
Tool Admin
Add an Access Control row action for active, publishable tools. Keep it visually and semantically separate from Workflow Access:
| Action | Security boundary |
|---|---|
| Access Control | Which users and agents may discover or invoke this published tool |
| Workflow Access | Which workflow definitions may use this tool as a pinned dependency |
The Tool Access Overview header should display:
- tool name and
toolId; stableToolRef;- execution placement;
- explicit authorization endpoint key;
- effective
authorizationToolName; - downstream
endpointName; - current tool version and aggregate version;
- selected gateway instance and environment;
- policy readiness and publication status;
- last published snapshot revision.
Reused Access Panels
Reuse the existing access-management panels with an AccessTargetContext
instead of an API-only route context:
hostId
targetType
targetId
endpointKey
instanceId
environment
The overview should expose:
- request rules;
- role permissions;
- group permissions;
- position permissions;
- attribute permissions;
- user permissions;
- row filters;
- column filters;
- tools-list visibility;
- preview and publication status.
The components may retain API-specific adapters during migration, but commands and queries should use the generic access-target identity at their boundary.
Readiness States
Tool Admin should show one of these states:
| State | Meaning |
|---|---|
UNCONFIGURED | No access target or request rule exists |
CONFIGURED | Policy exists but is not published to the selected instance |
PUBLISHED | Active snapshot has the exact owned rule and matching tool and policy source versions |
STALE | Desired property values differ from the current snapshot, or the reviewed base values changed before apply |
CONFLICT | Another owner or runtime pattern collides with or shadows the endpoint key |
RETIRED | Tool or access target is inactive and its policy is being removed |
State precedence is RETIRED, CONFLICT, STALE, UNCONFIGURED,
CONFIGURED, then PUBLISHED; the UI should retain all secondary reasons.
Portal must not infer that an unconfigured tool is non-callable from
defaultDeny alone: the gateway may fall back to a prefix or path-template
rule. A protected publication requires an exact owned endpoint rule and must
block every conflict, stale source, and shadow match.
Commands, Queries, and Events
The exact Portal service names can follow existing naming conventions, but the contract should cover these operations:
Queries
- get access overview by target;
- list rules and principal assignments;
- preview compiled endpoint rules;
- list target gateway instances and environments;
- compare source versions with the active snapshot;
- report endpoint-key ownership conflicts.
Commands
- create or reactivate an access target;
- set or retire a stable endpoint key;
- assign or remove principals;
- attach or detach request and response rules;
- preview list visibility derived from permission;
- publish access policy to a gateway instance;
- retire tool-owned policy from an instance.
Commands must use expected aggregate versions. Events must carry hostId,
targetType, targetId, accessTargetId, and the source aggregate version so
replay cannot attach a policy to the wrong tenant or stale tool revision.
Publication Design
Inputs
A tool access publication candidate contains:
- the selected tool publication and binding;
- the compiled tool endpoint key;
- the effective tool name,
authorizationToolName, andendpointName; - tool ID, stable reference, version, and aggregate version;
- target instance and environment;
- access-target rules and principal assignments;
- current
rule.endpointRulesandrule.ruleBodiesownership metadata; - the resolved property IDs, active flags, and
value_typeregistrations; - the current snapshot ID and digest used as the review baseline; and
- the existing carrier values, digests, and aggregate versions used for the compare-and-apply guard.
Compilation
Compile a tool access target into the existing gateway shape:
rule.endpointRules:
customer_360@call:
req-acc:
- req-access-light-portal.lightapi.net
permission:
roles: admin customer-service-agent
groups: customer-operations
Rule bodies remain deduplicated by rule ID in rule.ruleBodies. Endpoint maps
and rule ID lists must be deterministically ordered before digesting or
publishing. Protected-tool list visibility is derived from the permission block
in the first release; it is not a separately editable allow set. A public tool
instead binds the canonical public rule described below.
Ownership and Merge Rules
Portal must merge all active contributors into one physical carrier per target
gateway instance. The authoritative homes are single instance_property_t
rows for rule.endpointRules and rule.ruleBodies, keyed by
(hostId, instanceId, propertyId). API and tool publishers must not write
independent complete values at different source levels and expect the config
server to merge them.
Per-source ownership belongs in Portal publication and binding records, not in separate config-property rows. Each compiled endpoint contribution records:
sourceType API_ENDPOINT | TOOL
sourceTargetId
sourceVersion
endpointKey
publicationId
instanceId
ruleBodyIds
ruleBodyDigests
The instance policy compiler must:
- load every active API-endpoint and tool contribution for the instance;
- replace only the selected source’s desired contribution in Portal state;
- reject exact ownership collisions and runtime pattern shadowing;
- retain unrelated API and tool contributions;
- remove only endpoint keys owned by a retired source;
- compute the union of referenced rule IDs and reject one rule ID with different bodies or digests;
- retain each rule body while at least one active endpoint references it and retire it only after its last reference disappears;
- deterministically rebuild the complete endpoint and rule-body maps;
- assert that both selected config properties are active maps with the expected property IDs;
- in one publication transaction, write both canonical instance values and deactivate every legacy instance-API and instance-app-API row for those properties against the accepted target revision.
This follows the identity-keyed merge shape already used by
mergeExistingTools. Resolver convergence is a strict predecessor to carrier
migration, not parallel work in the same rollout. Every supported snapshot and
runtime-config path must honor inactive rows and the canonical instance-pool
merge. The deployed create_snapshot procedure and runtime-config query meet
that requirement; the alternate Java MAX resolver does not. It must delegate
to the canonical procedure, implement identical semantics, or be proven
unreachable and removed. Physical deletion is not an acceptable compatibility
workaround because it discards the audit trail.
The migration transaction absorbs all active legacy contributions, writes the
merged instance carriers, and deactivates the higher-priority legacy rows as
one publication. Only the snapshot generated after that transaction may be
used for verification. Readiness permanently asserts that no active
instance_api_property_t or instance_app_api_property_t row exists for
rule.endpointRules or rule.ruleBodies on the target instance. The legacy
API publisher must be changed in the same phase so it cannot recreate one.
The tool publisher’s apiVersionId remains selection scope and provenance; it
does not make an API property row the owner of standalone Tool Access.
Runtime Matching and Conflict Analysis
Gateway request lookup tries an exact endpoint key first, then prefix and path
template matches, choosing the longest matching pattern. A missing @method
is treated as @call. Portal conflict analysis must reproduce those rules,
not merely compare strings.
For managed Tool Access, an exact rule owned by that access target is required
for PUBLISHED. Pattern fallback is legacy runtime compatibility and cannot
satisfy readiness. Preview must show every exact collision, ancestor-prefix
match, path-template match, implicit @call match, and the rule that the
gateway would select. This avoids disagreement with the gateway’s exact-only
validate_request_policy readiness check.
Effective Policy Context and Digest
Preview must load these effective instance settings from the target snapshot:
access-control.enabled,defaultDeny,accessRuleLogic, anddefaultInclude;skipPathPrefixesand its prefix relation to the endpoint key, public tool name, andauthorizationToolName;toolsListAccessControl.mode,unknownRuleFallback,maxCelEvaluations, and claim mappings.
The preview digest must include those values, compiled tool identities, all source owners and versions, the endpoint shadow report, referenced rule-body digests, property IDs and value types, legacy-row inventory, property actions, and accepted config revision. Identical property JSON is not an identical authorization outcome when the instance-global settings or winning source level differ. Portal computes this digest before persistence from a canonical, key-sorted ownership merge, then requires the generated snapshot to reproduce the same JSON and digest. The database aggregate is a verification target, not the digest authority.
Desired State and Promotion Behavior
Tool and access-policy desired state share one reviewed Portal workflow. The
review page compares the existing instance_property_t values with the proposed
canonical values and shows additions, removals, changed rules, public/protected
mode changes, and unrelated entries that will be retained. Applying the review
writes the catalog and policy carrier values with their expected aggregate
versions; a concurrent change rejects the apply and requires the comparison to
be refreshed.
Writing instance_property_t changes desired state but does not change a
running instance. Portal then creates and validates a candidate snapshot from
that desired state. Only an explicit promotion that makes the candidate current
activates the change. If property persistence, snapshot generation, validation,
or promotion fails, the previous current snapshot remains authoritative. Portal
reports the tool policy as PUBLISHED only after the current snapshot contains
the reviewed tool and policy values. The control plane must never temporarily
add the tool to skipPathPrefixes to bridge these stages.
Gateway Runtime Behavior
Protected tools reuse the existing endpoint-keyed authorization algorithm. The
gateway needs two bounded extensions: recognize the canonical public rule as
visible in permission-mode tools/list, and resolve the restricted nested
response path before applying the existing row or column filter actions. The
normal tools/call rule engine executes the public rule’s constant-true CEL
condition. Neither extension introduces a second policy source or bypasses
response filtering.
For tools/call:
- Resolve the configured tool.
- Resolve its authorization endpoint key.
- Apply
access-control.enabledandskipPathPrefixesgates. - Look up the exact endpoint rule, then any compatible prefix or path-template rule using longest-pattern precedence.
- Evaluate
req-accwith the authenticated principal, permission metadata, headers, and tool arguments. - Invoke the tool only when allowed.
- Apply configured
res-filrules to the normalized result.
For tools/list, the gateway supports none, permission, and cel modes.
Protected deployments should use permission or CEL mode and must show the
effective choice in Portal. CEL evaluates no more than maxCelEvaluations;
tools after that bound are hidden. Call authorization remains mandatory because
list visibility can be stale or argument-insensitive.
skipPathPrefixes is a prefix test applied to both the endpoint key and a tool
name. Today permission-mode list filtering passes the public tool name, while
CEL list filtering, call authorization, and response filtering pass
authorizationToolName. Portal must test every skip prefix against all three
values and block publication on any match. The gateway should separately align
permission-mode list filtering to authorizationToolName; until then,
acceptance tests must cover both list and call paths when the two names differ.
The loc value customer_360 demonstrates why this is a prefix check, not an
equality check: it bypasses customer_360, customer_360_v2, and every other
endpoint key or authorization tool name beginning with that string.
For response filtering, any unfilterable MCP result, missing rule body, rejected rule, rule-execution error, missing filtered body, serialization/application failure, or top-level row denial must suppress the original payload and return a bounded MCP access-control error. No error path may return the unfiltered backend result.
Security Requirements
- Protected gateway instances use
access-control.enabled: true. - Protected gateway instances use
defaultDeny: true. - Preview and readiness include
accessRuleLogic,defaultInclude, list mode, unknown-rule fallback, CEL limit, and effective claim mappings. - Portal publication never creates
skipPathPrefixesfor managed tools. - A prefix match against the endpoint key, public tool name, or
authorizationToolNameis reported as a policy-readiness error. - Every mutation and publication is host-scoped and aggregate-versioned.
- Cross-tenant target IDs and endpoint ownership are rejected.
- An explicit access policy cannot silently fall back to another endpoint key.
- Missing exact rules, unknown rule bodies, stale source versions, ownership
collisions, and pattern shadowing fail publication closed. At runtime a
missing request-rule body denies a call; permission-mode list visibility may
apply
unknownRuleFallback, so list visibility alone is never proof of call authorization. tools/callalways reauthorizes regardless oftools/listvisibility.- Response filtering uses the same endpoint key and principal as request authorization.
- Audit records identify the user, target, endpoint key, source versions, instance, environment, and publication digest.
- Secrets and bearer tokens are never persisted in access-target events or policy snapshots.
Lifecycle Behavior
Tool Update
Description and metadata changes do not change the endpoint key. A tool version or access-relevant schema change marks the publication stale until republished.
Tool Rename
A display-name change must not implicitly rename a managed endpoint key. An endpoint-key change is an explicit migration that publishes the new denied or protected key before retiring the old key.
Tool Retirement
Retiring a tool deactivates its access target and removes only its owned endpoint-rule contribution from selected gateway instances. Shared rule bodies are retained while another active endpoint references them. Publication must reject the same rule ID supplied with different body content; retirement removes a body only after its reference set becomes empty.
Replay
Projection replay must produce the same access target, ownership records, compiled endpoint map, and digest. Replayed stale events cannot overwrite a newer aggregate version or active publication.
Migration Plan
Phase 0: Demonstration Bridge
For an immediate demonstration, operators may create an internal API version
with CALL endpoints whose endpoint strings exactly match the tool keys, then
use Endpoint Access Overview and the current API access publisher. This is a
temporary authorization projection, not the final Tool Admin experience.
This bridge is destructive on a shared property row today: the API publisher
compiles one API version and replaces the complete rule.endpointRules and
rule.ruleBodies values. Phase 0 is permitted only on an isolated demonstration
instance where those property rows have no other owner. It must not be used on
an instance with another API or tool policy contributor. Shared-instance
rollout waits for the instance-level merger in Phase 1B.
Do not remove a tool from skipPathPrefixes until the effective snapshot has
its exact owned req-acc and permission entry and no runtime shadow conflict.
Phase 1A: Snapshot Resolver Convergence
- Make every supported snapshot and runtime-config resolver use the canonical
active-row and instance-pool merge semantics. Prefer one implementation by
delegating Java snapshot creation to
create_snapshot. - Extend
config_snapshot_empty_collection_setup.sqland its snapshot tests with active and inactive cases for every override priority, including instance-app-API, instance-API, instance-app, instance, product version, environment, product, and default registrations. Cover fallthrough to the next priority and preservation of intentional empty maps and lists. - Add a parity gate that feeds the same contributors to every remaining resolver and requires identical canonical JSON, source level, and digest.
- Give the database map aggregate an explicit total order by update timestamp, stable source rank, and source identity; add a duplicate-key fixture proving that the resolver is reproducible even though Portal rejects the publication.
- Deploy and prove that convergence before enabling any carrier-migration command on a deployment where the alternate Java path is reachable.
Phase 1B: Authoritative Instance Merger and Access-Target Read Model
- Establish authoritative
instance_property_tcarrier rows for endpoint rules and rule bodies. - Assert active
mapregistrations for both properties on the target host. - Load all API and tool contributors, merge by owned identity, and atomically write the carriers while deactivating legacy higher-priority rows.
- Prevent the API publisher from recreating per-API rows for these properties.
- Add rule-body reference tracking and conflicting-body detection.
- Add the generic access-target identity and query contract.
- Backfill API endpoint access targets.
- Create tool targets from compiled gateway publication bindings.
- Detect exact collisions, pattern shadowing, and stale source versions.
- Keep existing API access commands working through adapters.
Phase 2: Tool Admin Access Control
- Add the Tool Admin Access Control action and overview.
- Reuse permission, rule, and filter panels.
- Add the bounded, schema-validated nested response target path to row- and column-filter editing and preview.
- Keep Workflow Access as a separate action.
- Add readiness and publication status.
Phase 3: Tool Policy Publication
- Compile tool targets into
rule.endpointRules. - Use the Phase 1B ownership and deterministic instance merger.
- Compare proposed carrier values with current
instance_property_t, apply them with expected aggregate versions, create and validate a candidate snapshot, and explicitly promote it to current. - Compile and digest nested response target paths with their output-schema dependency.
- Support retire and replay without deleting unrelated API rules.
Phase 4: Protected Rollout
- Require Phase 1A resolver qualification before any protected multi-API
publication. If a deployment can reach the alternate Java
MAXresolver, freeze further API access publication on an already-enabled instance and do not enable a new instance until resolver convergence and carrier migration succeed. A qualified canonicalinstance_mergeddeployment, including the checked loc instance, is not subject to that winner-swap freeze. - Even on a qualified instance, do not publish Tool Access or remove a tool bypass until Phase 1B carrier ownership and exact-rule validation succeed.
- Configure policies for the demonstration tools.
- Confirm the effective snapshot contains their endpoint keys.
- Remove their
skipPathPrefixesbypasses. - Remove the gateway-local
access-control.ymloverride when all remaining settings are config-server-owned. The gateway loads that file as the complete access-control config and consultsvalues.ymlonly when no local file is found; the two sources are not merged. - Enable permission-based
tools/listfiltering where discovery must match call permissions.
Validation Plan
Persistence and Replay
- API endpoint and tool access targets are host-isolated.
- Duplicate active endpoint keys and runtime pattern shadows are rejected per gateway instance.
- Principal and rule mutations enforce aggregate versions.
- Replay is idempotent and preserves source ownership.
- Tool retirement removes only tool-owned contributions.
- A shared rule body survives until its last reference is retired; conflicting content for one rule ID is rejected.
Publication
- Tool access preview shows the exact endpoint key, public name,
authorizationToolName,endpointName, compiled policy, effective global settings, and shadow analysis. - Unrelated API and tool endpoint rules survive republish.
- Missing exact
req-acc, stale tool versions, ownership conflicts, shadow matches, and skip-prefix matches block a protected publication. - Deterministic input produces a deterministic snapshot digest.
- Two contributors claiming one endpoint key produce
CONFLICTbefore any database write, regardless of whether their compiled rule values are equal. - Repeated snapshot generation of a deliberately colliding resolver fixture is byte-stable under the database total order, while publication of that fixture remains forbidden.
- Concurrent publication rejects a review whose base property value, digest, or aggregate version is stale.
- Carrier write and legacy-row deactivation share one publication transaction; verification uses only a snapshot generated afterward.
- Readiness rejects any active instance-API or instance-app-API row for either property on the target instance.
- Every supported resolver ignores inactive association and property rows and produces the same instance-pool merge.
- Snapshot tests cover active/inactive behavior and priority fallthrough at every override level, including empty map and list values.
- Resolver parity tests require identical canonical JSON and digest; the Java
MAXimplementation cannot remain as an alternate result. - Both properties resolve to active
mapregistrations for the selected host; changing a registration blocks publication until reviewed. - After migration, both effective properties have the canonical
instance_mergedsource level with only the instance carrier contributing, and a later API publication cannot recreate a per-API carrier.
Portal UI
- Standalone tools expose Access Control without requiring
endpointId. - Workflow Access and caller Access Control are clearly distinguished.
- Existing API Endpoint Access Overview behavior is unchanged.
- Readiness states explain the exact missing or stale prerequisite.
- Preview and publish actions show the selected instance and environment.
Gateway
- An authorized caller can list and call the tool.
- An unauthorized caller cannot call the tool.
- Permission-mode
tools/listhides unauthorized tools. - The canonical public rule makes a public tool visible and callable for every caller that reaches MCP routing, while route-level authentication remains authoritative.
- A missing, modified, or mixed public rule fails publication; a missing runtime rule body hides the tool and denies the call.
- CEL-mode
tools/listhides entries beyondmaxCelEvaluations. - A direct
tools/callremains denied even if list visibility is stale. - Row and column filters apply to top-level and configured nested workflow-backed structured content, and missing or mistyped nested targets fail closed.
- Every response-filter failure returns an MCP error without exposing the unfiltered result.
- Missing endpoint rules deny when
defaultDenyis true. - No skip prefix matches a managed endpoint key, public tool name, or
authorizationToolName. - An unqualified multi-API resolver cannot transition from disabled to protected, or accept further protected API publication, until resolver parity passes. Tool Access additionally requires the carrier and exact-rule gates.
Demonstration Acceptance
For customer_360@call and workflow_mcp_smoke@call:
- the config snapshot contains request rules and permissions for both keys;
- no
skipPathPrefixesvalue is a prefix of either endpoint key, public tool name, orauthorizationToolName; - an allowed principal receives a successful result;
- a denied principal receives an access-control error before workflow start;
tools/listbehavior matches the configured visibility mode;- restart and snapshot regeneration preserve the same behavior without local
mcp-router.ymloraccess-control.ymloverrides.
Alternatives Considered
Keep Gateway-Local access-control.yml
Rejected. It overrides config-server ownership, is deployment-specific, and cannot provide Portal audit, preview, lifecycle, or replay behavior.
Keep skipPathPrefixes
Rejected except as a temporary development bypass. It disables both request authorization and response filtering.
Require Users to Create APIs Manually
Useful as a short-term bridge, but rejected as the final user experience. It creates duplicate lifecycle ownership and makes a standalone tool appear to be an API solely to reach permission screens.
Add a Tool-Specific Gateway Policy File
Rejected. The gateway already has the necessary endpoint-keyed rule format. A second format would create precedence, reload, audit, and migration problems.
Reuse Workflow Tool Grants
Rejected. Workflow grants authorize definition dependencies and pin versions, digests, capabilities, and environments. Caller permissions authorize users and agents invoking an exposed tool. Combining them would weaken both models.
Resolved Design Questions
Access-Target Storage Migration
Do not replace the endpoint-specific tables in the first release. Introduce generic access-target storage and projection adapters, migrate readers and writers incrementally, and retire the old tables only after API Endpoint Admin and Tool Admin both use the generic contracts.
The eventual replacement surface is the 16 tables whose authorization data is
keyed directly by endpoint_id:
api_endpoint_rule_t;role_permission_t,group_permission_t,position_permission_t,attribute_permission_t, anduser_permission_t;- the role, group, position, attribute, and user variants of
*_row_filter_t; and - the role, group, position, attribute, and user variants of
*_col_filter_t.
The generic model can collapse those into access_target_rule_t, a
principal-typed access_target_permission_t, access_target_row_filter_t, and
access_target_col_filter_t, all referencing access_target_t. It must retain
attribute values and the optional user validity interval. api_endpoint_t
remains the API catalog identity and api_endpoint_scope_t remains the OAuth
scope projection; neither is replaced by this migration.
This is a high-impact migration because the 16 tables participate in command, query, event-replay, snapshot/export, cascade-lifecycle, and Portal UI paths. Projection adapters keep the tool feature from requiring a flag-day conversion and provide a period in which generic and legacy query results can be compared.
Publication Command
Tool and policy publication use one command and one accepted source revision.
The preview presents Access Control as a separate, explicitly approved section
and compares the complete proposed carrier values with the current
instance_property_t values. The operator reviews the endpoint key,
public/protected mode, principals, filters, removals, retained unrelated entries,
and every overwrite before accepting the desired-state update. Snapshot creation
and current-snapshot promotion remain explicit subsequent gates in the same
workflow.
Review Freshness and Promotion
There is no separate field-by-field “policy publication invalidation” action. The authoritative review artifact is the comparison between the existing and proposed complete property values, plus their canonical digest and expected aggregate versions. Any desired access-relevant change appears in that diff. A display-name or description-only edit that does not alter the compiled values does not create a policy change.
If either carrier value or its aggregate version changes after the review is
rendered, applying that review is rejected as stale and the user must review a
fresh comparison. Once desired state is written, the current snapshot continues
to govern runtime behavior until a reviewed candidate snapshot is explicitly
made current. The STALE readiness state means desired/current snapshot drift,
not automatic revocation of the active policy. Retirement and emergency
disablement remain explicit, audited desired-state changes followed by snapshot
promotion.
Public Tools
Support an explicit PUBLIC access mode in Portal, compiled as a canonical
shared allow-all request rule rather than a new endpoint marker:
rule.endpointRules:
public_tool@call:
req-acc:
- allow-public-access
rule.ruleBodies:
allow-public-access:
common: Y
ruleId: allow-public-access
ruleName: Allow public access
ruleType: req-acc
accessControlEffect: public
conditionLanguage: cel
conditionSecurityProfile: strict
expression: "true"
The compiler owns the rule ID and exact body digest; users cannot edit or
substitute it. A missing or modified body fails publication and runtime access
closed. Permission-mode tools/list needs a small gateway change to recognize
only the canonical rule ID and complete rule shape as visible. It must not infer
public access from arbitrary constant expressions, rule names, or an
accessControlEffect value alone. A public endpoint cannot combine this rule
with principal-specific request rules or permission metadata. The normal call
path still executes the rule, so request auditing and configured response
filters remain active.
Public means no principal-specific per-tool restriction; it does not bypass
authentication enforced before MCP routing. If the MCP route permits anonymous
access, the tool is callable anonymously. Public access must not use
skipPathPrefixes, an empty permission block, or an independently broad
visibility block. Publication requires explicit public-access approval and
records the approver and reason. Moving between PUBLIC and PROTECTED changes
the compared property values and therefore requires a new review and snapshot
promotion.
Permission-Mode Tool Listing
Permission mode is a discovery filter. For each tool, tools/list looks up its
endpoint rule and compares the rule’s permission metadata with the caller’s
normalized role, group, position, attribute, and user claims. It does not run
arbitrary CEL or argument-dependent rules. The subsequent tools/call remains
independently authorized, so list visibility is not an authorization grant.
Use permission mode by default for newly protected instances after the gateway
uses authorizationToolName consistently for list and call and recognizes the
canonical public rule. Keep unknown rules hidden. Existing instances migrate by
previewed opt-in because changing from none can remove tools from clients’
discovery results. CEL mode remains an explicit option for deployments that
accept its evaluation cost and argument-insensitive list semantics.
Nested structuredContent Filtering
The gateway already filters a top-level object, an array of objects, and an
object containing an items array. It does not generically address an array or
object deeper in a composed workflow result. Add a restricted, schema-checked
response target path rather than recursively filtering every object with a
matching field name.
The first nested-filter version should:
- use a bounded path grammar, such as JSON Pointer plus one explicit array-item selector, rather than unrestricted JSONPath;
- validate the path against the published output schema and show the selected object or array in Portal preview;
- apply row filters to the selected array and column filters to its object elements, or apply column filters to one selected object;
- fail closed on a missing path, wrong node type, traversal-limit breach, or schema mismatch;
- bound path depth, selected node count, and filtered response size; and
- continue regenerating textual MCP content from the filtered
structuredContent, as the current gateway does.
The JSON traversal itself is modest. The work is medium-sized because it spans gateway filter semantics and tests, output-schema validation, generic filter storage, Portal editing and preview, publication digests, and migration of existing filters. A fixed pointer to one nested object or array is a reasonable first increment; full recursive or general JSONPath filtering should remain out of scope until its ambiguity and resource limits have a separate design.
Recommended Decisions
- Adopt a first-class Access Target abstraction in Portal.
- Add Access Control directly to Tool Admin.
- Keep Workflow Access separate.
- Treat the compiled tool endpoint as the policy-key authority.
- Require an explicit endpoint for managed tools; permit an operator-confirmed
pin of a non-empty compiled path-derived key, but never auto-pin
@call. - Reuse the
rule.endpointRulesandrule.ruleBodiesproperty format. Compile public access as one canonical shared allow-all request rule rather than introducing an endpoint marker, a second policy file, or a skip prefix. - Store their canonical merged values in one
instance_property_tcarrier per gateway instance and track source ownership in Portal publication records. - Require active
mapregistrations and atomically retire all higher-priority per-API carriers when the instance carrier is first written. - Converge all resolvers on the deployed
create_snapshotactive-row and instance-pool semantics before exposing the carrier-migration command; never substitute physical deletion. - Permanently reject active legacy rule carriers during readiness.
- Define source policy by host and access target, but compile, publish, and assess readiness per gateway instance. Environment is selection metadata, not another merge layer; any future override must be explicit.
- Treat exact endpoint ownership as required readiness and gateway pattern fallback as legacy compatibility only.
- Derive list visibility from permission until Portal can enforce a formal subset invariant.
- Include effective instance-global access settings and runtime shadow analysis in preview and digest.
- Make Portal’s canonical ownership merge the digest authority; treat database ordering only as deterministic defense in depth and reject every duplicate endpoint owner before persistence.
- Keep
defaultDeny: trueand prohibit generated tool bypasses. - Freeze protected publication only on deployments that cannot prove canonical resolver semantics; require carrier migration and exact-rule readiness before publishing Tool Access or removing its bypasses.
- Use a manually managed internal API only on an isolated transition instance with no other rule-property contributor.
- Remove local access-control overrides after Portal-published policies are verified in the effective snapshot.