Delegating A KDCube Service To An External App
You have an external app and you want it to reach one KDCube service. Connecting it issues a delegated credential — scoped to that resource, limited to the tools and grants you approved, recorded as a durable consent edge that keeps the app as its own actor and you as the grantor. This Deep piece walks the connect → consent → delegated-credential flow, the two levels of least privilege, identity scope, and what developers declare. Not a session, not a shared secret.
You have Claude. You want it to reach into your KDCube memories — search them, read one back — from inside Claude's own connector list. What should actually happen when you wire that up?
The wrong answers are easy to name. KDCube should not hand Claude your browser session. It should not mint a single shared token that any app could replay. And it should not give Claude the run of every KDCube tool just because you wanted it to read two things. The right answer is a delegated credential: a credential issued to Claude, scoped to one KDCube service, carrying only the tools and grants you approved, with a durable record that points back to you as the person who consented.
This is the connect → consent → delegated-credential story. It builds directly on the identity model from Connected Identities Are Not One User Id: the external app is its own actor, you remain the grantor, and the link between them is a connection edge with selected grants. If those words are unfamiliar, read that piece first — this one assumes them.
What you want:
Claude can search and read my KDCube memories.
What you do not want:
Claude becomes my browser session.
Claude can call every KDCube tool.
Claude can spend budget with no trace of who caused it.
Claude can reach identities I did not consent to.
Connection Hub makes each of those distinctions concrete.
What "connect" actually creates
When the connection is approved, Connection Hub does not just say "OAuth succeeded." It writes a delegation edge — the same connection-edge primitive from the identity model, with a representative relationship and stricter grants. The external app gets its own identity; you stay the grantor.
delegate identity (the actor)
authority_id = delegated_client
subject = integration:claude:<grantor-id>
grantor identity (you)
authority_id = platform
subject = 02e53484-...
delegation edge — what the delegate may do
resource = one exact KDCube service URL
tools = the concrete tools you selected
grants = the grants you approved (e.g. memories:read)
identity_scope = grantor | grantor_identity_family | selected_identities
A channel link ("Telegram and platform are the same person") and a delegation edge ("this external app may act for me, only within these constraints") are the same graph object with different scope — the same connection-edge primitive with a different relationship scope and stricter grants.
Because the edge is durable and server-side, it is the record the runtime consults on every call:
Is this credential valid?
Was it issued for this exact resource?
Was this tool selected at consent time?
Does this tool have the grants it requires?
Which identity scope may the product read?
Who is charged if the operation costs money?
The delegated client is a first-class actor with a narrow, recorded permission — not an impersonated platform session.
The delegation lifecycle
Consent happens once, in the browser. Everything after runs on the scoped credential.
The user journey
The flow should feel concrete. You are not approving a vague "access to KDCube." You are connecting one external app to one service, and choosing what it may do.
1. Open the external app's connector settings.
Example: Claude -> Connectors.
2. Add a KDCube service URL.
.../user-memories@2026-06-26/public/mcp/memories
3. The app probes the service.
KDCube replies: this resource requires a delegated credential.
4. The app sends you to Connection Hub to authorize.
You sign in to KDCube if you are not already.
5. Connection Hub shows a consent screen for this exact resource:
Client: the requesting app + where the code will be sent
Scope: the grants requested
Capabilities: per-tool checkboxes (Search memories / Read one memory)
6. You approve (selecting which tools to allow).
7. Connection Hub issues a delegated credential to the external app.
8. The app calls the service with:
Authorization: Bearer <delegated credential>
9. The managed surface guard enforces the recorded resource/tools/grants/scope.
The product rule that matters: consent is service-shaped and tool-shaped. You see the service and the specific tools you are connecting, not OAuth jargon. One nuance worth stating plainly — KDCube here acts as a self-hosted authorization server, and it will only let you delegate grants your own KDCube account is allowed to delegate. Consent cannot widen your own authority; it can only narrow it for the app.
What consent authorizes
Consent is not one checkbox. The screen separates two different things, because they answer two different questions, and a third appears only for generic bridges.
| On the consent screen | Authorizes | Notes |
|---|---|---|
| Selected tools | what the app may call | a service may expose ten tools; you might select two — each unchecked tool fails closed |
| Selected grants (platform delegation edge) |
what the app may derive through your identity | shown as an explicit "platform account delegation edge"; edge grants can never exceed what your identity already holds |
| Named-service namespace rows |
concrete namespace operations the grants cover | generic-bridge case only; the dedicated memories service does not need it |
Tools and grants are deliberately separate levels of least privilege:
grant approved: memories:read
tools available: memory_search, memory_get
you select: memory_search only
result:
memory_search -> succeeds
memory_get -> fails closed (tool was not selected)
So even within one approved grant, the credential is narrowed to the exact tools you ticked. Tools declare the full grant set they require; if a tool needs two grants, it lists both rather than appearing twice. The guard's check at call time is simply: was this tool selected, and does the credential carry every grant this tool requires?
Identity scope is a separate choice
"Search my memories" is ambiguous. Just the memories created under your platform account? Or also the ones you created from a connected channel — your phone, a linked provider — that the identity model already treats as the same person?
That is an identity-scope decision, and it is explicit:
| Scope | Meaning |
|---|---|
grantor |
the delegate acts only on your primary platform user id |
grantor_identity_family |
the delegate may also reach the runtime identities linked to you, when the connection edges allow identity-family reads |
selected_identities |
reserved for a future UI where you pick individual linked identities |
The default is grantor. A resource must opt in to family scope, and even then the app never
invents user ids — it asks Connection Hub to resolve the allowed set server-side:
Delegated app calls memory_search
|
v
Managed surface guard validates credential / tool / grants
|
v
Memory service asks Connection Hub: delegated_identity_scope_resolve
|
v
Connection Hub returns the allowed memory_user_ids
|
v
Memory store query uses exactly those ids
This is why identity-family support lives in the backend product service, never in the external app. The app supplies a credential and tool arguments; the family is resolved behind the guard. (How that resolution works is the identity model's job — see Connected Identities Are Not One User Id.)
A delegated credential is not a browser session — or a shared secret
Two contrasts make the credential's role clear.
It is not a browser session. The browser session exists only to prove who you are and show consent. After that, the app holds a delegated credential, which carries far less:
Browser session
purpose: prove the user and render consent
authority: platform
lifecycle: browser login / session
Delegated credential
purpose: let one external app call one protected resource
authority: delegated_client
lifecycle: issued, short-lived, refreshable, revocable, resource-scoped
It is also not a shared secret. The access token is a presented handle, not a document the app can decode for your authority — product code must never read grantor facts out of the token body. The grantor, grants, tools, identity scope, and economics facts live in a server-side record keyed to the credential. (The token-format details, and the full shared-secret contrast, are Authenticated MCP In KDCube: Delegated Credentials, Not Shared Secrets — this piece only needs that the credential is scoped and recorded, not secret-shaped.)
Because the record holds the delegation edge and an economics projection, the runtime can answer both accounting questions at once:
actor (who caused it): integration:claude:<grantor-id>
economics subject (who pays): platform:02e53484-...
provenance: Claude acted on behalf of this grantor
The grantor is charged; the delegate is recorded as the actor. Nothing about "who did this" is lost.
The current built-in example
The shipped example is kdcube-services@1-0, which exposes KDCube-owned capabilities as managed MCP
resources, alongside the standalone user-memories@2026-06-26 memories service used throughout this
piece.
user-memories@2026-06-26/public/mcp/memories
memory_search
memory_get
kdcube-services@1-0/public/mcp/conversations
conversations_export
kdcube-services@1-0/public/mcp/named_services (generic bridge)
named_services_list
named_services_search / get / upsert / action / ...
The dedicated memories service has two named tools. The named_services surface is the generic-bridge
case from earlier: the app calls named_services_list first to learn which namespaces exist
(mem, task, cnv, …), then inspects each before calling it — and the consent
screen's namespace rows describe exactly that nested boundary.
The connector's appearance — the KDCube icon, tool titles, read-only vs. write/action grouping — is advertised by the MCP server so clients like Claude can render it nicely. But that presentation is metadata, not authority: enforcement always comes from the stored delegated-credential record, not from anything the client displays.
What a good consent screen says
Concrete enough to decide on. This mirrors what the real screen renders — client, redirect target, scope, the platform delegation-edge grants, and per-capability tool checkboxes:
Authorize an MCP connection to KDCube
Client: claude (pre-registered)
Sends code to: https://claude.ai/api/mcp/auth_callback
Platform account delegation edge:
[x] Read memories memories:read
Select which capabilities to authorize:
[x] memory_search Search memories Requires: memories:read
[x] memory_get Read one memory Requires: memories:read
[ Approve ] [ Deny ]
The screen warns the operator to approve only a connection they started and a redirect URL they recognize, and flags any client that is not pre-registered. The anti-pattern to avoid is a bare "Approve OAuth access" — it tells the person nothing. Show the service, the tools, and the identity scope.
What developers configure
Delegation is descriptor-driven on two sides. (The guard that enforces these descriptors at request time is its own subject — Protecting KDCube Surfaces With Managed Credentials. Here we only show what gets declared.)
The provider bundle marks its MCP surface as managed and maps each tool to the grants it requires:
surfaces:
as_provider:
mcp:
memories:
auth:
mode: "managed"
authority_id: "delegated_client"
tools:
memory_search:
grants: ["memories:read"]
memory_get:
grants: ["memories:read"]
selected_tool_grants: true
Connection Hub declares which grants are delegable and how they map to concrete resources — including the identity scope a resource opts into:
connections:
delegated_credentials:
oauth:
enabled: true
capabilities:
- grant: "memories:read"
label: "Read memories"
delegable_roles:
- "kdcube:role:chat-user"
- "kdcube:role:paid"
- "kdcube:role:privileged"
- "kdcube:role:super-admin"
resources:
- resource: "*/api/integrations/bundles/*/*/user-memories@2026-06-26/public/mcp/memories*"
label: "User memories MCP"
identity_scope: "grantor_identity_family"
tools:
memory_search:
label: "Search memories"
grants: ["memories:read"]
memory_get:
label: "Read one memory"
grants: ["memories:read"]
The capability row says who may delegate a grant (the delegable_roles). The
resource row says which tools exist on the concrete service, what each requires, and whether
family scope is allowed. Both blocks above are the real shapes from the example
user-memories@2026-06-26 and connection-hub@1-0 descriptors.
Related publications
Documentation on GitHub
The live docs behind this entry:
- Recipe — delegate a KDCube service to an external app
- Recipe — protect a bundle MCP with managed credentials
- Recipe — use connected identities in a feature
- Delegation edges
- Delegated-credential protocol adapters
- OAuth delegated-credential protocol adapter
- Consent screen branding
- Identity family resolver
- Bundle platform integration
- Bundles descriptor