Skip to main content

Use skybridge/server

This guide shows you how to add skybridge/server to an existing MCP server to enable widget support with full TypeScript type inference.

Prerequisites

You should already have:

Install Skybridge

pnpm add skybridge

Update your server

Replace your McpServer import with Skybridge’s enhanced version:
// Before
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

// After
import { McpServer } from "skybridge/server";
Keep your existing dependency to @modelcontextprotocol/sdk in your project, skybridge does not replaces it, but depends on it. Your existing tools, resources, and prompts will continue to work as before. With skybridge/server, you will also be able to register widgets, a new class encompassing a Tool and a corresponding UI Resource.

Widget structure

Skybridge follows a file-based convention for organizing widgets. Here’s the recommended project structure:
your-project/
├── server/
│   └── src/
│       └── index.ts          # Your MCP server with widget registration
└── web/
    └── src/
        └── widgets/
            ├── my-widget.tsx  # Widget component
            └── other-widget.tsx

Widget naming convention

Widget file name must match the registration name (e.g., "my-widget"my-widget.tsx). See registerWidget.

Set up the web project

Create a web folder for your UI widgets:
mkdir web && cd web
pnpm init
pnpm add react react-dom skybridge
pnpm add -D vite typescript @types/react @types/react-dom

Configure Vite

Create vite.config.ts in the web folder:
import { defineConfig } from "vite";
import { skybridge } from "skybridge/web";

export default defineConfig({
  plugins: [skybridge()],
});

Create your first widget

Create a widget file in web/src/widgets/my-widget.tsx:
import { mountWidget } from "skybridge/web";

const MyWidget: React.FC = () => {
  return (
    <div>
      <h2>Hello from Skybridge!</h2>
      <p>Your widget is working!</p>
    </div>
  );
};

mountWidget(<MyWidget />);

Register the widget

In your server code, register the widget using server.registerWidget():
import { McpServer } from "skybridge/server";
import { z } from "zod";

const server = new McpServer({ name: "my-app", version: "1.0" }, {});

server.registerWidget(
  "my-widget",  // Must match the filename: my-widget.tsx
  {},
  {
    description: "My first widget",
    inputSchema: {
      message: z.string(),
    },
  },
  async ({ message }) => {
    // Your widget logic here
    return {
      content: [{
        type: "text",
        text: message || "Hello World"
      }]
    };
  }
);

Configure your dev server

Update your server startup to serve the MCP endpoint. If you’re using Express:
import { McpServer } from "skybridge/server";
import { z } from "zod";
import express from "express";

const app = express();
const server = new McpServer({ name: "my-app", version: "1.0" }, {});

// Register your widgets
server.registerWidget("my-widget", {}, {
  inputSchema: { message: z.string() },
}, async ({ message }) => {
  return {
    content: [{ type: "text", text: message }]
  };
});

// Serve the MCP endpoint
app.use("/mcp", server.handler());

// Start the server
app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});

Type Safety

For full type inference and autocomplete, use method chaining and generateHelpers. See Type Safety for the complete setup. Quick summary:
  1. Use method chaining when registering widgets
  2. Export type AppType = typeof server
  3. Create a skybridge.ts file with generateHelpers<AppType>()
  4. Import typed hooks from your helper file