How a Service Becomes Available to an Agent
Your app publishes a service. Discovery indexes it. An agent asks for it — and gets an error about a permission nobody has heard of. Nothing is broken: three of the four decisions that make a service usable have not been taken yet, and each belongs to someone else.
The named-services pieces so far described how to build a realm and how an agent works it. This one is about the space between: a provider that runs, and a tool an agent may actually call. Four decisions stand in that space, taken by four different people at four different times, and each one fails in its own way when it is missing.
01The provider app publishes
An app publishes the complete registry it currently owns; discovery indexes it for cross-process lookup. That is the decision an engineer takes in code, and it is the only one of the four that lives inside the providing app.
It is worth being precise about what it buys, because the intuition is usually wrong: discovery makes a service findable, not usable. A namespace in the registry can be resolved, routed to, and described. Nobody can be asked to approve access to it yet.
02The deployment decides what may be asked for
This is the one that surprises people, and it is the point of this article.
Connection Hub owns connections, grants, and account registries for an installation. Its delegable catalog — connections.delegated_credentials.oauth.resources, in the Connection Hub app's own props — lists which endpoints may be requested here, tool by tool:
resources:
- resource: '*/api/integrations/bundles/*/*/records@1-0/mcp/records*'
label: Records service
tools:
records_search: { label: Search records, grants: [records:read] }
records_upsert: { label: Edit records, grants: [records:write] }
And a claim is delegable only when the same block also says who may delegate it:
capabilities:
- grant: records:write
label: Edit records
delegable_roles: [kdcube:role:super-admin, kdcube:role:admin]
delegable_permissions: [records:write]
Both halves are required, and half-doing it is the common failure: a resource entry naming a claim no capabilities row declares renders an access card that can never be approved. The hub reports that row at configuration time — resource, tool, claim — rather than leaving it for a user to discover by pressing Grant access.
The rule underneath is a subset relation:
what the deployment's apps expose (provided)
⊇
what Connection Hub lists as delegable (allowed here)
⊇
what a user has granted a given agent (granted)
⊇
what this conversation's pick left (in play this turn)
Each layer can only narrow. A claim no app serves cannot be conjured by listing it; a user cannot approve beyond the catalog; a conversation cannot re-enable what the administrator never declared.
An installation may run a thousand services. If installing one also made it askable, adding an app would silently widen what its users can be asked to approve. Installing and allowing stay two decisions, and the second belongs to whoever runs the deployment.
Name claims for the app and the consequence
A claim string is the text a human reads above a checkbox, so it carries two obligations.
Name the app, not the endpoint. A claim called docs:read reads like a genre. The day a second documentation service arrives, both render the same checkbox and only the resource line tells them apart. docs.handbook:read stays legible next to docs.releases:read.
Name the consequence, not the verb. A content-store app we built shipped a single docs:read claim over a tool set that included save_file, delete_comment and commit_entry. The label said read while approving writes, deletions and a push to a shared repository — the one thing a consent card must never do. Claims group by what a grant lets the agent do:
| claim | covers |
|---|---|
docs.handbook:read | search, get, related, list, about, fetch |
docs.handbook:write | edit an entry file, add a file, comment |
docs.handbook:delete | remove files and comments |
docs.handbook:commit | commit and push to the repository |
One claim per tool is the other tempting option. It is more granular and worse: twelve near-identical checkboxes, when the tool rows underneath already show that detail. The claim is the axis a person revokes on, so it should be the axis they think in.
03The user grants
With the catalog in place, an agent that lacks access gets a structured consent demand at the moment it needs the capability, not a sweep at the start of the turn. The demand names the resource, the claims, and an absolute Connection Hub route; the user approves under Delegated by KDCube, per agent.
04The app admin declares, the user narrows
An agent reaches KDCube services through a door and a list, declared together:
tools: - name: named_services # the DOOR — transport + consent grant kind: mcp server_id: named_services alias: named_services url: ".../kdcube-services@1-0/public/mcp/named_services" delegated: true scopes: [named_services:use] - name: named_services_inventory # the CEILING — namespaces and operations kind: named_service alias: named_services # the same alias ties it to the door namespaces: records: { allowed: [provider.about, object.search, object.get] }
The second item binds no tools of its own. It is the service inventory: the administrator's ceiling of namespaces and, per namespace, the operations this agent may run. It is also exactly what the capability picker renders, so the person chatting narrows their own conversation by unchecking rows — and can only subtract.
For an agent the platform hosts but did not author — a LangGraph graph, a Claude Code session, any runtime that runs to completion behind one turn — the narrowing lands at the binding step, and the ordering matters: a server the user switched off is dropped before it is dialled. No grant token is read for it, so it cannot even raise a consent card for something already declined.
What the pick enforces, and what it states
The door publishes generic tools — named_services_search, named_services_get, … — and takes the namespace as an argument. That splits enforcement, and the honest version is worth stating exactly:
enforcedBy tool name. A surviving operation maps to its door tool; a denied one loses it. The generic named_services_call, which takes its operation as an argument and would therefore reach what the pick removed, is withheld as soon as anything is removed at all. Deny every namespace and the door itself is not bound.unionAcross namespaces. Door tools are granted as the union, so an operation loses its tool only when it is denied in every surviving namespace. Deny object.get in one namespace while another still allows it, and the tool stays — the second namespace needs it.statedNot enforced: the namespace argument. No tool name distinguishes one namespace from another, so the surviving inventory is also written for the agent as a short block naming its namespaces and their operations, rendered identically for every hosted runtime, and the door's own gate enforces the namespace because it reads the call.That last line is the seam where a per-namespace operation denial is currently instructional rather than mechanical. Two designs close it — an opaque per-turn ticket the runtime cannot author, or enforcing the user's standing selection at the door — and the choice depends on whether a capability pick is a standing preference or a per-conversation boundary.
05Reading an error as a missing decision
Each gate fails in its own vocabulary, which makes the error a diagnosis:
| what you see | the decision that has not been taken |
|---|---|
| the namespace is not found at all | (1) the provider app does not publish it here |
delegated_access_unknown_resources | (2) the endpoint is not in this deployment's catalog |
delegated_access_grants_not_delegable | (2) the claim is outside the endpoint's tools, or no capabilities row declares it |
| a consent demand naming resource and claims | (3) the user has not granted this agent |
| the agent never mentions the namespace | (4) it is not in that agent's inventory, or the user unchecked it |
The pattern is worth internalizing beyond named services: when a capability is missing, ask whose decision is missing, not which flag is off.
06The shape to keep
A service becomes available through four decisions with four owners — the engineer who publishes, the deployment that allows, the user who grants, the administrator who declares for an agent — and one direction of travel: each layer narrows the one above it and none can widen it. That is what lets an installation run a large catalog of services and still answer, precisely, why a particular agent may do a particular thing.