Chapter 8: Remove The External MCP Web Server
ACME Banking's assistant can now use MCP Apps to show interactive workspace previews. The current architecture works, but it still has one extra moving part: MCP Web is started by the io.Connect Browser platform as a platform plugin.
In this chapter, you will remove that external MCP Web server and start MCP Web directly inside the io.Assist application.
The result is simpler:
- the platform still provides the banking apps and the
getClientsinterop method - io.Assist now owns the MCP Web server configuration
- AI Web connects to the MCP Web server from the same assistant app
This is useful for deployments where external MCP servers are not allowed, even on the frontend. It is also useful when the assistant developer wants full control over the MCP Web configuration and exposed capabilities.
Related API References
This chapter combines MCP Web server setup with io.Assist and AI Web configuration:
Why Move MCP Web Into io.Assist?
Until now, MCP Web has been configured in io-cb-home:
io-cb-home platform
guide plugin registers getClients
MCP Web plugin exposes MCP tools
io.Assist connects as MCP client
After this chapter, the platform remains responsible for the io.Connect Browser environment and the simple guide plugin. The assistant becomes responsible for MCP Web:
io-cb-home platform
guide plugin registers getClients
io.Assist app
starts io.Connect Browser
starts MCP Web server
starts AI Web and connects to MCP Web
This keeps the guide easier to deploy and reason about. When you open io.Assist, it brings the MCP layer it needs. When the assistant is not open, the platform doesn't need to host the MCP Web server.
Remove The Platform MCP Web Plugin
Open:
apps/io-cb-home/src/app/helpers.ts
Remove the active imports for ServerFactory and getMCPWebServerConfig, but keep them as comments:
import config from "../config.json";
import { guidePluginStart } from "./plugin";
// Chapter 8 moves MCP Web into the io.Assist application.
// Keep these imports and the plugin definition below as comments so you can see how the guide progressed.
//
// import { ServerFactory } from "@interopio/mcp-web";
// import { getMCPWebServerConfig } from "./mcp";
Then update getPluginsDefinitions() so only the guide plugin runs:
const getPluginsDefinitions = (): IOConnectBrowserPlatform.Plugins.Config => {
return {
definitions: [
{
name: "guide-plugin",
start: guidePluginStart,
critical: true
},
// {
// name: "io.MCPWeb",
// start: ServerFactory,
// critical: true,
// config: getMCPWebServerConfig()
// }
]
};
};
The platform still starts guide-plugin. That plugin registers the getClients interop method and keeps the ACME Banking client data in the platform. The only thing you removed is the platform-hosted MCP Web server.
Add MCP Web To The Angular Assistant
Create:
apps/io-assist-angular/src/app/mcp.ts
Add the MCP Web server configuration:
import IOBrowser from "@interopio/browser";
import type { IoIntelMCPWeb } from "@interopio/mcp-web";
type IOConnectAPI = Awaited<ReturnType<typeof IOBrowser>>;
const MCP_SERVER_METHOD_NAME = "io.mcp.web.server";
const GET_CLIENTS_METHOD = "getClients";
const GET_CLIENTS_TOOL = "get_clients";
const getMCPWebServerConfig = (): IoIntelMCPWeb.Server.Config => ({
licenseKey: (import.meta as any).env.VITE_IO_INTELLIGENCE_LICENSE_KEY,
mcpCoreServer: {
tools: {
static: {
methods: [
{
availability: "constant",
name: GET_CLIENTS_TOOL,
config: {
title: "Get Clients",
description: "Returns the list of ACME Banking clients available in the platform.",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
outputSchema: {
type: "object",
properties: {
clients: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "string" },
portfolioId: { type: "string" },
firstName: { type: "string" },
lastName: { type: "string" },
segment: { type: "string" },
advisor: { type: "string" },
riskProfile: { type: "string" },
},
required: ["id", "portfolioId", "firstName", "lastName", "segment", "advisor", "riskProfile"],
},
},
},
required: ["clients"],
},
},
interop: {
methodName: GET_CLIENTS_METHOD,
},
},
],
},
},
},
});
export const startMCPWebServer = async (io: IOConnectAPI): Promise<void> => {
const hasMCPWebServer = io.interop.methods().some((method) => method.name === MCP_SERVER_METHOD_NAME);
if (hasMCPWebServer) {
console.info("MCP Web server is already available.");
return;
}
const { ServerFactory } = await import("@interopio/mcp-web");
await ServerFactory(io, getMCPWebServerConfig());
console.log("MCP Web server started in io.Assist");
};
The server configuration is the same MCP Web configuration you used in the platform earlier. The difference is where it runs.
The ServerFactory import is dynamic on purpose. MCP Web is a large dependency, and loading it dynamically keeps the Angular initial bundle under the existing guide budget while still starting the server during assistant initialization.
Now open:
apps/io-assist-angular/src/app/app.config.ts
Import startMCPWebServer:
import { startMCPWebServer } from "./mcp";
Create a small wrapper around the io.Connect Browser factory:
const createIOConnect: typeof IOBrowser = async (config) => {
const io = await IOBrowser(config);
await startMCPWebServer(io);
return io;
};
Finally, pass that wrapper to io.Assist:
connectConfig: {
browser: {
factory: createIOConnect,
config: {
libraries: [IOWorkspaces],
modals: {
dialogs: {
enabled: true,
},
},
},
},
},
Now the Angular assistant starts io.Connect Browser, starts MCP Web, and then lets io.Assist initialize AI Web against the available MCP Web server.
Add MCP Web To The React Assistant
Create:
apps/io-assist-react/src/mcp.ts
Add the same server configuration using the React app's formatting:
import IOBrowser from '@interopio/browser'
import type { IoIntelMCPWeb } from '@interopio/mcp-web'
type IOConnectAPI = Awaited<ReturnType<typeof IOBrowser>>
const MCP_SERVER_METHOD_NAME = 'io.mcp.web.server'
const GET_CLIENTS_METHOD = 'getClients'
const GET_CLIENTS_TOOL = 'get_clients'
const getMCPWebServerConfig = (): IoIntelMCPWeb.Server.Config => ({
licenseKey: import.meta.env.VITE_IO_INTELLIGENCE_LICENSE_KEY,
mcpCoreServer: {
tools: {
static: {
methods: [
{
availability: 'constant',
name: GET_CLIENTS_TOOL,
config: {
title: 'Get Clients',
description: 'Returns the list of ACME Banking clients available in the platform.',
inputSchema: {
type: 'object',
properties: {},
additionalProperties: false,
},
outputSchema: {
type: 'object',
properties: {
clients: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
portfolioId: { type: 'string' },
firstName: { type: 'string' },
lastName: { type: 'string' },
segment: { type: 'string' },
advisor: { type: 'string' },
riskProfile: { type: 'string' },
},
required: [
'id',
'portfolioId',
'firstName',
'lastName',
'segment',
'advisor',
'riskProfile',
],
},
},
},
required: ['clients'],
},
},
interop: {
methodName: GET_CLIENTS_METHOD,
},
},
],
},
},
},
})
export const startMCPWebServer = async (io: IOConnectAPI): Promise<void> => {
const hasMCPWebServer = io.interop.methods().some((method) => method.name === MCP_SERVER_METHOD_NAME)
if (hasMCPWebServer) {
console.info('MCP Web server is already available.')
return
}
const { ServerFactory } = await import('@interopio/mcp-web')
await ServerFactory(io, getMCPWebServerConfig())
console.log('MCP Web server started in io.Assist')
}
Open:
apps/io-assist-react/src/App.tsx
Import startMCPWebServer:
import { startMCPWebServer } from './mcp'
Create the io.Connect Browser factory wrapper:
const createIOConnect: typeof IOBrowser = async (config) => {
const io = await IOBrowser(config)
await startMCPWebServer(io)
return io
}
Then update the React assistant config:
const staticConfig: IoAssistStaticConfig = {
connectConfig: {
browser: {
factory: createIOConnect,
config: {
libraries: [IOWorkspaces],
modals: {
dialogs: {
enabled: true,
},
},
},
},
},
// existing io.Assist config...
}
The React app now follows the same architecture as Angular.
Test The Same User Flow
Start the agent backend:
cd agentic-backend
npm start
In another terminal, start the browser platform and apps:
cd io-assist-anywhere-start
npm start
Open the platform:
http://localhost:4200
Before opening io.Assist, you can verify that the platform no longer hosts MCP Web by running this in the platform console:
window.io.interop.methods().some((method) => method.name === "io.mcp.web.server")
The result should be:
false
Now open either assistant app from the platform. After the assistant loads, run:
window.io.interop
.methods()
.find((method) => method.name === "io.mcp.web.server")
?.getServers()
.map((server) => server.applicationName)
If you opened the Angular assistant first, the result should include:
["io-assist-angular"]
If you opened the React assistant first, the result should include:
["io-assist-react"]
If both assistants are open at the same time, the first one opened hosts MCP Web and the second one reuses the already available server.
Now repeat the Chapter 7 prompt:
show me the portfolio details for Amelia Reed. But before opening any workspace, I would like to get a preview
The visible result should be exactly the same as before. The assistant should discover Amelia Reed, find the client-management workspace, and show the Workspace Widget preview. You can still click Restore Workspace from the preview to open the workspace with the correct context.
The important difference is architectural: the request is no longer handled by an MCP Web server running as a browser platform plugin. MCP Web is now started from the io.Assist app itself.
What You Have Built
The assistant now owns the complete AI integration layer:
- io.Assist initializes io.Connect Browser
- the assistant starts MCP Web
- AI Web connects to that MCP Web server
- MCP Web exposes the same ACME Banking tools and built-in system tools
- MCP Apps still render through the Workspace Widget
You have simplified the environment without changing the user experience. From the advisor's perspective, the assistant behaves the same. From the developer and deployment perspective, there is one fewer external component to configure.