Documentation
¶
Overview ¶
Package webmcp is a server-side toolkit for WebMCP, the W3C Community Group proposal that lets a web page register tools an in-browser AI agent can call through document.modelContext (registerTool, getTools, executeTool).
WebMCP is not the server-to-server Model Context Protocol (MCP). An MCP server is called by desktop or CLI agents over stdio or HTTP; WebMCP tools live in a browser tab and run with the signed-in user's session. This package covers the server half of WebMCP for net/http applications, using the standard library only:
- NewTool validates a tool definition at boot (name 1–128 of [A-Za-z0-9_.-], WebMCP annotations read_only/untrusted_content/consequential/debugging, a strict JSON Schema subset, a same-origin endpoint, GET only when read_only).
- Registry.Manifest selects the tools a page exposes; Manifest.ScriptTag renders them as a script-safe <script type="application/json"> element.
- RuntimeHandler serves the shared browser runtime (embedded, zero dependencies). It registers the manifest's tools and, when an agent calls one, calls the tool's endpoint with the user's session cookies and CSRF token, without following redirects and without retries.
- FormAttrs and ParamAttr render the declarative form attributes (toolname, tooldescription, toolautosubmit, toolparamdescription).
- OriginTrial and OriginTrialMetaTag deliver the Chrome origin trial token.
Your endpoints keep authentication, authorization, CSRF checks and input validation; annotations are hints, not security controls.
The same manifest format and runtime ship in the Ruby (reference), Django and Rust packages: https://github.com/seunghan91/webmcp. Spec baseline: WebMCP Draft CG Report 2026-10-02.
Example ¶
Define a read-only tool, select it for a page, and render the manifest. The page also needs RuntimeScriptTag pointing at RuntimeHandler.
package main
import (
"encoding/json"
"fmt"
webmcp "github.com/seunghan91/webmcp-go"
)
func main() {
tool, err := webmcp.NewTool(webmcp.Def{
Name: "list_tasks",
Description: "List at most 20 tasks for the signed-in user.",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"completed": map[string]any{"type": "boolean"},
},
},
Annotations: webmcp.Annotations{"read_only": true, "untrusted_content": true},
Endpoint: webmcp.Endpoint{Path: "/api/tasks", Method: "GET"},
})
if err != nil {
panic(err)
}
registry, err := webmcp.NewRegistry(webmcp.Transport{
CSRF: &webmcp.CSRF{Source: "meta", Name: "csrf-token", Header: "X-CSRF-Token"},
})
if err != nil {
panic(err)
}
if err := registry.Register(tool); err != nil {
panic(err)
}
registry.Freeze()
manifest, err := registry.Manifest("list_tasks")
if err != nil {
panic(err)
}
var parsed struct {
Tools []struct {
Name string `json:"name"`
Annotations map[string]bool `json:"annotations"`
Endpoint struct{ Path, Method string }
} `json:"tools"`
}
raw, _ := json.Marshal(manifest)
_ = json.Unmarshal(raw, &parsed)
first := parsed.Tools[0]
fmt.Println(first.Name, first.Endpoint.Method, first.Endpoint.Path, first.Annotations)
}
Output: list_tasks GET /api/tasks map[readOnlyHint:true untrustedContentHint:true]
Index ¶
- Constants
- func FormAttrs(tool, description string, autosubmit bool) (template.HTMLAttr, error)
- func OriginTrial(token string) func(http.Handler) http.Handler
- func OriginTrialMetaTag(token string) template.HTML
- func OriginTrialWithOptions(token string, opts OriginTrialOptions) func(http.Handler) http.Handler
- func ParamAttr(description string) template.HTMLAttr
- func RuntimeHandler() http.Handler
- func RuntimeScriptTag(src, nonce string) template.HTML
- type Annotations
- type CSRF
- type Def
- type Endpoint
- type Manifest
- type OriginTrialOptions
- type Registry
- type ScriptTagOptions
- type Tool
- type Transport
Examples ¶
Constants ¶
const OriginTrialHeader = "Origin-Trial"
OriginTrialHeader is the origin trial response header name.
const Version = "0.2.2"
Version is this package's release version.
Variables ¶
This section is empty.
Functions ¶
func FormAttrs ¶ added in v0.2.0
FormAttrs emits only the declarative form attributes defined by WebMCP. Descriptions must be trusted application metadata, not visitor-controlled text.
func OriginTrial ¶
OriginTrial fills a missing Origin-Trial header when the response is written. It preserves existing headers (even empty ones) including downstream values. An empty token is a pass-through.
func OriginTrialMetaTag ¶ added in v0.2.0
OriginTrialMetaTag renders the token, or nothing for an empty token.
func OriginTrialWithOptions ¶ added in v0.2.0
OriginTrialWithOptions adds an opt-in, once-per-handler OAC warning. It never rewrites Origin-Agent-Cluster or forces ?1. Empty tokens remain pass-through.
func RuntimeHandler ¶ added in v0.2.0
RuntimeHandler serves the pinned browser runtime, with a strong SHA-256 ETag. Register it at the URL passed to RuntimeScriptTag.
func RuntimeScriptTag ¶ added in v0.2.0
RuntimeScriptTag loads an external runtime module from the application's URL. src and nonce are escaped independently, including values cast from safe HTML.
Types ¶
type Annotations ¶ added in v0.2.0
Annotations accepts only read_only, untrusted_content, consequential, debugging. False hints are omitted from manifests. Hints are not security controls.
type CSRF ¶ added in v0.2.0
type CSRF struct {
Source string `json:"source"`
Name string `json:"name"`
Header string `json:"header"`
}
CSRF locates a fresh token at invocation time. Source is meta or cookie.
type Def ¶ added in v0.2.0
type Def struct {
Name string `json:"name"`
Description string `json:"description"`
Title *string `json:"title,omitempty"`
InputSchema map[string]any `json:"input_schema"`
Endpoint Endpoint `json:"endpoint"`
Annotations Annotations `json:"annotations,omitempty"`
MaxResponseChars *int `json:"max_response_chars,omitempty"`
Logger *slog.Logger `json:"-"` // nil uses slog.Default for budget warnings.
}
Def defines the browser surface explicitly. Optional pointers distinguish absence from an empty title or an invalid zero response limit.
type Endpoint ¶ added in v0.2.0
type Endpoint struct {
Path string `json:"path"`
Method string `json:"method"`
ParamMap map[string]string `json:"param_map,omitempty"`
ArrayFormat string `json:"array_format,omitempty"`
}
Endpoint describes an existing same-origin application route.
type Manifest ¶ added in v0.2.0
type Manifest struct {
WebMCPManifestVersion int `json:"webmcpManifestVersion"`
Transport map[string]any `json:"transport"`
Tools []map[string]any `json:"tools"`
}
Manifest is a manifest v1 snapshot. Marshal it with encoding/json or render it with ScriptTag. Mutating a snapshot never changes the registry or tools.
type OriginTrialOptions ¶ added in v0.2.0
type OriginTrialOptions struct {
WarnOnOACOptOut bool
Logger *slog.Logger // nil uses slog.Default when warnings are enabled.
}
OriginTrialOptions enables diagnostics for older Origin-Trial builds.
type Registry ¶ added in v0.2.0
type Registry struct {
// contains filtered or unexported fields
}
Registry stores validated tools. Its zero value uses empty transport. Register at boot; Manifest only exposes explicitly requested names. A Registry must not be copied after first use.
func NewRegistry ¶ added in v0.2.0
NewRegistry validates and snapshots the transport configuration.
func (*Registry) Freeze ¶ added in v0.2.0
func (r *Registry) Freeze()
Freeze prevents further registration after application initialization.
type ScriptTagOptions ¶ added in v0.2.0
ScriptTagOptions controls CSP and page startup. Autostart nil defaults to true; a pointer to false omits data-webmcp-autostart.
type Tool ¶ added in v0.2.0
type Tool struct {
// contains filtered or unexported fields
}
Tool is a validated, private snapshot; changing its Def cannot change it. Construct tools with NewTool; the zero value is not valid.
func NewTool ¶ added in v0.2.0
NewTool validates a definition at boot and makes a deeply copied snapshot.
Example (GetRequiresReadOnly) ¶
A GET endpoint must be read-only; NewTool rejects the definition at boot.
package main
import (
"fmt"
webmcp "github.com/seunghan91/webmcp-go"
)
func main() {
_, err := webmcp.NewTool(webmcp.Def{
Name: "delete_task",
Description: "Delete one task.",
InputSchema: map[string]any{"type": "object"},
Endpoint: webmcp.Endpoint{Path: "/api/tasks/delete", Method: "GET"},
})
fmt.Println(err != nil)
}
Output: true