hyperserve

module
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2026 License: MIT

README

HyperServe

CI Latest release Go reference

HyperServe began as the Go API server I wanted to own. The name was inspired by hyperHTML; the design came from the same preference for a small, understandable core.

It keeps ordinary net/http handlers and collects the operational pieces that accumulate around them: lifecycle, typed request binding, security middleware, observability, WebSockets, and optional Model Context Protocol (MCP). Routes still use http.ServeMux patterns, and handlers remain http.Handler values.

The runtime module has one external dependency. Its WebSocket, JSON-RPC, and MCP implementations live in-tree. That means fewer packages for users to assemble, but more protocol code maintained here.

Use HyperServe when you want that integrated server boundary without rebuilding it for each API. If routes plus JSON are all your service needs, use net/http directly.

Install

HyperServe requires Go 1.27.

go get github.com/osauer/hyperserve@latest

golang.org/x/time provides rate limiting. Build and conformance tools live in a separate tools/go.mod module.

Start a server

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/osauer/hyperserve/pkg/server"
)

func main() {
    srv, err := server.NewServer()
    if err != nil {
        log.Fatal(err)
    }

    srv.GET("/", func(w http.ResponseWriter, _ *http.Request) {
        fmt.Fprintln(w, "Hello, World!")
    })

    // Run owns process signals and drains active requests during shutdown.
    if err := srv.Run(); err != nil {
        log.Fatal(err)
    }
}

Save the example as main.go in your module, then start it:

go run .

From another terminal:

curl http://localhost:8080/

NewServer listens on :8080 and installs request logging, metrics, and panic recovery. Run handles SIGINT, SIGTERM, and SIGQUIT. If the application already owns its lifecycle, use RunContext(appCtx) instead; context cancellation is a normal request to drain and stop.

Add WithHealthServer() for separate liveness and readiness endpoints, or WithDeferredInit(...) when readiness must wait for startup work. The deferred-init example shows both.

Own the configuration boundary

NewServer() uses deterministic defaults; it does not read options.json or the process environment. Bind only the authorities your application intends to accept, in precedence order:

srv, err := server.NewServer(
    server.WithConfigFile(configPath), // Required, application-chosen JSON file.
    server.WithEnvironment(),          // Opt into supported deployment variables.
    server.WithAddr("127.0.0.1:8080"),  // Later options win; keep this invariant fixed.
)

HS_ is simply the HyperServe namespace for its environment variables, such as HS_RATE_LIMIT; those variables have no effect without WithEnvironment(). Use DefaultServerOptions, modify the returned value, and pass it through WithOptions when an embedding application wants to bind one complete, reviewed snapshot. The configuration example shows the precedence rules.

HyperServe also avoids process branding by default: the ASCII banner is off and HeadersMiddleware omits Server. Use WithStartupBanner() or WithServerHeader("my-service") to opt in. These identification settings do not install security policy; attach SecureWeb or HeadersMiddleware to the routes that need security headers.

Bind and validate input

JSONHandler keeps decoding, validation, and safe error responses at the HTTP boundary while the callback works with Go values:

type CreateUser struct {
    Email string `json:"email" validate:"required,email"`
}

type User struct {
    Email string `json:"email"`
}

srv.POST("/users", server.JSONHandler(
    func(_ context.Context, in CreateUser) (User, error) {
        return User{Email: strings.ToLower(in.Email)}, nil
    },
))

Malformed or invalid input produces a structured 400; unexpected callback errors produce a generic 500 without exposing error details. Use BindJSON, BindQuery, BindForm, and Validate directly when the response needs custom headers, streaming, or a different envelope. See the binding example for both levels.

Use WebSockets on either side

The websocket package implements RFC 6455 for servers and outbound clients. Server upgrades default to same-origin browser requests and both sides enforce a 1 MiB message limit unless configured otherwise.

Use srv.WebSocketUpgrader() when the upgrade should appear in server metrics:

upgrader := srv.WebSocketUpgrader()
upgrader.MaxMessageSize = 512 << 10

srv.GET("/ws", func(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        return
    }
    defer conn.Close()

    messageType, payload, err := conn.Read(r.Context())
    if err != nil {
        return
    }
    if err := conn.Write(r.Context(), messageType, payload); err != nil {
        return
    }
})

For outbound connections, websocket.Dial accepts a context plus either a caller-owned http.Client or explicit dial/TLS settings. It supports headers, subprotocols, TLS verification, and bounded redirects; reconnect policy remains with the application. See the WebSocket guide and the browser echo example.

Add MCP when the service needs it

MCP is opt-in and does not change the HTTP or WebSocket APIs:

srv, err := server.NewServer(
    server.WithMCPSupport("payments", "1.0.0"),
)

The standard endpoint implements MCP 2026-07-28 Streamable HTTP: finite requests return JSON, while subscriptions/listen uses request-scoped SSE. Initialize-era 2025-11-25 request/response remains available for older clients. HyperServe's proprietary routed-SSE transport is deprecated and disabled by default.

Applications must put their own authorization middleware in front of /mcp. Built-in tools and resources are also disabled by default. The MCP guide documents transport headers, subscriptions, limits, discovery, built-ins, and the legacy migration path.

Packages

Import path Purpose
github.com/osauer/hyperserve/pkg/server HTTP server, middleware, lifecycle, and MCP wiring
github.com/osauer/hyperserve/pkg/websocket WebSocket upgrader, connection, and outbound dialer
github.com/osauer/hyperserve/pkg/mcp MCP handler, transports, discovery, and namespaces
github.com/osauer/hyperserve/pkg/mcp/builtin Opt-in demonstration tools and resources
github.com/osauer/hyperserve/pkg/jsonrpc Standalone JSON-RPC 2.0 engine

The four top-level package APIs follow semantic versioning on the v1 module line. Generated layouts, examples, and commands are maintained and tested but are not stable import surfaces. See API stability for the compatibility and deprecation policy.

Generate a service

hyperserve-init creates a Go module, a server entry point, and tests:

go install github.com/osauer/hyperserve/cmd/hyperserve-init@latest
hyperserve-init --module github.com/acme/payments
cd payments
go run ./cmd/server

The generated project omits MCP because the initializer cannot choose an application's authorization policy. Pass --with-mcp only when the service will protect /mcp itself. --local-replace points a generated project at a local HyperServe checkout.

Documentation and development

Repository checks:

make check
make test-race
make fuzz-smoke

make check runs vet, Staticcheck, vulnerability scans, Go modernization, example builds, and MCP conformance checks. Bugs and usage questions belong in GitHub Issues.

MIT — see LICENSE.

Directories

Path Synopsis
cmd
hyperserve-init command
examples
best-practices command
Package main demonstrates best practices for using serverpkg.
Package main demonstrates best practices for using serverpkg.
binding command
Example: request binding + validation, three ways.
Example: request binding + validation, three ways.
complete command
configuration command
deferred-init command
Deferred-init example.
Deferred-init example.
devops command
Example demonstrating DevOps features: debug logging and MCP resources
Example demonstrating DevOps features: debug logging and MCP resources
enterprise command
Enterprise example demonstrating FIPS 140-3 compliance and enhanced security features
Enterprise example demonstrating FIPS 140-3 compliance and enhanced security features
hello-world command
htmx-dynamic command
htmx-stream command
json-api command
mcp-basic command
Smallest MCP-enabled HyperServe binary: built-in MCP tools/resources, a custom tool, a custom resource, and a sandboxed file-tool root.
Smallest MCP-enabled HyperServe binary: built-in MCP tools/resources, a custom tool, a custom resource, and a sandboxed file-tool root.
mcp-cli command
Example: Using command-line flags to configure MCP
Example: Using command-line flags to configure MCP
mcp-discovery command
mcp-extensions command
Example: typed MCP tools — one tool per verb.
Example: typed MCP tools — one tool per verb.
mcp-sse command
Example: legacy HyperServe routed SSE.
Example: legacy HyperServe routed SSE.
mcp-stdio command
Package main demonstrates hyperserve's MCP support as a stdio server for Claude Desktop.
Package main demonstrates hyperserve's MCP support as a stdio server for Claude Desktop.
static-files command
web-worker-csp command
websocket-demo command
go module
internal
validate
Package validate implements the tag-driven struct validator used by pkg/server (HTTP request binding) and pkg/mcp (typed-tool argument binding).
Package validate implements the tag-driven struct validator used by pkg/server (HTTP request binding) and pkg/mcp (typed-tool argument binding).
pkg
mcp
Typed MCP tools.
Typed MCP tools.
mcp/builtin
Package builtin provides ready-to-register MCP tools and resources.
Package builtin provides ready-to-register MCP tools and resources.
server
Request binding + struct-tag validation.
Request binding + struct-tag validation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL