Host a compact MCP server¶
Start with an explicitly authorized registry from the registry tutorial. Import the optional adapter:
Create one server per hosting lifetime:
host, err := server.New(registry, server.WithIdentity("my-tool", "0.1.0"))
if err != nil {
return err
}
return host.RunStdio(ctx)
Keep application logs on stderr. A connected client sees three fixed tools: search, inspect and invoke. It can invoke a known operation without first searching.
For an HTTP service, mount host.Handler() on the application's existing mux at
an explicitly selected path. Authenticate the request before calling the handler,
placing the current principal in the context used by your policies. The handler
starts no listener. Attach resources with server.WithResources(resources) when
needed; their catalogue uses its own explicit policy.
Register host.Shutdown with the owning controls lifecycle and expose host.Status
in health aggregation. Shutdown cancels calls and waits within its budget. Keep a
shutdown failure visible until the underlying application work has stopped.
To expose individual native tools, add server.WithMode(server.Direct) at
construction. Choose separate server instances/endpoints if you need both views.
Direct lists remain caller-authorized; selecting the mode grants no extra access.
Both views support resources. Modern HTTP and stdio clients can use the form/URL interactions
from the interaction tutorial. The adapter derives supported
modes from each request and passes verified resumed answers back to your handler.
Report active work with invocation.Progress(ctx, update); Clients supplying
a bounded progress token receive advisory notifications while that call runs.
Legacy stdio clients use an explicit answer loop; June clients receive simpler forms and an application-interface fallback for URL/multiselect requests. Read the server reference before integrating a consumer.
Attach an application shell¶
For a host with embedded views, load your static HTML and enable Apps explicitly:
host, err := server.New(registry, server.WithApps(server.Apps{
URI: "ui://my-tool/shell",
HTML: shellHTML,
Views: []string{"gallery", "player", "editor"},
CSP: server.AppCSP{
ResourceDomains: []string{"https://media.example.com"},
ConnectDomains: []string{"https://studio.example.com"},
},
}))
if err != nil {
return err
}
Supply a valid HTML5 document in shellHTML; keep it public and static. This URI
belongs exclusively to the shell, so choose a different URI for application
resources. The shell should handle ordinary text results and failures as well as
its configured views. Leave permissions and external domains empty unless needed.
An authorized operation can select a view without importing the server or SDK:
presentation, err := mcp.NewPresentation(mcp.PresentationSpec{
View: "gallery",
Data: json.RawMessage(`{"project":"demo","revision":"9007199254740993"}`),
})
if err != nil {
return mcp.Outcome{}, err
}
result, err := mcp.NewResult(
[]mcp.Content{mcp.Text("Candidate previews are available in Studio.")},
mcp.WithPresentation(presentation),
)
return mcp.Finish(result), err
The UI calls the same call_tool dispatcher for its actions. Keep project/revision,
confirmation and idempotency checks in the operations; a view identifier conveys
no authority. Publish useful text, structured data and authorized links for clients
without Apps. Test launch, actions, refresh and fallback in each supported host
before treating the integration as accepted.