源码快照 @ 2026-09-02 (source-only, 剔除非源码素材)
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,110 @@
|
||||
# Native MCP Apps local demo
|
||||
|
||||
This runbook proves the native MCP Apps path with a deterministic local fixture.
|
||||
It does not depend on a hosted gallery, production rollout flags, customer
|
||||
connectors, or personal credentials.
|
||||
|
||||
The required fixture is Project Atlas from
|
||||
`evals/specs/remote-mcp-apps.e2e.test.ts`. It runs as a local Streamable HTTP MCP
|
||||
server, exposes `ui://project-atlas/view.html`, and has a same-server
|
||||
`search_projects` operation. The tape starts real local Den API and Web
|
||||
processes, a real Electron Desktop/local-server process, MySQL, Redis, and a
|
||||
synthetic model server.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 24 and pnpm 11.4.0 (the versions declared by the repository)
|
||||
- Bun, Docker, and an available local MySQL port (`3306`)
|
||||
- no real model/provider credentials in the shell
|
||||
|
||||
From a clean checkout of the exact branch under test:
|
||||
|
||||
```bash
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm --dir evals install --frozen-lockfile
|
||||
pnpm --filter @openwork/types build
|
||||
pnpm --filter @openwork-ee/den-db build
|
||||
pnpm --filter @openwork/email build
|
||||
pnpm dev:den:mysql
|
||||
```
|
||||
|
||||
## Run the exact-head demo tape
|
||||
|
||||
```bash
|
||||
OPENWORK_EVAL_E2E_TESTS=1 pnpm evals:e2e remote-mcp-apps
|
||||
```
|
||||
|
||||
A valid required proof ends with one passing test, zero failed tests, zero
|
||||
skips, and `"verdict":"passed"`. Keep the JSON report path printed by the
|
||||
runner with the local verification record.
|
||||
|
||||
The tape performs this demo sequence:
|
||||
|
||||
1. Starts an isolated Den organization, synthetic member, local Project Atlas
|
||||
MCP server, and Desktop profile.
|
||||
2. Adds Project Atlas as an organization Connect MCP server and waits for it to
|
||||
become ready.
|
||||
3. Confirms the model-visible surface contains only the central
|
||||
`search_capabilities` and `execute_capability` tools.
|
||||
4. Confirms stale per-connection compatibility exposes only that same bounded
|
||||
pair, no resources/templates/App metadata, and no direct provider tools.
|
||||
5. Confirms the private App host receives only the originating server's
|
||||
app-visible tools and exact `ui://project-atlas/view.html` resource.
|
||||
6. Searches and executes `search_projects` through the originating server's
|
||||
App-host capability pair and observes the synthetic Atlas migration result.
|
||||
7. Prompts the synthetic model to return the bound tool result, renders the App
|
||||
in Desktop, reloads it, and confirms it renders again.
|
||||
8. Restarts Desktop with the same isolated profile, revisits the session, and
|
||||
confirms the App recovers.
|
||||
|
||||
Native MCP Apps are enabled for every deployment and organization; no
|
||||
deployment or organization gate needs to be flipped for this demo.
|
||||
|
||||
## Focused security and compatibility checks
|
||||
|
||||
```bash
|
||||
cd apps/server
|
||||
bun --conditions=development test \
|
||||
src/connect-mcp-server-catalog.test.ts \
|
||||
src/mcp-app-host.test.ts \
|
||||
src/mcp-app-sandbox.test.ts \
|
||||
src/cloud-mcp-reconcile.e2e.test.ts
|
||||
|
||||
cd ../app
|
||||
bun test \
|
||||
tests/den-mcp-url.test.ts \
|
||||
tests/mcp-app-frame.test.ts \
|
||||
tests/session-mcp-maintenance.test.ts \
|
||||
tests/cloud-mcp-maintenance-gate.test.ts
|
||||
|
||||
cd ../../ee/apps/den-api
|
||||
bun test \
|
||||
test/remote-mcp-app-rollout.test.ts \
|
||||
test/external-connection-proxy.test.ts \
|
||||
test/admin-organization-capabilities.test.ts \
|
||||
test/organization-capabilities.test.ts
|
||||
```
|
||||
|
||||
These checks cover exact trusted-origin matching, cross-origin rejection,
|
||||
origin-bound App-host credentials, App-host credential privacy, sandbox/CSP
|
||||
enforcement, same-server binding, rollout defaults, older responses without an
|
||||
App-host credential, bounded stale-client compatibility, and preservation of
|
||||
ordinary Connect when Apps are disabled.
|
||||
|
||||
## Expected failure categories
|
||||
|
||||
- A skipped tape is incomplete proof. Check the consent environment variable,
|
||||
local placement, and MySQL availability.
|
||||
- `missing local server credentials` is a Desktop startup/readiness problem.
|
||||
- A resource error names the exact URI, MIME, size, or CSP validation failure.
|
||||
- An origin error indicates that the Den/App-host endpoint pair is not an exact
|
||||
trusted-origin match.
|
||||
- A tool denial indicates wrong-server routing, app visibility, workspace
|
||||
policy, provider authorization, or required mutation confirmation.
|
||||
- A client that does not advertise the private App-host capability keeps the
|
||||
bounded search/execute surface; that is expected, not a resource-loading
|
||||
failure.
|
||||
|
||||
The hosted SOL gallery may be checked separately as an external observation,
|
||||
but its reachability is not OpenWork compatibility proof and it is not part of
|
||||
this required demo.
|
||||
Reference in New Issue
Block a user