tinymcp defaults to stdio (Start()), which local clients (Cursor, Claude Desktop) expect. For remote clients, gateways, or browser-based tools, use streamable HTTP or legacy SSE via thin helpers on the official go-sdk.
| Transport | tinymcp API | Best for |
|---|---|---|
| stdio | Start() |
Local AI clients, subprocess MCP |
| Streamable HTTP | StartHTTP / StreamableHTTPHandler |
Current MCP spec; remote clients, Glama-style gateways, POST + SSE |
| Legacy SSE | StartSSE / SSEHandler |
Older clients on MCP spec 2024-11-05 |
Prefer streamable HTTP for new remote deployments unless a client explicitly requires legacy SSE.
server := tinymcp.NewServer("my-mcp", "1.0.0")
if err := tinymcp.RegisterTool(server, "ping", "Echo a message", pingHandler); err != nil {
log.Fatal(err)
}
// Blocks; bind to loopback for local-only access. Add TLS or a reverse proxy in production.
log.Fatal(server.StartHTTP("127.0.0.1:8080", &tinymcp.HTTPOptions{
Stateless: true, // simple demos; omit for full sessions + server-initiated messages
}))Or mount the handler on an existing http.Server (auth, CORS, paths):
opts := (&tinymcp.HTTPOptions{Stateless: true}).WithMiddleware(
requestLogger, // your func(http.Handler) http.Handler
)
handler, err := tinymcp.StreamableHTTPHandler(server, opts)
if err != nil {
log.Fatal(err)
}
handler = tinymcp.WithCrossOriginProtection(handler)
handler = tinymcp.BearerTokenAuth(os.Getenv("MCP_API_KEY"), handler)
mux := http.NewServeMux()
mux.Handle("/mcp", handler)
log.Fatal(tinymcp.ListenAndServeHTTP("127.0.0.1:8080", mux))ListenAndServeHTTP drains active connections on SIGINT or SIGTERM (PaaS deploys). For custom lifecycle control:
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := tinymcp.ListenAndServeHTTPContext(ctx, addr, mux); err != nil && !errors.Is(err, context.Canceled) {
log.Fatal(err)
}Runnable example: examples/http.
| Field | Effect |
|---|---|
Stateless |
No session ID; one-shot requests (no server→client RPC) |
JSONResponse |
POST replies as application/json instead of SSE |
SessionTimeout |
Close idle sessions after duration |
DisableLocalhostProtection |
Disable DNS rebinding guard (use with care) |
Middleware |
Wrap MCP handler (first = innermost); chain with WithMiddleware |
Recommended wrapping (inner → outer):
StreamableHTTPHandler(MCP core)HTTPOptions.Middleware— logging, rate limits, metricsWithCrossOriginProtection— browser clientsBearerTokenAuth— optional API key
See examples/http-deploy for a full stack.
Point an MCP client that supports streamable HTTP at your server URL (often the root path or /mcp). See your client’s docs for url / transport: http config.
Host streamable HTTP on a public HTTPS URL and publish to Smithery so clients connect via the Smithery Gateway — end users do not run Docker or a local binary.
Deploy template: examples/http-deploy (includes /health and /.well-known/mcp/server-card.json). Full steps: docs/SMITHERY.md.
log.Fatal(server.StartSSE(":8080", nil))Clients use SSEClientTransport with your server’s SSE endpoint URL. Prefer streamable HTTP for new work.
- Bind to
127.0.0.1:8080when only local access is needed (TINY_GO_MCP_ADDR=0.0.0.0:8080to listen on all interfaces). - For local ngrok/tunnel testing on loopback, set
TINY_GO_MCP_DISABLE_LOCALHOST_PROTECTION=1(tinymcp.DisableLocalhostProtectionFromEnv()inexamples/httpandexamples/http-deploy). Seedocs/LOCALHOST-PROTECTION.md. - Put TLS in front of public endpoints — see
docs/TLS.md. Authentication and rate limiting: reverse proxy or middleware. Helpers:tinymcp.BearerTokenAuth,tinymcp.WithCrossOriginProtection(used inexamples/http-deploy). - Do not set
DisableLocalhostProtectionunless you understand MCP security guidance. StartHTTPandListenAndServeHTTPsetReadHeaderTimeout(10s),ReadTimeout(30s), andIdleTimeout(120s) on the underlyinghttp.Server, and drain on SIGINT/SIGTERM.WriteTimeoutis intentionally unset so streamable HTTP/SSE responses can be long-lived.
For custom session routing, event stores, or advanced streamable options, use server.RawServer() with mcp.NewStreamableHTTPHandler directly — same underlying SDK, full control.