Files

139 lines
7.0 KiB
Markdown

# Native MCP Apps
OpenWork supports MCP Apps delivered by standard MCP servers connected through
OpenWork Connect. A server advertises the stable
`io.modelcontextprotocol/ui` extension, a tool binds an exact UI resource with
`_meta.ui.resourceUri`, the host reads that resource with `resources/read`, and
tool inputs and results move over the standard MCP Apps bridge.
This unit of value does **not** include installing a standalone App from an
HTML URL. URL-imported MCP Apps are deferred future work with a separate
product, security, lifecycle, and rollout contract.
Workflows remain executable `workflow` config objects. A Workflow's generated
views can be MCP resources, but that does not turn Workflow execution into
resource loading or a standalone URL-App installation path.
## Standard MCP server path
Connect continues to own server configuration, authentication, access grants,
per-member credentials, and tool policy. The OpenWork Cloud control server
publishes a member-scoped resource at:
```text
openwork://connect/mcp-servers/index.json
```
The signed-in Desktop session mints a separate short-lived App-host credential
with a non-public `mcp:app-host` scope. Desktop stores that credential and the
endpoint descriptors only in private App-host state; neither is projected into
OpenCode. It reads the index with that credential and advertises the
`mcp-app-host-v1` client capability. Den returns a non-empty provider index only
when the server-verified scope, client capability, and both rollout gates are
present. A normal model or legacy MCP token cannot unlock the index by spoofing
an audience or capability header. Desktop never writes `openwork-connect-*`
entries to the OpenCode runtime or any model-visible MCP registry. A connection
is proxied at:
```text
/mcp/agent/connections/{connectionId}
```
Current Desktop clients never register these provider descriptors in OpenCode;
the model discovers and invokes ordinary provider operations only through the
central `openwork-cloud` `search_capabilities` and `execute_capability` tools.
For stale published clients that still retain an old per-connection entry, the
proxy exposes only a compatibility pair named `search_capabilities` and
`execute_capability`. It never returns the provider catalog, MCP App launch
tools or metadata, resources, or templates, and it rejects every direct
provider call. This keeps ordinary operations bounded while old entries are
removed during reconciliation.
Desktop's local App host authenticates with the private scoped credential. Only
that transport receives tools with a valid `_meta.ui.resourceUri` whose
provider-declared visibility includes `app`, plus app-visible `search_capabilities` and
`execute_capability` scoped to the originating server. A native MCP App can
therefore render and use authorized tools from its regular MCP server without
placing the provider catalog in the model request.
The app-host view preserves:
- the App tool's exact name, input/output schemas, annotations, and UI binding;
- concrete descriptors and `resources/read` content only for resources bound
by exposed App tools;
- `content`, `structuredContent`, `_meta`, and `isError` from `tools/call`;
- the stable MCP Apps extension and `text/html;profile=mcp-app` resources;
- one server identity per Connect connection, preserving the same-server
tool-call boundary.
OpenWork access grants, disabled-tool policy, and approval rules still apply at
the proxy boundary. The App-host credential authorizes only this bounded proxy
surface; it is not a provider credential and grants no direct cross-server
access.
## Rollout
Native MCP Apps are enabled for every deployment and organization. The former
deployment gate (`DEN_REMOTE_MCP_APPS_ENABLED`) and the per-organization
**Native MCP Apps (preview)** capability were removed once the feature
stabilized; stale stored organization overrides are ignored. App launch
metadata (`kind: mcp_app`, `mcpApp.resourceUri`, `openwork/mcpApp` meta) is an
opaque binding published on every bounded search/execute result; clients that
do not host Apps ignore it and keep the normal tool result. The private
App-host index and per-connection provider proxy remain gated on the
client-advertised App-host capability header, so older Desktop clients keep
their bounded search/execute surface. Reconciliation also removes and
disconnects stale
`openwork-connect-*` OpenCode entries while preserving user-authored MCPs and
all durable Connect records.
The provider proxy advertises `listChanged: false` because the current
enterprise connector opens bounded request sessions rather than a durable
downstream notification stream. Catalog refresh happens on Connect
reconciliation, Desktop startup, engine refresh, or an explicit Cloud MCP
refresh. Forwarding downstream list-change notifications remains follow-up
interoperability work.
## Deferred: standalone URL-imported Apps
Installing a self-contained HTML App from a URL is intentionally outside this
change. In the current product:
- Den Web has no Add MCP App button, URL form, installed-App detail page, or
URL-App lifecycle entry point;
- the central MCP server does not register `import_remote_mcp_app` or any
standalone-App launch tool;
- capability search returns no standalone URL-App matches;
- model and App-host catalogs contain no standalone URL-App tools;
- no `ui://openwork/library-apps/...` resources are registered;
- member server indexes and launch metadata contain no standalone URL Apps;
- REST calls under `/v1/remote-mcp-apps` are not registered and are therefore
unavailable.
Existing database rows and cached revisions from earlier development remain
stored non-destructively. They are inactive and unreachable through the UI,
MCP catalogs, capability search, resources, launch metadata, and HTTP API. This
change performs no deletion and introduces no destructive migration.
The retained storage and validation implementation is not a supported runtime
surface. A future standalone URL-App unit of value must deliberately restore
its own API, UI, security review, lifecycle, testing, and rollout contract.
## Host security and compatibility
Desktop negotiates the stable extension, resolves the current tool definition,
reads the exact `ui://` resource even when it is absent from `resources/list`,
accepts text or base64 HTML, enforces MIME and size limits, validates CSP
origins, and loads the document through the isolated sandbox proxy. It sends
tool input and the preserved tool result after initialization, bounds size
changes, tears the bridge down on unmount, and contains resolution, handshake,
document, and runtime failures without hiding the normal tool result.
App-requested tools are resolved only on the originating regular MCP server
and must be visible to the app. When a tool carries `_meta.ui.resourceUri`,
Desktop also requires it to match the exact resource loaded in the calling
iframe. Workspace denies apply. Read-only capability search runs directly;
capability execution uses conservative mutation annotations and requires user
confirmation while provider authorization and audit still run server-side.
Cross-server iframe calls are not allowed.