Host bridge and adaptors
One Adaptor interface behind two host runtimes, and how provideMcpUi wires it into Angular DI.
Two host runtimes exist today.
- The OpenAI Apps SDK. It gives a
window.openaiglobal. ChatGPT uses it. - The open MCP-Apps postMessage specification,
@modelcontextprotocol/ext-apps. Claude and other MCP-Apps hosts use it.
ng-mcp-ui/web puts one Adaptor interface in front of the two runtimes. Your widget never
branches on the host.
provideMcpUi
provideMcpUi() is the one provider that a widget needs. It sets up
zoneless change detection, it derives the two host tokens from the window.mcpUi object that the
shell injected, and it registers the modal service.
import { bootstrapWidget, provideMcpUi } from "ng-mcp-ui/web";
import PollWidget from "./poll.widget";
// bootstrapWidget applies provideMcpUi() for you:
bootstrapWidget(PollWidget);
// …or provide it yourself, e.g. alongside app-specific providers:
bootstrapWidget(PollWidget, [provideMcpUi(), provideMyThing()]);
bootstrapWidget boots a standalone component into the #root
element of the shell, and it returns the ApplicationRef.
The two DI tokens
| Token | Value |
|---|---|
MCP_ADAPTOR |
The resolved host bridge. Each inject* function reads it. None of them reads a global. |
MCP_SERVER_URL |
The origin of the MCP server that served this view. The mcpAsset pipe uses it. |
ng-mcp-ui/web exports both tokens. Therefore you can provide MCP_ADAPTOR yourself, and run a
widget against your own adaptor or a mock. This is what
provideMockMcpUi() does.
Because DI resolves the tokens, you must call each inject* function from an Angular injection
context: a field initializer, a constructor, or runInInjectionContext.
Reading the raw host context
injectHostContext() is the low-level escape hatch. It gives one
readonly signal for each raw host-context key.
import { injectHostContext } from "ng-mcp-ui/web";
const host = injectHostContext();
// host.theme(), host.displayMode(), host.toolOutput(), …
The typed wrappers are derived from it: injectToolInfo,
injectLayout, injectUser and
injectViewState. Use those first.
createHostContextSignals is the non-DI form, for code that already holds an adaptor.
Driving the host
The bridge also gives callable forwarders for each action that a view can ask of the host: open an external URL, send a follow-up message, request a modal, resize the iframe, close the view, start a download, set the open-in-app link, and register a view tool. The ng-mcp-ui/web reference lists each one.
Some actions work on one runtime only. Read host support before you depend on one.
getAdaptor
getAdaptor() reads the adaptor from the environment directly, with no DI.
Prefer the MCP_ADAPTOR token. Nothing in the library calls getAdaptor(), and that is what makes
one provider override replace the host for a whole widget. A call to getAdaptor() bypasses that
seam, therefore a test cannot replace it.
The dev tunnel
A host must reach your server over HTTPS. ng add writes a tunnel npm script as a documented
placeholder. It does not hard-wire a provider CLI.
npm run tunnel
# → Expose http://localhost:4200 with your tunnel of choice, e.g.
# cloudflared tunnel --url http://localhost:4200
Replace the body of the script with the command you choose. ng-mcp-ui/tunnel is the reserved slot
for a managed cloudflared surface, and it is a skeleton today.