Runtimes

Go Runtime

The Go runtime is the most complete implementation of the AppTheory contract and ships with the broadest middleware and CDK surface. It is a reference implementation, not the source of truth — 271 single-envelope contract fixtures run in Go, TypeScript, and Python, while the remaining two route-algebra and facade-inventory vectors are loaded by Go and CDK-TS tests only.

Install

The Go toolchain resolves modules from the immutable git tag — no registry is involved beyond Go’s standard proxy.```bash go get github.com/theory-cloud/apptheory/v4@v4.2.1


The command is stamped from the latest published release at Pages build time (pages.yml resolves it with
`gh release view`), so the module path and version always match a release that actually exists. Pin a specific release
tag from the [releases page](https://github.com/theory-cloud/AppTheory/releases) when you need a different line.
AppTheory does not publish to the npm or PyPI registries; the Go module is the only language artifact that ships through
Go's normal toolchain path.

Module layout (see `api-snapshots/go.txt` for the exact exported surface):

| Package | Purpose |
| --- | --- |
| `github.com/theory-cloud/apptheory/v4/runtime` | Core runtime: `apptheory.New`, `Context`, `Request`, `Response`, route registration, middleware. |
| `github.com/theory-cloud/apptheory/v4/runtime/mcp` | MCP Streamable HTTP transport, sessions, resumable SSE. |
| `github.com/theory-cloud/apptheory/v4/runtime/mcproutes` | Versioned MCP endpoint and OAuth route algebra. |
| `github.com/theory-cloud/apptheory/v4/runtime/mcpfacade` | Golden-path MCP/OAuth facade registration and metadata composition. |
| `github.com/theory-cloud/apptheory/v4/runtime/oauth` | OAuth protected-resource metadata, PKCE, DCR, token-store helpers. |
| `github.com/theory-cloud/apptheory/v4/testkit` | Deterministic test environment (clock, ID queue, event builders). |
| `github.com/theory-cloud/apptheory/v4/testkit/mcp` | In-process MCP client for unit tests. |
| `github.com/theory-cloud/apptheory/v4/pkg/limited` | DynamoDB-backed cross-instance rate limiter. |
| `github.com/theory-cloud/apptheory/v4/pkg/jobs` | Jobs-ledger primitives. |
| `github.com/theory-cloud/apptheory/v4/pkg/sanitization` | Safe logging helpers. |

## Minimal app

```go
package main

import (
    "context"
    "encoding/json"

    "github.com/aws/aws-lambda-go/lambda"
    apptheory "github.com/theory-cloud/apptheory/v4/runtime"
)

func main() {
    app := apptheory.New()

    app.Get("/ping", func(ctx *apptheory.Context) (*apptheory.Response, error) {
        return apptheory.Text(200, "pong"), nil
    })

    lambda.Start(func(ctx context.Context, event json.RawMessage) (any, error) {
        return app.HandleLambda(ctx, event)
    })
}

HandleLambda is the only entrypoint you need for any AWS trigger. It detects the event shape and dispatches to the right adapter — see Event Shape Dispatch for the full table.

Tier selection

The default tier is P2. To opt down:

app := apptheory.New(apptheory.WithTier(apptheory.TierP0))

See HTTP Runtime for what each tier includes.

Deterministic tests

The Go testkit fixes time, request IDs, and AWS event shapes so handler tests do not depend on AWS:

func TestHello(t *testing.T) {
    env := testkit.NewWithTime(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
    env.IDs.Queue("req-1")

    app := env.App()
    app.Get("/hello", func(ctx *apptheory.Context) (*apptheory.Response, error) {
        return apptheory.MustJSON(200, map[string]any{
            "now_unix": ctx.Now().Unix(),
            "id":       ctx.NewID(),
        }), nil
    })

    event := testkit.APIGatewayV2Request("GET", "/hello", testkit.HTTPEventOptions{
        Headers: map[string]string{"x-request-id": "request-1"},
    })
    resp := env.InvokeAPIGatewayV2(context.Background(), app, event)

    if resp.StatusCode != 200 {
        t.Fatalf("expected 200, got %d", resp.StatusCode)
    }
}

env.IDs.Queue(...) pre-fills the ID generator so any ctx.NewID() call returns the queued value in order — handler tests stay deterministic across rerolls.

Route registration

Fluent route registration fails closed: invalid patterns, duplicate method/pattern pairs, and nil handlers panic during registration instead of silently producing a dead route. Strict helpers remain deprecated compatibility wrappers when a caller still needs an error-returning shape, and their errors now use canonical AppTheoryError messages where applicable:

if _, err := app.GetStrict("/users/{id}", h); err != nil {
    t.Fatal(err)
}

TypeScript and Python follow the same fail-closed registration contract.

For the parameterized four-kind MCP OAuth surface, use mcpfacade.RegisterMCPFacade rather than copying route patterns. FacadeConfig requires per-kind scopes, an application-owned MCP handler, and one explicit absolute-URL mode. URLModePublicBaseURL is for front-door/CDN deployments; URLModeRequestHost derives scheme and host from each request for direct API Gateway custom domains and tests. Optional HandlerFactory plug points attach app-owned authorize/token behavior as a pair. See Go MCP Facade Helper.

HTTP error format

Default HTTP error envelopes are nested under error. AppTheoryError is the canonical client-safe error type for new code; AppError remains supported for code/message compatibility. Any HTTP error whose code string is EMPTY_BODY or INVALID_JSON is remapped by the default nested envelope to app.bad_request. To match Lift’s flat shape and preserve those legacy codes/messages:

app := apptheory.New(apptheory.WithHTTPErrorFormat(apptheory.HTTPErrorFormatFlatLegacy))
// or
app := apptheory.New(apptheory.WithLegacyHTTPErrorShape())

This setting applies to HTTP only. AppSync and WebSocket error payloads keep their existing shapes regardless.

MCP runtime

import (
    "context"
    "encoding/json"

    apptheory "github.com/theory-cloud/apptheory/v4/runtime"
    "github.com/theory-cloud/apptheory/v4/runtime/mcp"
)

srv := mcp.NewServer("example", "1.0.0")

_ = srv.Registry().RegisterTool(mcp.ToolDef{
    Name:        "echo",
    Description: "Echo back the provided message.",
    InputSchema: json.RawMessage(`{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}`),
}, func(ctx context.Context, args json.RawMessage) (*mcp.ToolResult, error) {
    var in struct{ Message string `json:"message"` }
    if err := json.Unmarshal(args, &in); err != nil {
        return nil, err
    }
    content := []mcp.ContentBlock{
        {Type: "text", Text: in.Message},
    }
    return &mcp.ToolResult{Content: content}, nil
})

app := apptheory.New()
h := srv.Handler()
app.Post("/mcp", h)
app.Get("/mcp", h)
app.Delete("/mcp", h)

See the MCP Method Surface for the full Streamable HTTP contract, and Remote MCP for OAuth-protected deployments.

What’s verified

The Go runtime passes the 271 generic runner fixtures and directly consumes the MCP route-algebra and facade-inventory tables, covering all 273 contract vectors on every commit. Any behavioral divergence on a shared surface is treated as a contract bug — fix the implementation, or update the fixture and prove the change holds across every participating runtime.

Next reads