@mcp-b/webmcp-ts-sdk adapts the official @modelcontextprotocol/sdk for browser environments. It provides BrowserMcpServer, a class that extends the upstream McpServer to support dynamic tool registration after transport connection.
npm install @mcp-b/webmcp-ts-sdk
Most applications should use @mcp-b/global instead of this package directly. @mcp-b/global creates and manages a BrowserMcpServer internally. Use this package only when you need direct control over the MCP server instance.
Minimal example
Why this package exists
The official MCP SDK throws an error when registering server capabilities after a transport connection:
// Official SDK behavior:
server.registerCapabilities(capabilities);
// Error: "Cannot register capabilities after connecting to transport"
In browser environments, tools arrive dynamically as pages load. The transport must be connected at startup, but tools are registered later via navigator.modelContext.registerTool().
BrowserMcpServer solves this by pre-registering tool capabilities in the constructor, before the transport connects.
BrowserMcpServer
Constructor
new BrowserMcpServer(serverInfo: Implementation, options?: BrowserMcpServerOptions)
| Parameter | Type | Description |
|---|
serverInfo | Implementation | Server name and version. { name: string; version: string } |
options | BrowserMcpServerOptions | Server configuration |
BrowserMcpServerOptions
Extends ServerOptions from the upstream SDK.
| Property | Type | Description |
|---|
native | ModelContextCore? | Reference to the native/polyfill modelContext. When provided, core tool operations (registerTool, unregisterTool) are mirrored to it. |
capabilities | ServerCapabilities? | Additional MCP capabilities. Tools, resources, and prompts with listChanged: true are pre-registered. |
jsonSchemaValidator | JsonSchemaValidator? | Custom JSON Schema validator. Defaults to PolyfillJsonSchemaValidator. |
import { BrowserMcpServer } from '@mcp-b/webmcp-ts-sdk';
const server = new BrowserMcpServer(
{ name: 'my-app', version: '1.0.0' },
{ native: navigator.modelContext }
);
WebMCP standard methods
These methods implement the navigator.modelContext API. When a native context is provided, operations mirror to it.
Registers a single tool. Returns { unregister: () => void }.
const handle = server.registerTool({
name: 'search',
description: 'Search products',
inputSchema: {
type: 'object',
properties: { query: { type: 'string' } },
required: ['query'],
},
async execute(args) {
return { content: [{ type: 'text', text: `Found: ${args.query}` }] };
},
});
// Later:
handle.unregister();
- Mirrors to
native.registerTool() first (if native is provided).
- Rolls back the native registration if the server-side registration fails.
- Streamed tools (
stream: true) are not yet supported and throw an error.
Removes a tool by name. Also calls native.unregisterTool(name) when native is provided.
Extension methods
These methods are not part of the WebMCP specification. They are MCP-B extensions.
Returns ToolListItem[] for all enabled registered tools.
const tools = server.listTools();
// [{ name: 'search', description: '...', inputSchema: {...} }]
Executes a registered tool by name. Returns Promise<ToolResponse>.
const result = await server.callTool({
name: 'search',
arguments: { query: 'laptop' },
});
Convenience wrapper for callTool. Returns Promise<ToolResponse>.
const result = await server.executeTool('search', { query: 'laptop' });
Resource methods
registerResource(descriptor)
Registers an MCP resource. Returns { unregister: () => void }.
const handle = server.registerResource({
uri: 'app://settings',
name: 'App Settings',
description: 'Current application settings',
mimeType: 'application/json',
async read(uri) {
return { contents: [{ uri: uri.toString(), text: JSON.stringify(settings) }] };
},
});
| Property | Type | Required |
|---|
uri | string | Yes |
name | string | Yes |
description | string | No |
mimeType | string | No |
read | (uri: URL) => Promise<{ contents: ResourceContents[] }> | Yes |
listResources()
Returns metadata for all enabled resources.
readResource(uri)
Reads a resource by URI. Throws if the resource is not found.
Prompt methods
registerPrompt(descriptor)
Registers an MCP prompt. Returns { unregister: () => void }.
const handle = server.registerPrompt({
name: 'summarize',
description: 'Summarize a document',
argsSchema: {
type: 'object',
properties: { url: { type: 'string', description: 'Document URL' } },
required: ['url'],
},
async get(args) {
return {
messages: [{ role: 'user', content: { type: 'text', text: `Summarize: ${args.url}` } }],
};
},
});
| Property | Type | Required |
|---|
name | string | Yes |
description | string | No |
argsSchema | InputSchema | No |
get | (args) => Promise<{ messages: PromptMessage[] }> | Yes |
listPrompts()
Returns metadata for all enabled prompts, including argument schemas.
getPrompt(name, args?)
Retrieves a prompt by name. Validates args against the schema if present.
Sampling and elicitation
createMessage(params, options?)
Requests LLM sampling from the connected client. Returns Promise<CreateMessageResult>.
const response = await server.createMessage({
messages: [{ role: 'user', content: { type: 'text', text: 'Hello' } }],
maxTokens: 100,
});
Default timeout: 10 seconds (via AbortSignal.timeout).
Requests user input from the connected client. Returns Promise<ElicitResult>.
const result = await server.elicitInput({
message: 'Please confirm the order',
requestedSchema: {
type: 'object',
properties: { confirm: { type: 'boolean' } },
required: ['confirm'],
},
});
Transport connection
connect(transport)
Connects to an MCP transport. Sets up request handlers before connection to avoid the “cannot register after connect” restriction.
import { TabServerTransport } from '@mcp-b/transports';
const transport = new TabServerTransport({ allowedOrigins: ['*'] });
await server.connect(transport);
The connect method:
- Sets up tool, resource, and prompt request handlers.
- Replaces upstream handlers that expect Zod schemas with handlers that support plain JSON Schema.
- Calls
super.connect(transport) to establish the connection.
Schema handling
BrowserMcpServer accepts both Zod schemas and plain JSON Schema objects. The handling depends on the schema format:
| Schema Format | Validation Method |
|---|
Zod schema (_zod or _def present) | safeParseAsync from the upstream SDK |
| Plain JSON Schema | PolyfillJsonSchemaValidator (built-in) |
Input schemas without a type field receive type: "object" automatically. Empty {} schemas default to { type: "object", properties: {} }.
Registers tools that were already present on the native/polyfill context before this server was created. Skips tools that are already registered on the server.
const synced = server.backfillTools(existingTools, async (name, args) => {
return await existingContext.callTool({ name, arguments: args });
});
Returns the number of tools synced.
Convenience method that calls backfillTools using the native context’s listTools() and callTool().
Re-exports
This package re-exports types, classes, and utilities from @modelcontextprotocol/sdk:
- All MCP protocol types (
Tool, Resource, Prompt, etc.)
Server (base server class, unchanged)
McpServer aliased to BrowserMcpServer
Transport interface
mergeCapabilities helper
- Protocol version constants
- Request/response schemas