KDCube Application Integrations: Provide, Consume, and Guard Every Surface
An integration has a direction, identity, authentication owner, capability boundary, delivery contract, and product owner.
An integration is not just an endpoint. It has a direction, an identity, an authentication owner, a capability boundary, a delivery contract, and a product owner. KDCube apps declare what they provide and what they consume so an API, MCP server, agent tool, named service, widget, event lane, or Telegram channel does not acquire authority merely because it is reachable.
AI app integrations often begin as a list of URLs and tokens. That works until one app has several agents, one user connects several accounts, an external automation needs bounded access, or the same domain operation must work locally, through API, and through MCP.
KDCube treats the app as the integration boundary. Provider surfaces describe what the app offers. Consumer surfaces describe what the app and each of its agents may reach. The descriptor carries policy and product intent; code carries the implementation. Connection Hub handles linked identities, connected accounts, and delegated clients without giving raw provider credentials to an agent.
This is the deep integration map. The first-app and package story remains in Build an AI App with KDCube.
KDCube uses app in builder-facing prose. Literal contracts still use bundle in bundle_id, bundles.yaml, /bundles/ routes, and SDK decorator names. They identify the same deployable unit.
00Implementation highlights
| Concept | The takeaway |
|---|---|
| Provider and consumer are separate declarations | Registering a connection does not expose it to every agent, and implementing a surface does not authorize every caller. |
| Transport and authentication are separate | A public route may still verify webhook proof or use managed MCP authorization; a reachable URL is not an authority decision. |
| MCP does not require named services | Any app can expose ordinary FastMCP tools or consume any supported MCP server directly. |
| Named services add domain semantics | Use them when several apps, agents, or transports should share one typed realm grammar and owner. |
| Identity is selected per connection | A consumed call can act as the app, as a user under one agent's delegated grant, or through the user's connected external account. |
| Telegram is queued ingress | The public webhook verifies proof, resolves the linked identity, submits a conversation event, acknowledges early, and leaves turn execution to proc. |
01Two directions, one app boundary
An app can provide capabilities, consume capabilities, or do both.
KDCube app
callers / proc ---> AS PROVIDER
surfaces this app implements
API, MCP, UI, handlers, jobs, public content,
named-service realms
AS CONSUMER ---> services / providers / other apps
connections this app may open
MCP, tools, named services, models, scene surfaces,
connected accounts
Provider/consumer describes capability ownership, not simply arrow direction on the wire. A @data_bus_handler receives data, but it is a provider declaration because the app offers that handler to proc. An agent turn enters through @on_reactive_event, while the agent then consumes tools and services from its own inventory.
The two descriptor families keep those decisions visible:
surfaces: as_provider: # What this app offers and the policy on each offered surface. as_consumer: # What this app and each of its agents may reach.
Code decorators say a surface exists. The descriptor says what that surface means in this deployment: enabled state, visibility, authentication owner, default-chat intent, connection details, per-agent inventory, or scene composition.
02What an app can provide
An app exposes only the surfaces it owns.
| Provider surface | Code declaration | Who normally calls it |
|---|---|---|
| Reactive conversation turn | @on_reactive_event on the entrypoint | chat ingress and proc's ordered conversation scheduler |
| Authenticated or public REST operation | @api(route="operations") or @api(route="public") | KDCube UI/internal caller, webhook, partner system |
| MCP server | @mcp(...) returning a stateless FastMCP app | hosted or external MCP clients |
| Widget or main view | @ui_widget, @ui_main | KDCube scene/host/browser |
| Scheduled/background work | @cron, @on_job | proc scheduler and job workers |
| Durable app-domain message handler | @data_bus_handler | widget, UI, app, or service through Data Bus |
| Discoverable public content | @public_content | crawler, catalog reader, public site |
| Named-service realm | explicit provider contribution | agents, apps, widgets, tools through local/API/MCP/Data Bus adapters |
The decorators are not a checklist. A backend API app does not need chat. An MCP provider does not need a named-service realm. A site does not need a scene. A named-service provider can offer no public URL at all when its callers are runtime-local.
Provider ownership must be explicit. A reusable base class that contains memory-capable code does not make every derived app a memory provider. The owner app contributes the provider to its registry; discovery publishes the complete current registry and withdraws providers no longer owned.
03Every inbound surface names its trust boundary
The proc router delivers all app surfaces, but authentication ownership varies.
| Surface | Normal trust owner | Important limit |
|---|---|---|
@api(route="operations") | KDCube session/auth stack plus declared user/role checks | app still owns record-level product authorization |
@api(route="public") | app/SDK proof verifier or configured managed guard | "public route" means no normal browser login gate, not "trust every caller" |
| Widget/main view | KDCube session and widget/runtime config handshake | browser receives presentation/runtime config, not app secrets |
| Public content | intentionally anonymous published item state | inputs, catalogs, rate, and publish/retract lifecycle remain bounded |
| MCP with no auth mode | intentionally public | suitable only for genuinely public tools |
MCP auth.mode: bundle | app-owned credential verifier | app must implement the proof and authorization policy |
MCP auth.mode: managed | proc plus Connection Hub | delegated bearer, resource, grants, selected tools, expiry, and revocation checked before app code |
Transport is not policy. A route="public" MCP URL is often necessary for MCP discovery and OAuth challenge, while auth.mode: managed still rejects an unauthorized tool call before the FastMCP app runs.
Likewise, a Telegram webhook is a public API route because Telegram cannot present the user's browser session. It is still authenticated by the configured Telegram proof and linked-identity flow.
For managed MCP, the boundary journey is:
external request -> verify delegated bearer -> load server-side grant -> match concrete resource -> check requested tool and required grants -> check selected/consented operations -> project delegate actor + approving user into request context -> invoke app MCP tool -> app performs record-level authorization
The bearer is a handle. The server-side grant remains the authority.
04Consuming MCP has a registry and an inventory
An app that consumes MCP makes two declarations:
surfaces.as_consumer.mcp.services where the servers are and how a client may authenticate surfaces.as_consumer.agents.<agent_id>.tools which server and tools this agent may see and call
Those declarations answer different questions:
connection registry: "where is it and how can the app reach it?" agent inventory: "may this agent know and call it, as whom?"
One MCP server can serve several agents with different allow-lists. Registering the server does not expose it to any agent automatically. The effective tool catalog is the administrator's ceiling intersected with the user's conversation selection.
A consumed tool call can run under three useful identity paths:
| Mode | Configuration | Call identity |
|---|---|---|
| App credential | bearer/API-key/header secret reference | the app itself |
| Delegated user per agent | delegated: true, resource id, requested KDCube claims | signed-in user under this specific hosted agent's resource-scoped grant |
| Connected account | provider claims checked by the tool/provider adapter | user's external Gmail, Slack, or other account |
The first two bind the service/client call. A connected account is a tool-level provider requirement behind that call; it does not replace the MCP transport's own authentication or managed guard.
Static credentials belong in app secrets, never in the MCP URL, tool arguments, model prompt, or generated code.
For delegated mode, the hosted agent is its own delegated client:
kdcube-agent:<app-id>:<agent-id>
Each delegated connection asks for claims under its configured KDCube resource. Consent stores that decision in resource_grants; repeated consent for the same deterministic agent and resource can merge further claims into that grant. Other resources and sibling agents gain nothing implicitly. The runtime reuses the bound token for subsequent turns. After revocation, the next bearer resolution no longer binds the connection and a direct guarded call fails.
Consent is demand-driven. KDCube does not sweep every configured tool at the start of a turn. When the agent first attempts a tool whose delegated grant is missing, the runtime can emit an actionable consent demand with the Connection Hub approval link instead of pretending the tool does not exist. After the user approves, a later attempt resolves the bound grant and token.
05Named services are an optional semantic layer
Ordinary MCP is enough when the product has a small, transport-specific tool surface:
search_reports get_report create_invoice
A named service is useful when the platform should understand a reusable realm across apps, agents, widgets, and transports:
provider.about provider.capabilities object.search object.get / object.action relation.* event.resolve block.produce / block.render
The owner app defines refs, object kinds, search scopes, operations, presentation, guards, and connected-account claims. Its registry publishes an authoritative snapshot into tenant/project discovery. A consumer resolves the provider locally when loaded in the same runtime or through a declared transport when it lives elsewhere.
one provider implementation -> runtime-local client -> REST adapter -> MCP adapter -> Data Bus relay
The adapters hydrate caller context and protocol envelopes; they do not reimplement the domain.
Named services therefore solve "one domain grammar, several consumers." They are not a prerequisite for exposing an app through MCP, and they should not be added merely to make a product-specific tool sound generic.
Provider ownership is an explicit app decision:
class FreightEntrypoint(BaseEntrypointWithEconomics): def _named_service_providers(self) -> list: providers = list(super()._named_service_providers()) providers.append(self._freight_provider()) return providers
The complete registry published by that app is authoritative for its current contribution. Implementing reusable provider code in a base class does not silently make every derived app an owner.
A consuming app grants a specific agent only the operations it needs:
surfaces: as_consumer: agents: main: tools: - kind: named_service alias: named_services namespaces: freight: allowed: - provider.about - object.search - object.get - object.action
Tenant/project discovery finds the owner contribution. The consumer config sets its allowed operation ceiling; the provider still parses refs, enforces actions, and owns returned object semantics. Other consumers, such as a widget resolver or background job, call the same provider contract under their own current AuthContext.
06Connection Hub governs both delegation directions
Two integration directions are easy to confuse:
| Connection Hub view | Meaning |
|---|---|
| Delegated to KDCube | the user connects an external account so trusted KDCube code may use that provider on the user's behalf |
| Delegated by KDCube | the user grants a hosted agent, external MCP client, or automation bounded access to KDCube resources |
The first direction stores provider-account metadata separately from raw provider credentials. Trusted provider adapters resolve the credential server-side only after the required account claims pass.
This first direction also has an operator prerequisite: Connection Hub must have a provider adapter and connector app configured, including its allowed_claims ceiling and secret client credential. A tool can request only claims inside that ceiling; connecting an account cannot invent provider permissions the connector app was never configured to request.
The second direction creates a server-side grant for a concrete client. Its authority includes resource_grants (resource to grants/claims), optional selected operations/tools, identity scope, expiry, and revocation state. The client receives a bounded bearer handle, not the user's browser session.
For a hosted agent to use the user's Slack account through the KDCube named-services MCP bridge, both decisions must hold:
per-agent Delegated by KDCube grant
allows this agent into the named-services MCP resource
with KDCube resource grants:
named_services:use + the Slack tool's declared claims
AND
user's Delegated to KDCube Slack connection
has the provider account and its approved provider claims
|
v
provider adapter resolves token server-side and executes
Revoking either side causes subsequent calls to stop. The agent never receives the Slack token.
Consent can expose the nested KDCube claims required behind a selected MCP tool. The user chooses which claims to place under that KDCube resource; the runtime enforces the accepted resource/tool/grant set at the managed boundary. Separately, the provider adapter checks the connected account and its provider-approved claims at the actual Slack call. The vocabulary may contain the same slack:* names, but the two records are independent and neither one implies the other.
07External clients and hosted agents use the same principal class
Connection Hub's Delegated by KDCube registry can contain:
- an external OAuth/MCP client such as Claude Code;
- a manually created automation token;
- a hosted agent acting for the current user.
All are delegated clients, but they have distinct client ids and grants.
An external MCP client follows discovery, authorization, consent, and token exchange. A manual automation receives a token once from the Connection Hub UI. A hosted agent raises an in-product consent demand and later resolves its per-agent grant through the connections SDK.
They converge at enforcement:
presented bearer -> bound server-side grant -> resource_grants + selected operations + expiry + revocation -> projected actor/grantor authority -> protected app surface
No path receives the user's unrestricted browser session. This is what lets an operator revoke one agent without revoking its sibling, or narrow one external automation without changing the app's own credential.
08Telegram is a public ingress adapter, not an inline agent runtime
A Telegram-capable app exposes a public webhook surface, but the webhook should remain thin:
Telegram Bot API -> POST app public telegram_webhook -> verify configured webhook proof -> claim update_id for idempotency -> hydrate Telegram files -> resolve Telegram actor and Connection Hub identity edge -> submit normal conversation external_events[] -> acknowledge Telegram early proc later claims queued conversation turn -> runs the app's reactive surface -> streams permitted activity -> renders final result/files from the turn record -> sends through Telegram Bot API
The Telegram actor does not become a platform user because its numeric id appears in a payload. Connection Hub verifies the channel identity and resolves the explicit link/delegation edge to a platform subject. An unlinked actor receives the connection flow.
The webhook does not keep an HTTP request open while an agent reasons and does not start a private answer subscriber. It reuses the same conversation scheduler and turn result contract as the browser channel.
09Four complete integration journeys
The four journeys share one discipline. The app database remains the authority and the Data Bus stream is delivery; the managed guard runs before any app code; server-side credential resolution keeps raw provider secrets away from agents; and the provider app remains the domain owner in every transport.
10Choose the narrowest surface that preserves the contract
| Need | Start with |
|---|---|
| Browser or internal request/response | authenticated @api(route="operations") |
| Provider callback or webhook | @api(route="public") plus explicit proof |
| Standard agent tool protocol | ordinary @mcp provider/consumer |
| Reusable object realm across apps and agents | named-service provider/client |
| Durable browser-to-app mutation | Data Bus handler |
| Ordered agent-visible input | conversation external event |
| Retryable deferred work | background job |
| Scheduled work discovery | cron producer plus job handler |
| Product UI | widget or main view, scene only when composition is needed |
| Crawlable public content | public-content provider |
| User's external account | Connection Hub connected-account path |
| External or hosted delegated client | Connection Hub Delegated by KDCube |
The architecture stays small when each integration earns its surface. A single API need not become a named service. An MCP tool need not become chat. A Telegram adapter need not invent another scheduler. A local cross-app call need not traverse public ingress.
11Integration review
For every new edge, answer:
- Is the app providing or consuming this capability?
- Which code declaration implements it, and which descriptor path carries policy?
- Who authenticates the caller: KDCube, the app, Connection Hub, or an external provider?
- Which actor and user subject reach product storage?
- Is the call acting as the app, one hosted agent under user delegation, or a connected external account?
- Are credentials resolved server-side and excluded from prompts, generated code, URLs, and logs?
- Is the transport durable, transient, or request/response, and where does authoritative state live?
- Can the grant/account/edge be inspected and revoked independently?
- Does this domain really need named-service semantics, or is ordinary API/MCP the clearer surface?
Those questions turn an integration inventory into a governable system.
Read the contracts
Platform documentation:
- Provider surfaces
- Consumer surfaces
- App transports
- App platform integration
- Expose an MCP service
- Connect an MCP service to an agent
- Named-service providers
- Named-service clients
- Named-service integration
- Named-service discovery
- Connection Hub
- Delegated accounts and connector apps
- Agents acting for a user
- Telegram webhook submission and delivery
Related public articles and recipes:
- MCP in KDCube
- App as MCP Provider and Consumer
- Named Services: The Interface Between Agents and App Realms
- Create a Named Service
- Connection Hub: One Broker for Delegated Trust
- Authenticated MCP in KDCube
- Connecting a Telegram Channel Through Connection Hub
- Connect Your Named Services to Claude Code
The Markdown file beside this page is the approved indexing source. Literal platform contracts retain names such as bundle_id, bundles.yaml, and SDK bundle decorators.