registerTool adds an action the model can call to your server, optionally rendering its result through a view .
Example
search-products searches the store catalog for the signed-in user and renders the matches in the carousel view.
import { McpServer } from "skybridge/server" ;
import { z } from "zod" ;
const server = new McpServer ({ name: "shop" , version: "1.0" }). registerTool (
{
name: "search-products" ,
title: "Search products" ,
description: "Search the store catalog by keyword and optional price ceiling." ,
inputSchema: {
query: z . string (). describe ( "What the shopper is looking for" ),
},
outputSchema: {
productIds: z . array ( z . string ()). describe ( "The IDs of products matching the query." ),
},
annotations: { readOnlyHint: true },
securitySchemes: [{ type: "oauth2" , scopes: [ "catalog.read" ] }],
view: {
component: "carousel" ,
csp: { connectDomains: [ "https://api.myshop.com" ] },
},
},
async ({ query , maxPrice }, { authInfo }) => {
const userId = authInfo . extra ?. sub as string ;
const products = await search ( query , { maxPrice , userId });
return {
content: `Found ${ products . length } products for " ${ query } ".` ,
structuredContent: { products },
};
},
);
Signature
server . registerTool ( config : ToolConfig , handler : ToolHandler ): McpServer ;
Config
The first argument declares the tool.
type ToolConfig = {
name : string ;
title ?: string ; // human-readable label
description ?: string ; // model-facing: decides when the model calls the tool
inputSchema ?: ZodRawShape ; // Zod shape; validates the call and types the handler input
outputSchema ?: ZodRawShape ; // Zod shape; types structuredContent
annotations ?: ToolAnnotations ; // standard MCP hints
view ?: ViewConfig ; // bind a view to render the result
securitySchemes ?: SecurityScheme []; // declare per-tool auth
_meta ?: ToolMeta ; // extra metadata
};
name, title, description
All three are the tool’s prompt surface: the model reads them to decide when to call it, so write each for the model. name is also the identifier the call uses, and title a short display name.
Zod raw shapes. inputSchema validates the call arguments and types the handler’s input. Add .describe() to a field to tell the model what it’s for. outputSchema tells the model the shape to expect back, it does not type the returned value.
annotations
Standard MCP hints describing what the tool does, so the host can label it and order calls. They are hints: the host may surface or ignore them, and never gates a call on them.
type ToolAnnotations = {
title ?: string ;
readOnlyHint ?: boolean ;
destructiveHint ?: boolean ;
idempotentHint ?: boolean ;
openWorldHint ?: boolean ;
};
Hint Meaning Default titleHuman-readable label; the host gives it precedence over name. none readOnlyHintThe tool does not modify its environment. falsedestructiveHintThe tool may perform destructive updates, not only additive ones. Meaningful only when readOnlyHint is false. trueidempotentHintRepeating the call with the same arguments has no further effect. Meaningful only when readOnlyHint is false. falseopenWorldHintThe tool interacts with an open world of external entities, not a closed domain. true
view
Bind the tool to a React view to render its result instead of plain text. Each view backs exactly one tool.
type ViewConfig = {
component : ViewName ;
description ?: string ;
hosts ?: Array < "apps-sdk" | "mcp-app" >;
prefersBorder ?: boolean ;
domain ?: string ;
csp ?: ViewCsp ;
_meta ?: Record < string , unknown >;
};
Key Purpose componentThe view’s file name, type-checked against your views. descriptionLabel the host may show during view discovery. hostsRestrict where the view renders; defaults to all. prefersBorderApps SDK only: request a visible border around the view. domainApps SDK only: override the served domain (advanced). cspPer-view CSP overrides; see below. _metaFree-form metadata forwarded on the view resource.
csp
A view runs in a sandboxed iframe; your server’s domain is allowlisted automatically. Add external origins per directive:
type ViewCsp = {
resourceDomains ?: string [];
connectDomains ?: string [];
frameDomains ?: string [];
redirectDomains ?: string [];
baseUriDomains ?: string [];
};
Directive Purpose resourceDomainsStatic assets: images, fonts, scripts, styles. connectDomainsFetch / XHR targets. frameDomainsIframe embed origins (opts into stricter app review). redirectDomainsopenExternal targets that skip the safe-link modal.baseUriDomains<base href> origins (MCP Apps only).
securitySchemes
Declare which auth a tool supports so clients can label it public vs. requires-sign-in before invocation, and request the right scopes during sign-in.
type SecurityScheme =
| { type : "noauth" }
| { type : "oauth2" ; scopes ?: string [] };
Across the array, match any. Each entry is an alternative.
Within an oauth2 entry’s scopes, match all. The token must carry every listed scope.
noauth and oauth2 together means “works anonymously, but auth unlocks more.”
securitySchemes is client-facing metadata, not self-enforcing. Pair it with an auth middleware (requireBearerAuth or optionalBearerAuth ) to authenticate requests, and enforce the tool’s own requirements in the handler from extra.authInfo.
Metadata on the tool. Skybridge recognizes the keys below; any other key you set is forwarded on the tool’s _meta untouched.
type ToolMeta = {
ui ?: { visibility ?: Array < "model" | "app" > };
"openai/toolInvocation/invoking" ?: string ;
"openai/toolInvocation/invoked" ?: string ;
"openai/fileParams" ?: string [];
[ key : string ] : unknown ;
};
Key Purpose ui.visibilityExpose the tool to the model, the app, or both. openai/toolInvocation/invoking, invokedStatus text shown while the tool runs and after it completes. openai/fileParamsTop-level input fields that carry file references; see FileRef . openai/widgetAccessibleDeprecated, use ui.visibility.
The openai/* keys are read only on ChatGPT . openai/widgetAccessibleis deprecated, prefer ui.visibility instead.
Handler
Runs when the tool is called. It receives the validated input (typed from inputSchema ) and the request context extra:
type ToolHandler = ( input : Input , extra : RequestHandlerExtra ) => Promise <{
content ?: string | ContentBlock | ContentBlock [];
structuredContent ?: Record < string , unknown >;
isError ?: boolean ;
_meta ?: Record < string , unknown >;
}>;
The request context.
Field Purpose authInfoValidated token from an auth middleware: clientId, scopes, extra. requestInfoThe HTTP request: headers and URL. signalAn AbortSignal, aborted when the call is cancelled. _metaClient hints (below).
Return
Field Purpose contentText for the model, shown in the conversation. A string, a ContentBlock, or an array. structuredContentTyped data the model and the view read (via useToolInfo ). isErrorMarks the call as failed. _metaView-only response metadata, hidden from the model.
For a view tool, Skybridge adds a viewUUID to _meta, a reserved key used for state persistence.
Client hints
ChatGPT attaches conversation context to extra._meta. They are hints: tolerate their absence and never use them for authorization.
type ClientHintsMeta = {
"openai/locale" ?: string ;
"openai/userAgent" ?: string ;
"openai/userLocation" ?: {
city ?: string ;
region ?: string ;
country ?: string ;
timezone ?: string ;
longitude ?: number ;
latitude ?: number ;
};
"openai/subject" ?: string ;
"openai/session" ?: string ;
"openai/organization" ?: string ;
"openai/widgetSessionId" ?: string ;
};
Key Purpose openai/localeRequested locale, BCP-47, e.g. en-US. openai/userAgentBrowser user-agent of the ChatGPT client. openai/userLocationCoarse location: city, region, country, timezone, latitude, longitude (each optional). openai/subjectAnonymized user id. openai/sessionAnonymized conversation id, stable within a session. openai/organizationAnonymized organization id, when the user account is in an organization. openai/widgetSessionIdStable id for the mounted view instance.
These hints are ChatGPT only.
Content helpers
skybridge/server exports helpers that build ContentBlocks for the handler’s content:
import { audio , embeddedResource , image , resourceLink , text } from "skybridge/server" ;
return {
content: [ text ( "Here's your chart:" ), image ( pngBuffer , "image/png" )],
structuredContent: { /* ... */ },
};
Helper Block text(value, annotations?)TextContentimage(data, mimeType, annotations?)ImageContent (base64)audio(data, mimeType, annotations?)AudioContent (base64)embeddedResource(resource, annotations?)EmbeddedResourceresourceLink(link, annotations?)ResourceLink
Returning plain ContentBlock objects works too; the helpers are optional.
McpServer The server you register tools on
useToolInfo Read the tool’s result in the view
FileRef Pass files in and out of a tool