Skip to content

POC MCP App#322

Draft
domarmstrong wants to merge 6 commits intomainfrom
dom/basic-app
Draft

POC MCP App#322
domarmstrong wants to merge 6 commits intomainfrom
dom/basic-app

Conversation

@domarmstrong
Copy link
Contributor

Goal

Investigate MCP Apps and how they could be integrated

Design

Rather than the examples of MCP apps which have an html entry for each tool and then bundle it's resources in to a single file. I wanted to proof out being able to set up an easier to expand approach which requires less set up for each tool.

  • Make it easy to add ui to each client
  • Make it easy to add new tools

Example MCP app

  • Starts when the bugsnag list-projects tool is called
  • Shows an interactive list of project names
  • Clicking a project will perform a further tool call to get the errors for the project

Example when run in vs code

I found running the app in vs code painful as it seems to need completely quitting to update the mcp server.

Screen.Recording.2026-02-10.at.16.48.07.mov

Example when server is run with UI_DEV

Hot reloading works completely making development not a complete nighmare

Screen.Recording.2026-02-10.at.16.52.31.mov

Changeset

This is a POC and not expected to merge in the current state.

  • Issues around correct handling of CORs. Currently had to allow origin * to work with the basic-host for testing. So that needs more investigation
  • explicit localhost urls should be probably be handled better
  • Static /asset serving is rough

Testing

Tested via vscode copilot + mcp basic-host example.

);
}

interface ErrorResult {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tool results should probably all be exported in some common types for sharing

function Router({ toolId, data }: { toolId: string; data: CallToolResult }) {
switch (toolId) {
case "list-projects":
return <ListProjects data={data} />;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a handling for a new tool would just be a case of adding a new case with a lazy import.

],
_meta: {
ui: {
resourceUri: this.createAppUri("list-projects"),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding a new tool call is just a case of adding the correct meta to another tool. The app will then be passed the toolId and data to handle.

if (client.registerResources) {
client.registerResources((name, path, cb) => {
const url = `${client.toolPrefix}://${name}/${path}`;
const url = typeof path === 'string' ? `${client.toolPrefix}://${name}/${path}` : path.uri;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the default uri construction doesn't work for mcp apps. the uri schema must be ui://.... so I'm allowing passing a complete uri to override it here.

}

// Serve static assets from dist/assets/
if (req.method === "GET" && url.pathname.startsWith("/assets/")) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basic static file serving. probably not the best solution

*
* A meta tag with the tool id is expected mcp-tool-id.
*/
export function createMcpApp(config: McpAppConfig) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide an easy setup for a react app. If you follow the conventions just create an html file and call this in the entry. The root component will then get the toolId and the tool result.

createMcpApp({ name, version, RootComponent })


describe("API methods", async () => {
beforeEach(async () => {
client = await createConfiguredClient("test-token", "test-project-key");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this setup is currently bad. if you don't run all tests then later tests will fail because they are relying on setup from previous tests to exist and not have been cleaned up.

process.exit(1);
}

if (process.env.UI_DEV) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run the vite dev server as a sub process to make life easier

outDir: resolve(__dirname, "dist"),
emptyOutDir: false,
rollupOptions: {
input: [resolve(__dirname, "src/bugsnag/ui/app.html")],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding new apps, new entries would need adding here

order: "post",
handler(html: string, context) {
const path = dirname(context.path);
return html
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't very robust but works fine if entries follow the convention of the simple app.html

@@ -0,0 +1,26 @@
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { lazy } from "react";
import "./ListProjects.css";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import "./ListProjects.css";

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant