Skip to main content

Self-Managing Your Server

By default, app.start() spins up an HTTP server, registers the Teams endpoint, and manages the full lifecycle for you. Under the hood, the SDK uses FastAPI as its built-in HTTP framework. But if you need to self-manage your server — because you have an existing app, need custom server configuration (TLS, workers, middleware), or use a different HTTP framework — the SDK supports that through the HttpServerAdapter interface.

How It Works​

The SDK splits HTTP handling into two layers:

  • HttpServer handles Teams protocol concerns: JWT authentication, activity parsing, and routing to your handlers.
  • HttpServerAdapter handles framework concerns: translating between your HTTP framework's request/response model and the SDK's pure handler pattern.

The adapter interface is intentionally simple — implement registerRoute and the SDK handles the rest.

The Adapter Interface​

class HttpServerAdapter(Protocol):
def register_route(self, method: HttpMethod, path: str, handler: HttpRouteHandler) -> None: ...
def serve_static(self, path: str, directory: str) -> None: ...
async def start(self, port: int) -> None: ...
async def stop(self) -> None: ...

class HttpRouteHandler(Protocol):
async def __call__(self, request: HttpRequest) -> HttpResponse: ...
  • registerRoute — Required. Routes are registered dynamically (/api/messages, /api/functions/{name}, etc.).
  • serveStatic — Optional. Only needed for tabs or static pages.
  • start / stop — Optional. Omit when you manage the server lifecycle yourself.

Self-Managing Your Server​

To add Teams to an existing server:

  1. Create your server with your own routes and middleware.
  2. Wrap it in an adapter (or use the built-in one with your server instance).
  3. Call app.initialize() — this registers the Teams routes on your server. Do not call app.start().
  4. Start the server yourself.
import asyncio
import uvicorn
from fastapi import FastAPI
from microsoft_teams.apps import App, FastAPIAdapter

# 1. Create your FastAPI app with your own routes
my_fastapi = FastAPI(title="My App + Teams Bot")

@my_fastapi.get("/health")
async def health():
return {"status": "healthy"}

# 2. Wrap it in the FastAPIAdapter
adapter = FastAPIAdapter(app=my_fastapi)

# 3. Create the Teams app with the adapter
app = App(http_server_adapter=adapter)

@app.on_message
async def handle_message(ctx):
await ctx.send(f"Echo: {ctx.activity.text}")

async def main():
# 4. Initialize — registers /api/messages on your FastAPI app (does NOT start a server)
await app.initialize()

# 5. Start the server yourself
config = uvicorn.Config(app=my_fastapi, host="0.0.0.0", port=3978)
server = uvicorn.Server(config)
await server.serve()

asyncio.run(main())

See the full example: FastAPI non-managed example

Using a Different Framework​

If you use a framework other than the built-in default, implement the adapter interface for your framework. The core work is in registerRoute — translate incoming requests to { body, headers }, call the handler, and write the response back. Since you manage the server lifecycle yourself, start/stop aren't needed. And serveStatic is only required if you serve tabs or static pages.

Here is a Starlette adapter — only register_route is needed:

from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import Route
from microsoft_teams.apps.http.adapter import HttpMethod, HttpRequest, HttpResponse, HttpRouteHandler

class StarletteAdapter:
def __init__(self, app: Starlette):
self._app = app

def register_route(self, method: HttpMethod, path: str, handler: HttpRouteHandler) -> None:
# Teams only sends POST requests to your bot endpoint
async def starlette_handler(request: Request) -> Response:
body = await request.json()
headers = dict(request.headers)
result: HttpResponse = await handler(HttpRequest(body=body, headers=headers))
if result.get("body") is not None:
return JSONResponse(content=result["body"], status_code=result["status"])
return Response(status_code=result["status"])

route = Route(path, starlette_handler, methods=[method])
self._app.routes.insert(0, route)

Usage:

starlette_app = Starlette()
adapter = StarletteAdapter(starlette_app)
app = App(http_server_adapter=adapter)
await app.initialize()
# Start Starlette with uvicorn yourself

See the full implementation: Starlette adapter example