> ## Documentation Index
> Fetch the complete documentation index at: https://docs.armature.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Stateless & serverless servers

> Keep sessions intact when every request hits a fresh instance

Serverless MCP servers (Vercel, Lambda, Cloud Run scaled to zero) handle `initialize` and each tool call on **different instances**. Two things break without help: session identity (calls stop lining up into one session) and delivery (the platform freezes your process before background events are sent).

Both fixes are one-liners.

## 1. Use `await` delivery

Background delivery relies on the process staying alive after the response. In serverless, send events before returning instead:

<CodeGroup>
  ```typescript TypeScript theme={null}
  createMcpAnalyticsServer(buildServer, {
    armature: { delivery: "await" },
  });
  ```

  ```python Python theme={null}
  instrument_fastmcp(mcp, {"armature": {"delivery": "await"}})
  ```

  ```go Go theme={null}
  config.Delivery = armatureanalytics.DeliveryAwait
  ```
</CodeGroup>

## 2. Resolve the session per request

The SDK mints **identity-bearing session IDs** (`mcp_<client>_v_<version>_<uuid>`) on `initialize` and reads them back on later requests, so client attribution survives across instances.

<Tabs>
  <Tab title="TypeScript">
    Call `resolveStatelessHttpSession` per request and pass the results to the transport and dispatch context:

    ```typescript theme={null}
    import { resolveStatelessHttpSession } from "@armature-tech/mcp-analytics";
    import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

    const session = resolveStatelessHttpSession({
      body: requestBody,
      headers: requestHeaders,
    });

    const transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: session.sessionIdGenerator,
      enableJsonResponse: true,
    });

    // with a custom dispatcher:
    await analytics.dispatch(name, args, { ...context, ...session.dispatchContext });
    ```
  </Tab>

  <Tab title="Python">
    Wrap your ASGI app in the dependency-free middleware — it handles minting and echoing automatically:

    ```python theme={null}
    from armature_mcp_analytics import StatelessHttpSessionMiddleware

    # standalone FastMCP
    app = StatelessHttpSessionMiddleware(
        mcp.http_app(stateless_http=True, json_response=True)
    )

    # official MCP Python SDK
    # mcp = FastMCP("Customer MCP", stateless_http=True, json_response=True)
    # app = StatelessHttpSessionMiddleware(mcp.streamable_http_app())
    ```
  </Tab>

  <Tab title="Go">
    Resolve the session per request and hand it to the server options:

    ```go theme={null}
    session := armatureanalytics.ResolveStatelessHTTPSession(armatureanalytics.StatelessHTTPInput{
    	Body:    body,
    	Headers: r.Header,
    })

    // official go-sdk
    s, shutdown := official.NewMCPServer(impl, &mcp.ServerOptions{
    	GetSessionID: session.SessionIDGenerator(),
    })
    // serve with &mcp.StreamableHTTPOptions{Stateless: true, JSONResponse: true}

    // mark3labs/mcp-go
    srv := server.NewMCPServer(name, version,
    	server.WithSessionIdManager(session.Mark3labsSessionIDManager()),
    )
    ```
  </Tab>
</Tabs>

<Note>
  Session IDs are attribution metadata, not a security boundary — they are echoed by clients unsigned. Keep authorization on your own auth layer.
</Note>
