KDCube
← Engineering
KDCube Engineering · Deep Dive

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.

2026-08-14Engineering11 minExperience
named services connection hub delegated access agent capabilities delegable catalog service inventory consent card mcp door

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.

FOUR DECISIONS, FOUR OWNERS a provider that runs a tool the agent may call GATE 1 published registry + discovery owner: the engineer GATE 2 delegable catalog: resource + tool→claim, capability owner: the deployment GATE 3 granted per agent, revocable owner: the user GATE 4 declared inventory + pick owner: admin, then user WHEN THE GATE IS CLOSED, THIS IS WHAT THE CALLER HEARS 1 · the namespace is not found at all delegated_access_unknown_resources delegated_access_grants_not_delegable 3 · a consent demand naming resource + claims 4 · the agent never mentions the namespace ONE DIRECTION OF TRAVEL — EACH LAYER MAY ONLY NARROW provided ⊇ allowed here ⊇ granted to this agent ⊇ left by this conversation's pick
Findable, grantable, granted, and declared for this agent — four separate decisions, four separate owners.

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 catalogconnections.delegated_credentials.oauth.resources, in the Connection Hub app's own props — lists which endpoints may be requested here, tool by tool:

connection-hub propsYAML
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:

connection-hub propsYAML
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:

the narrowingTEXT
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.

REGISTRATION IS DELIBERATELY MANUAL

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:

claimcovers
docs.handbook:readsearch, get, related, list, about, fetch
docs.handbook:writeedit an entry file, add a file, comment
docs.handbook:deleteremove files and comments
docs.handbook:commitcommit 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.

THE DOOR AND THE INVENTORY surfaces.as_consumer.agents.<id>.tools — TWO ITEMS THAT TRAVEL TOGETHER THE DOOR transport + the consent grant kind: mcp server_id: named_services delegated: true scopes: [ … ] publishes GENERIC tools alias the tie THE INVENTORY the administrator's ceiling kind: named_service alias: named_services namespaces: records: [ operations ] binds NO tools of its own THE PICKER RENDERS THE INVENTORY — THE USER MAY ONLY SUBTRACT Services ☑ records — provider.about · object.search · object.get ☑ tasks — object.list · object.upsert ☐ archive — (unchecked by this user) what comes back, and how it is keyed disabled.named_services → by NAMESPACE disabled.mcp → by SERVER_ID, never the alias a denied server is dropped BEFORE the dial
The door is how the agent reaches the services; the inventory is which of them it may use.

04The app admin declares, the user narrows

An agent reaches KDCube services through a door and a list, declared together:

bundles.yamlYAML
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 seethe 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.

Read on

Documentation

KDCube Engineering
2026-08-14 · Experience E1