Skip to main content
The fastest way to understand the bridge is to run its example project - a minimal, working embedding you can copy straight into your own repo. Budget about 10 minutes once the Teams setup is done.

examples/basic-bridge

loadConfig() + startServer() in a few lines, with a custom vision hook (your own model answers the agent’s look tool) and a custom lookup_order function tool the bridge executes. Env-file config and graceful shutdown are built in.

What you need first

  • Node.js >= 20.
  • An OpenAI API key with Realtime access (platform.openai.com). No agent to create - the bridge configures each gpt-realtime session itself.
  • A StandIn identity with its shared secret (from pairing or the dashboard). The sandbox works too if you have no Teams bot yet.

1. Clone and install

git clone https://github.com/komaa-com/openai-msteams-bridge
cd openai-msteams-bridge/examples/basic-bridge
npm install

2. Configure the environment

The example reads the same env file as the CLI - copy the fully commented template from the repo root:
cp ../../.env.example .env
Open .env and fill in the two required values:
VariableWhat to put there
OPENAI_API_KEYYour OpenAI API key (server-side only; never sent to the Teams side).
WORKER_SHARED_SECRETThe shared secret from StandIn pairing - both sides must match exactly.
Worth setting while you are there: OPENAI_VOICE (e.g. marin), OPENAI_INSTRUCTIONS (the agent’s personality), and OPENAI_FIRST_MESSAGE (a deterministic greeting / AI disclosure). The example still boots with dummy values, so you can check the wiring before the credentials are real. Everything else (PORT, MAX_CALL_MINUTES, goodbye behavior, VAD mode) has sensible defaults - see the configuration reference.

3. Start it

npm start
The bridge prints the WebSocket URL to give StandIn:
Point your StandIn identity's agent WebSocket URL at ws://<this-host>:8080/voice/msteams/stream

4. Expose port 8080

StandIn connects from the internet, so the port needs a public wss:// URL. Any tunnel works:
tailscale funnel --bg --https=8080 8080
Your URL: wss://<machine>.<tailnet>.ts.net:8080/voice/msteams/stream

5. Connect it to StandIn and call

  1. In your StandIn dashboard, set the identity’s Agent voice URL to the wss:// URL from step 4.
  2. Make sure the identity’s shared secret equals WORKER_SHARED_SECRET.
  3. Place a Teams call to your bot (or join the sandbox meeting). StandIn joins, connects to the bridge, and your gpt-realtime agent answers.

The vision hook

The example ships a custom VisionDescriber in index.mjs. When your agent calls its built-in look tool, the bridge hands your function the current camera or screen-share frame and returns your text description to the agent - the raw frame never leaves your process. The example answers with gpt-4o-mini; swap in any vision-capable model. If you prefer configuration over code, the VISION_API_URL / VISION_API_KEY / VISION_MODEL variables do the same against any OpenAI-compatible endpoint - see Vision.

The custom tool

The example also registers a lookup_order function tool:
const tools = [{
  name: "lookup_order",
  description: "Look up the status of a customer order by its order number.",
  parameters: {
    type: "object",
    properties: { orderNumber: { type: "string", description: "The order number, e.g. KO-1234." } },
    required: ["orderNumber"],
  },
  async handler({ orderNumber }, ctx) {
    // call your own backend here; the returned string goes to the agent
    return `Order ${orderNumber} shipped yesterday and arrives tomorrow.`;
  },
}];

startServer(cfg, undefined, describeFrame, { handleSignals: true, tools });
Ask the agent on a call “where is order KO-12?” and it calls your handler, then speaks the answer. This is the pattern for wiring the agent to your own systems (CRM lookups, transfers, bookings); remote MCP servers are the config-only alternative - see Extending the agent’s tools.

From example to your own project

Depend on the published package instead of the local checkout:
npm install @komaa/openai-msteams-bridge
Then copy the example’s index.mjs as your starting point. The library API documents the programmatic surface (config, server options, tools, hooks).

If something does not work

  • WebSocket rejected with 401 - WORKER_SHARED_SECRET does not match the secret in StandIn.
  • Bot joins the call but stays silent - the Agent voice URL is unreachable from the internet or points at the wrong port; re-check the tunnel.
  • Call connects, then ends with agent-unavailable - the OpenAI key is invalid, lacks Realtime access, or OPENAI_REALTIME_MODEL names a model your account cannot use; the log line carries the underlying error.
  • More: Troubleshooting.

Example README

Configuration

Library API