KDCube
← Engineering
KDCube Engineering · Protocol Map

MCP in KDCube: Connect Any Agent, Expose Any App, Govern Every Call

A technical map for consuming and exposing MCP in KDCube: per-agent tool inventories, FastMCP app surfaces, managed delegated credentials, named services, files, accounting, and economics.

16 July 2026Engineering18 minDeep Dive
MCPagent toolsConnection Hubapp surfaceseconomicsas_consumeras_providerdelegated credentialsFastMCP

MCP standardizes how an agent discovers and calls a tool. It does not decide which agent receives that tool, who may cross the endpoint, whose data the call can reach, or who pays for the work. KDCube keeps those decisions around the protocol: as_consumer connects a governed tool view to each agent; as_provider exposes an app-native FastMCP surface; Connection Hub binds external actors to approving users and concrete grants; accounting and economics control paid work. Named services can add reusable object semantics, but they are not required to use MCP in either direction.

MCP made one part of agent integration pleasantly ordinary. A client can ask a server what tools it has, inspect their schemas, and call one without learning a vendor-specific RPC protocol. That is useful. It is also only the wire.

The production questions begin one layer out:

Which of my agents can see this server?
Which tools does each agent receive?
Where is the endpoint and how is its credential resolved?
Can an external client act for a user without becoming that user?
Does consent cover this exact tool and resource?
Which identity owns product data, and which identity pays?
What happens when the tool returns a file or a result too large for context?

This article maps the actual KDCube implementation that answers those questions. It covers both directions: consuming an MCP server from any hosted agent and exposing MCP from any KDCube app.

One terminology note: current source, descriptors, and routes still use bundle in literal identifiers such as bundle_id, bundles.yaml, @bundle_entrypoint, and /api/integrations/bundles/.... In builder-facing prose, that deployable unit is an app.

01 The whole map: two directions, four owners

An app can consume MCP, provide MCP, or do both. The directions are independent.

                                KDCUBE RUNTIME

 external MCP server                                      external MCP client
          |                                                         |
          | transport + client credential                           | MCP + bearer
          v                                                         v
  +----------------------+                              +----------------------+
  | surfaces.as_consumer |                              | surfaces.as_provider |
  | mcp.services         |                              | mcp.<alias>.auth     |
  +----------+-----------+                              +----------+-----------+
             |                                                         |
             v                                                         v
  +----------------------+                              +----------------------+
  | per-agent inventory  |                              | proc MCP boundary    |
  | allowed tools/traits |                              | public/app/managed   |
  +----------+-----------+                              +----------+-----------+
             |                                                         |
             v                                                         v
     KDCube-hosted agent                                     app FastMCP tools

  connection owner: app descriptor                    boundary owner: app descriptor
  agent policy owner: app descriptor                  delegated grants: Connection Hub
  remote price owner: explicit adapter                paid work: accounting + economics
MCP in KDCube has independent consumer and provider directions The consumer lane connects an external MCP server through an app connection registry and per-agent policy. The provider lane connects an external MCP client through a managed boundary to app FastMCP tools. Shared policy services sit below both lanes. ONE PROTOCOL, TWO INDEPENDENT DIRECTIONS MCP is the wire. The app descriptor owns the boundary. AS CONSUMER Connect an MCP server to a specific agent MCP server HTTP / SSE / stdio remote or local mcp.services endpoint + transport credential reference registered once agents.main server id tool allow-list traits KDCube-hosted agent mcp.<alias>.<tool> ReAct or a framework adapter AS PROVIDER Expose app capabilities to an MCP client MCP client agent / IDE / app MCP + bearer mcp.<alias>.auth public / app-owned or managed before app code @mcp FastMCP tools async domain services Named services: optional Use for a reusable object realm and common grammar Not required for app-native FastMCP SHARED POLICY RAIL Secrets resolve connections | Connection Hub governs delegated actors, grantors, resources and tools Accounting records usage | Economics verifies funding, reserves and settles paid work
Fig. 1 - two independent MCP directions with one explicit policy rail.

Four owners keep the design understandable:

Owner Question it answers
Consumer server registry Where is the server, which transport reaches it, and how is the client credential resolved?
Per-agent tool inventory Which server and which concrete tools may this agent discover and call?
Provider auth policy + Connection Hub Which external actor may cross this exact resource/tool boundary, on whose behalf, and with which grants?
Accounting + economics What usage occurred, what did it cost, may it run, and which projected subject funds it?

MCP does not replace any of these owners. It gives all of them a common tool transport to govern.

02 Direction 1: connect an MCP server to an agent

The canonical consumer configuration deliberately separates one server connection from the views granted to individual agents.

Register the connection once

The app-wide connection lives under surfaces.as_consumer.mcp.services.mcpServers:

surfaces:
  as_consumer:
    mcp:
      services:
        mcpServers:
          docs:
            transport: streamable-http
            url: https://mcp.example.com/docs
            auth:
              type: bearer
              secret: b:mcp.docs.token

That block owns endpoint, transport, and credential reference. Supported client transports are stdio, http/streamable-http, and sse. A URL must be reachable from the runtime process that owns the MCP client, not merely from the developer's browser. In a container, 127.0.0.1 means that container.

The secret reference is resolved at runtime. The token is not copied into agent configuration, a model prompt, or a tool call. Tool-list cache keys include a hash of resolved auth headers so catalogs that legitimately differ by principal do not collide; the credential itself is not the cache key or log payload.

Attach a narrowed view to each agent

The agent connection lives separately:

surfaces:
  as_consumer:
    default_agent: main
    agents:
      main:
        tools:
          - kind: mcp
            server_id: docs
            alias: docs
            allowed: [search, fetch]
            tool_traits:
              search:
                strategy: [exploration]
              fetch:
                strategy: [exploration]

The model-facing ids become:

mcp.docs.search
mcp.docs.fetch

server_id selects the connection. alias selects the model-facing tool namespace. allowed is the administrator's tool allow-list for that agent. tool_traits describes planner/execution strategy; it is not authorization and does not widen the allow-list.

Two agents can use one server without receiving equal authority:

agents:
  analyst:
    tools:
      - kind: mcp
        server_id: crm
        alias: crm
        allowed: [search_customers, read_customer]

  operator:
    tools:
      - kind: mcp
        server_id: crm
        alias: crm
        allowed: [search_customers, read_customer, update_customer]
one physical CRM MCP connection
        |
        +-> analyst:  search + read
        `-> operator: search + read + update

This is the first governance boundary: registering a server does not expose it to every agent, and attaching it to one agent does not attach it to another.

One MCP connection produces different governed tool inventories for different agents The CRM endpoint, transport, and secret are registered once. Analyst receives search and read tools, operator also receives update, and summarizer receives no MCP tools. REGISTER ONCE, GRANT PER AGENT A connection is not an agent permission. MCP connection registry mcpServers.crm transport streamable-http url https://.../crm auth secret: b:mcp.crm.token Owned once by the app. No agent id here. No model prompt receives the endpoint credential. Answers: where and how to connect? Agent: analyst server_id: crm | alias: crm search_customers read_customer read-only inventory Agent: operator server_id: crm | alias: crm search_customers read_customer update_customer Agent: summarizer No `kind: mcp` entry for CRM. No CRM tools in its catalog. Registration alone never widens this agent. Policy question: which server and tools may this model-facing agent know and call?
Fig. 2 - register the connection once; grant concrete tools per agent.

03 The consumer package journey

For the KDCube ReAct agent, the descriptor path is direct:

effective app props
  surfaces.as_consumer.mcp.services
  surfaces.as_consumer.agents.<agent_id>.tools
        |
        v
agent_tool_config_from_bundle_props(...)
        |
        +-> mcp_tool_specs
        |     {server_id, alias, tools}
        +-> allowed_tool_names_by_alias
        +-> tool_traits / runtime policy
        |
        v
BaseWorkflow.build_react(
  mcp_tools_spec=...,
  tool_traits=...,
)
        |
        v
MCPToolsSubsystem
  connection registry + secret resolution + tools/list
        |
        v
filtered catalog: mcp.<alias>.<tool>
        |
        v
agent emits complete tool call
        |
        v
io_tools.tool_call -> MCPToolsSubsystem.execute_tool -> MCP transport

The parser creates the policy half:

AgentToolConfig(
    mcp_tool_specs=[
        {"server_id": "docs", "alias": "docs", "tools": ["search", "fetch"]}
    ],
    allowed_tool_names_by_alias={"docs": ["search", "fetch"]},
    # tool traits, runtime policy, and other resolved fields omitted
)

The MCP subsystem combines that policy with the separate server registry. The model receives only the resulting agent-scoped catalog.

This is not ReAct-only

A hosted LangGraph, CrewAI, Claude Agent SDK, or custom loop needs the same two inputs:

agent policy
  AgentToolConfig.mcp_tool_specs
  = server refs + aliases + allowed tool names

connection registry
  surfaces.as_consumer.mcp.services
  = transports + endpoints + credential references

Its adapter translates the selected server entries into that framework's MCP client, discovers tools, filters them to mcp_tool_specs, and binds only that narrowed list to the agent. The app descriptor stays the authority; framework code should not carry a second URL list, a second token store, or a second hand-written allow-list.

The KDCube LangGraph reference demonstrates the framework seam with langchain-mcp-adapters: load MCP tools asynchronously, bind them alongside plain LangChain tools, and degrade cleanly when the optional adapter is absent. The canonical production configuration remains the two-part registry + per-agent policy above.

For Claude Code as a subprocess, .mcp.json is the client contract. The KDCube Claude workspace adapter can generate it from selected app configuration before the process starts. Changing surfaces.as_consumer does not mutate an already running external CLI process.

04 Trusted routing, untrusted execution

The boundary remains intact when an agent uses isolated code execution:

model-generated code
       |
       v
network-isolated executor
  no MCP credential
       |
       | approved tool request over supervisor channel
       v
trusted supervisor
  MCP registry + secret resolution + transport
       |
       v
MCP server

The executor does not receive the full app configuration or MCP token. It asks the trusted supervisor to execute an allowed tool. That preserves the same tool inventory while keeping credentials out of unsafe code.

05 Direction 2: expose MCP from any KDCube app

The provider path is equally small. Build an ordinary FastMCP app and expose it through one @mcp method.

from mcp.server.fastmcp import FastMCP
from kdcube_ai_app.infra.plugin.bundle_loader import mcp


def build_reports_mcp(service):
    server = FastMCP("Reports", stateless_http=True)

    @server.tool(
        name="search_reports",
        description="Search reports visible to the authorized principal.",
    )
    async def search_reports(query: str, limit: int = 10):
        return await service.search(query=query, limit=min(max(limit, 1), 25))

    return server


class ReportingApp(...):
    @mcp(
        alias="reports",
        route="public",
        transport="streamable-http",
        auth_config="surfaces.as_provider.mcp.reports.auth",
    )
    async def reports_mcp(self, request=None, **kwargs):
        return build_reports_mcp(self.reports.for_request(request))

The route is:

/api/integrations/bundles/<tenant>/<project>/<bundle_id>/public/mcp/reports

or, for route="operations":

/api/integrations/bundles/<tenant>/<project>/<bundle_id>/mcp/reports

The surface uses streamable HTTP today. stateless_http=True is not cosmetic: proc dispatch is request-by-request, and the next MCP request may hit another worker or a fresh app instance. Durable credential, consent, product, and job state live in their stores. A FastMCP session object in one worker is not a durable boundary.

App tools remain ordinary async product code. FastMCP supplies protocol discovery and input schemas; KDCube supplies loading, routing, request context, descriptor policy, secrets, observability, and optional managed authorization and economics.

06 Three provider authorization modes

The provider descriptor selects who owns authorization:

Mode Shape Meaning
Intentionally public no managed/app-owned mode no credential guard; suitable only for genuinely public, bounded work
App-owned auth.mode: bundle the app validates its own credential and applies its own policy
Platform-managed auth.mode: managed KDCube/Connection Hub validates delegated credential, resource, grants, and selected tool before app code runs

The route and auth mode answer different questions. A public/mcp/... route is reachable for MCP discovery and OAuth challenge. It can still require a managed bearer for every useful call.

The managed provider declaration is compact:

surfaces:
  as_provider:
    mcp:
      reports:
        auth:
          mode: managed
          authority_id: delegated_client
          selected_tool_grants: true

Connection Hub owns the delegable capability and the concrete resource/tool catalog:

connections:
  delegated_credentials:
    oauth:
      capabilities:
        - grant: reports:read
          label: Read reports
          delegable_roles:
            - kdcube:role:registered
            - kdcube:role:paid

      resources:
        - resource: >-
            */api/integrations/bundles/*/*/reporting@1-0/public/mcp/reports*
          tools:
            search_reports:
              label: Search reports
              grants: [reports:read]
            get_report:
              label: Read report
              grants: [reports:read]

The app says, "this endpoint uses managed delegated-client auth." Connection Hub says, "these tools exist, these grants unlock them, and these roles may delegate those grants." Keeping those owners separate lets one authority govern many app surfaces without moving product code into the authority service.

07 What happens before a managed tool runs

The proc MCP bridge resolves the app and its effective descriptor before it calls the FastMCP builder. For mode: managed, the request body is inspected and the Connection Hub guard runs first:

POST .../public/mcp/reports
        |
        v
resolve app + @mcp endpoint + effective auth descriptor
        |
        v
mcp_auth_mode == managed ?
        |
        v
authorize_delegated_mcp_request
  1. bearer present and valid
  2. issuer authority matches
  3. credential resource matches this public URL
  4. resource grants cover this tool policy
  5. concrete tool is selected in the consent/grant record
        |
        +-- deny: HTTP/MCP error; app tool body never runs
        |
        v
apply delegated runtime projection
        |
        v
bind request/app-operation contexts
        |
        v
invoke app @mcp method -> FastMCP dispatch -> async domain service

tools/list is protocol discovery; concrete tools/call is where per-tool grant and selected-tool enforcement applies. MCP ToolAnnotations help a client present read/write/destructive intent. They do not grant authority. The Connection Hub record and product checks do.

Managed MCP request path through Connection Hub policy A delegated MCP request passes bearer, authority, resource, grant, and selected-tool checks before app code runs. Accepted calls project the external actor and approving grantor into request context. MANAGED AS_PROVIDER REQUEST JOURNEY Authorization finishes before the FastMCP tool starts. External MCP client tools/call + Authorization: Bearer ... Proc resolves the provider boundary app + @mcp alias + effective auth descriptor mode: managed | authority_id: delegated_client Connection Hub delegated credential guard 1 Bearer is present, valid, and backed by a stored grant record 2 Issuer authority matches the endpoint policy 3 Credential resource matches this concrete public MCP URL 4 Resource grants cover the requested tool policy 5 Concrete tool was selected in this connection's consent ANY CHECK FAILS HTTP or MCP error returned immediately App tool body never runs Accepted request projection delegate_identity = external client actor grantor_user_id / platform_user_id / economics_user_id = approving user Invoke @mcp -> FastMCP tool -> async product service No app-specific token parser in managed mode
Fig. 3 - the delegated credential guard completes before FastMCP dispatch.

08 MCP, OAuth, and delegated credentials are different layers

These terms often get collapsed, which makes the system sound more mysterious than it is:

MCP                   tool discovery and invocation transport
OAuth                 client-facing adapter used to obtain a credential
delegated credential  stored authorization model behind that bearer
Connection Hub        owner of consent, grants, identity projection, revocation

An external client first probes the MCP resource. The managed guard returns a protected-resource challenge. The client follows Connection Hub metadata, the user signs in through the normal KDCube authority, approves the concrete resource/tools/grants, and the client receives a short-lived delegated bearer.

On later calls the bearer identifies a derived external actor, not the user's browser session. The durable grant record retains what matters:

delegate actor
approving grantor
concrete resource
resource grants
selected tools
identity scope
expiry and revocation

Revoking one connection removes that authority without rotating a shared key or breaking unrelated clients.

09 Actor and grantor survive the boundary

After acceptance, the runtime projection deliberately preserves two identities:

delegate_identity / actor_identity
  the external client that made this request

grantor_user_id / platform_user_id
  the KDCube user who approved access and whose scoped product data is used

economics_user_id
  the projected platform subject used for funding/economics decisions

The app therefore does not need to accept user_id as a tool argument. It reads the request's identity_authority, uses platform_user_id for product scope, keeps delegate_identity in provenance/audit records, and derives economics from economics_user_id.

This projection also carries grants, selected operations, identity scope, and authority provenance. Product storage still performs domain authorization: a valid reports:read grant does not make an arbitrary report id visible if the grantor cannot read that record.

10 Named services are optional, not a toll booth

MCP and named services solve different problems.

Use app-native FastMCP when product-specific tools are the clearest contract:

search_reports(query, limit)
get_report(report_id)
create_forecast(request_id, assumptions)

Use a named-service provider when multiple apps, agents, widgets, and scenes should share a typed object realm with stable refs and a common grammar:

namespace: report
refs: report:item:<id>
operations:
  provider.about
  provider.capabilities
  object.schema
  object.search
  object.get
  object.upsert
  object.action

The generic kdcube-services@1-0 MCP surface can project configured named services to external agents. That gives an agent one discovery/search/get/action grammar across memory, conversation, canvas, mail, or another provider. It also supports nested namespace grants behind the outer MCP resource grant.

But an app does not need to become a named-service provider to expose MCP, and an agent does not need named-service tools to consume an MCP server. Named services add reusable object semantics. MCP remains the transport.

App-native MCP and named-service MCP are two valid provider paths A product app can expose domain-specific FastMCP tools directly. Alternatively, a named-service provider can use the generic named-services MCP bridge for a reusable object grammar. Both are optional peers behind the same governance boundary. TWO VALID PROVIDER PATHS Named services extend MCP. They do not gate it. A. App-native FastMCP Use when product-specific tools are already clear. async product domain service @mcp + FastMCP tools search_reports | get_report | create_forecast B. Named-service MCP bridge Use when a reusable typed object realm is useful. namespace provider + stable object refs generic named-services MCP bridge schema | search | get | upsert | action Same MCP client ecosystem and governance choices public | app-owned | Connection Hub managed | accounting/economics MCP clients and KDCube agents
Fig. 4 - named services extend MCP with an object grammar; they do not gate it.

11 Results and files: keep bytes out of model context

An MCP result normally becomes a structured tool-result block. Good result contracts are bounded and actionable:

  • cap and paginate searches;
  • return stable ids or object refs;
  • return summaries before full bodies;
  • use explicit error shapes;
  • make write operations idempotent with caller-supplied request ids;
  • return short-lived download URLs or product refs for large/binary files.

Do not return a large binary as base64 merely because JSON can carry it. That inflates the payload and puts bytes into the agent's context.

There is one narrower KDCube path: an MCP-backed tool executing inside the current ReAct workspace can declare files with ret.artifact_type == "files" when those paths really exist under the current turn_<id>/files/... output tree. The runtime then hosts the declared files. A remote MCP service cannot write into that workspace; it should return an already-hosted reference or URL instead.

12 Economics: MCP does not imply free, paid, or metered

MCP carries a call. It does not declare a price.

Keep three mechanisms separate:

Mechanism Question
Accounting/tracking What service and units were used?
Pricing What did those units cost?
Economics enforcement May this flow run, which funding source pays, and how is actual cost settled?

Inbound: an external agent calls your paid MCP tool

A managed delegated request already projects the approving user's economics identity while preserving the external client actor. The app can therefore put paid work under the same guard used by a UI, REST operation, background job, or chat turn:

external client tools/call
        |
        v
managed MCP authorization
  delegate actor + grantor/economics projection
        |
        v
EconomicsGuard
  verify plan/quota/funding -> reserve estimate
        |
        v
tracked paid service
  emit provider/model/units/cost in this scope
        |
        v
settle actual cost or release reservation

That is inbound payment control: the app knows the accountable operation, the projected subject, and the tracked cost, so it can admit and settle the request. It is not "MCP billing." The paid service must still report usage and a price.

Outbound: your agent calls somebody else's MCP server

The outgoing call runs inside the current KDCube request context, so paid KDCube services invoked inside that context keep their accounting lineage. But KDCube cannot infer what an arbitrary remote MCP vendor charged. If that remote call must affect a KDCube budget, wrap it in an instrumented adapter that reports supported usage or actual cost_usd, then place the call under economics enforcement.

Transport choice is never a price declaration:

same MCP call
  could be free local lookup
  could invoke an accounted KDCube embedding
  could call a metered third-party API

The implementation, not the tools/call envelope, determines accounting.

MCP economics requires explicit tracking, pricing, and enforcement Inbound managed calls project the approving user's economics identity into a guard and tracked service. Outbound remote MCP costs require an explicit adapter to report cost. MCP transport itself is not billing. ACCOUNTING IS IMPLEMENTATION, NOT TRANSPORT MCP carries the call. It does not declare the price. INBOUND: an external agent calls your paid MCP tool Managed MCP actor + grantor validated Identity projection economics_user_id EconomicsGuard verify + reserve funding decision Tracked service provider + units actual cost Settlement commit actual or release hold Result: paid work is admitted and charged to the projected platform subject while the external client remains the actor. OUTBOUND: your KDCube agent calls a remote MCP provider KDCube agent current request lineage MCP transport tools/call Remote provider vendor-defined pricing Explicit accounting adapter required report supported units or actual cost_usd then run under economics when budgets must apply MCP transport != billing, pricing, or automatic vendor cost inference
Fig. 5 - MCP transports a call; explicit implementation supplies usage, price, and enforcement.

13 A practical decision table

Need Use
Connect one remote MCP server to one KDCube agent register under as_consumer.mcp.services; attach one kind: mcp allow-list to that agent
Give several agents different views of one server one registry entry; separate per-agent allowed lists
Bind MCP tools into LangGraph/CrewAI/custom agent adapt registry + resolved agent policy into that framework; do not duplicate config authority
Expose product-specific tools from an app FastMCP + one @mcp entrypoint method
Make an endpoint intentionally public omit managed/app-owned auth and keep work strictly bounded
Use an app-specific API key or header auth.mode: bundle; app owns verification and policy
Let external clients connect by user consent auth.mode: managed + Connection Hub resource/tool/grant catalog
Share one object grammar across apps/agents/UI optional named-service provider + generic named-services MCP bridge
Charge for work behind an MCP tool tracked service + pricing + EconomicsGuard under projected identity
Deliver large files product ref or short-lived URL; local ReAct file declaration only when file exists in that workspace

14 Regression checklist

Test the boundaries independently. A green MCP tool call alone is not enough.

Consumer

  1. One registered server plus one agent kind: mcp entry produces only the expected mcp.<alias>.<tool> ids.
  2. Removing a tool from allowed removes it from that agent's catalog.
  3. A second agent can use a narrower list without duplicating endpoint/auth.
  4. A missing server_id, unreachable container URL, or interactive auth profile fails closed without leaking credentials.
  5. In isolated execution, the executor has no MCP token and the trusted supervisor performs the call.

Provider

  1. initialize, tools/list, and a bounded read-only tools/call survive a worker restart because the FastMCP app is stateless.
  2. Managed endpoint without a bearer returns the protected-resource challenge.
  3. Valid consent permits a selected tool; an unselected tool fails before its body runs.
  4. The same token fails against a different concrete MCP resource.
  5. Revocation stops the next call.
  6. App data reads use projected platform/grantor identity, not caller-supplied user ids.

Economics and data

  1. A paid inbound call records delegate provenance and charges the projected economics subject.
  2. Denied economics leaves no paid service call and no abandoned reservation.
  3. Remote provider cost is absent unless an adapter explicitly reports it.
  4. Large binaries are refs/URLs, not base64 in model context.
  5. Retried write calls do not duplicate side effects.

15 The builder path, in order

For consumption:

1. register server once
2. attach explicit tools to one agent
3. verify model-facing catalog
4. add credential reference
5. test isolation and result size
6. instrument cost only if the operation is paid

For exposure:

1. wrap existing async domain methods as FastMCP tools
2. expose one @mcp alias with stateless_http=True
3. choose public, app-owned, or managed auth
4. for managed auth, publish Connection Hub resource/tool/grant policy
5. use projected identity in product queries
6. add accounting/economics for paid work
7. add named-service semantics only if a reusable object realm is useful

MCP should make the integration easy. The surrounding runtime should make the easy integration safe to operate.

16 Build it

17 Related engineering articles

KDCube Engineering
16 JUL 2026