Skip to main content
Glama
README.md
# Teams Copilot MCP

A standalone MCP server that talks to Teams Copilot over direct SignalR WebSockets. It launches no browser and has no Playwright dependency. Conversation state persists between calls and server restarts.

Requires Python 3.11+ on macOS or Linux. This implements the ChatHub protocol observed in the working Teams capture; it is an unofficial integration and Microsoft can change that protocol. A browser is needed for manual authentication setup and token replacement, but not to run the connector. Automatic token renewal is not implemented.

## Install

Copy this entire `copilot-mcp` directory to the destination machine, then run:

```sh
cd /absolute/path/to/copilot-mcp
python3 -m venv .venv
.venv/bin/python -m pip install .
```

Alternatively, with uv installed: `uv sync --no-dev`. The runtime dependencies are `mcp` and `websockets` and their dependencies. Neither installation requires Chrome.

## Initialize before calling MCP tools

Initialization saves your ChatHub endpoint, access token, and request template. A token alone does not supply the account-specific endpoint and protocol options.

**If you already have `teams-network.jsonl` from this connector's capture command**, import it directly:

```sh
.venv/bin/teams-copilot-mcp --profile .copilot-profile import-capture /absolute/path/to/teams-network.jsonl
.venv/bin/teams-copilot-mcp --profile .copilot-profile status
```

The importer chooses a connection with a successful, completed chat invocation and starts a fresh conversation. This expects the connector's JSONL format, not a HAR export. An expired token can be replaced using the instructions below.

### Get the chat access token and initialize manually

1. In your regular browser, sign in to Teams and open its Copilot app. The observed personal-account setup redirects to `teams.live.com` and embeds Copilot from `outlook.office.com`.
2. Open Developer Tools → **Network** before sending a harmless test prompt. Select the **WS** filter. If the connection predates opening DevTools, reload Teams with DevTools open and reopen Copilot.
3. Send the test prompt and wait for its completed response. Select the WebSocket request whose URL starts with `wss://substrate.office.com/m365Copilot/Chathub/`.
4. Under **Headers**, copy the full **Request URL**. Its `access_token` query parameter is the chat access token. Keep the whole URL for initialization; the tool extracts and URL-decodes the token. A cookie header is not a replacement for this token.
5. Under **Messages**, copy the outgoing JSON object with `"type":4`, `"target":"chat"`, and an `"arguments"` array. Copy the entire object, not only its `message` field. The handshake and type-6 ping messages are not chat requests.
6. Save that outgoing JSON as `private/request.json` in the project folder. Prepare the private folder first:

```sh
mkdir -p private
chmod 700 private
```

Run initialization, then paste the copied Request URL at the hidden prompt:

```sh
.venv/bin/teams-copilot-mcp --profile .copilot-profile init --request-file private/request.json
```

Alternatively, save the full URL as `private/chat-url.txt` and run:

```sh
chmod 600 private/chat-url.txt private/request.json
.venv/bin/teams-copilot-mcp --profile .copilot-profile init --request-file private/request.json --url-file private/chat-url.txt
```

An optional `--user-agent 'VALUE'` preserves the User-Agent from the captured request headers. Importing a JSONL capture preserves it automatically.

The profile directory is private (0700); its JSON files are private (0600). The copied URL and profile credentials grant access to your chat account: keep them out of source control and MCP configuration. Initialization blanks the captured prompt text; the template can still contain account metadata or references, so use a plain test prompt without attachments. Remove temporary setup files after successful initialization.

Verify initialization before starting your MCP client:

```sh
.venv/bin/teams-copilot-mcp --profile .copilot-profile status
.venv/bin/teams-copilot-mcp --profile .copilot-profile ask 'Reply with exactly: connector works'
```

`status` reads local state without contacting Microsoft. `ask` sends a real prompt and verifies protocol completion. Initialization refuses to overwrite an existing profile; use another profile path for a different account.

## Configure your MCP client

Merge this entry into your client's MCP server configuration. Replace both paths with absolute paths on the machine running the server. See also `mcp.example.json`.

```json
{
  "mcpServers": {
    "teams-copilot": {
      "command": "/ABSOLUTE/PATH/copilot-mcp/.venv/bin/teams-copilot-mcp",
      "args": ["--profile", "/ABSOLUTE/PATH/copilot-mcp/.copilot-profile", "serve"]
    }
  }
}
```

Restart or reconnect your MCP client after saving its configuration. The client starts the stdio server; you do not need to run a separate background process. Running `serve` manually waits for an MCP client on stdin. stdout is reserved for MCP protocol messages.

| Tool | Arguments | Behavior |
| --- | --- | --- |
| `copilot_ask` | `{"prompt":"Your question"}` | Sends one prompt in the saved conversation and returns completed text plus request/conversation IDs. |
| `copilot_status` | `{}` | Returns local state, turn count, and whether a previous send is uncertain. |
| `copilot_new_chat` | `{}` | Starts a fresh local conversation for subsequent prompts; previous service chats remain. |

Use one profile per independent conversation. Calls on one connector serialize; separate processes attempting to use the same profile concurrently receive an error. The default response timeout is 120 seconds; configure a longer timeout with `--timeout 240` before `serve` if needed, and ensure your MCP client's timeout is at least as long.

## Replace an expired access token

Repeat the DevTools steps above after signing in or reloading Copilot, and capture a fresh ChatHub Request URL after a successful test response. Save it as `private/chat-url.txt`, then:

```sh
.venv/bin/teams-copilot-mcp --profile .copilot-profile set-token --from-url private/chat-url.txt
```

This extracts the new token and checks that the URL has the same host and account-specific ChatHub path as the initialized profile. It preserves the conversation and takes effect on the next request, without restarting MCP. For a different endpoint/account, initialize a new profile from the new URL and request message.

If you already have the raw, URL-decoded token in `private/access-token.txt`:

```sh
.venv/bin/teams-copilot-mcp --profile .copilot-profile set-token private/access-token.txt
```

The file must contain only the token, without a `Bearer ` prefix. `COPILOT_ACCESS_TOKEN` can override the saved token; unset an old override when using `set-token`. Do not place token values directly in shell commands or shared configuration. Token validity is determined by Microsoft; the opaque captured token does not provide a locally readable expiry.

## Errors and recovery

- Authentication rejection: replace the token. There is no automatic login, browser fallback, or token refresh.
- Uncertain previous send: the request may already have reached Microsoft. Do not automatically repeat it. Inspect the conversation in Teams before choosing to start a new chat with `copilot_new_chat` or the CLI `new-chat` command. That resets local state; it does not cancel or delete the earlier request.
- “Service communication is currently unavailable”: check whether Copilot itself works in Teams. Reimporting credentials does not repair a Microsoft service outage.
- No successful completed invocation in a capture: collect a new capture that includes a successful response and its completion frame.

## Build and test

```sh
uv sync --extra dev
uv run pytest
uv build
```

The tests use a local WebSocket service and a real MCP stdio subprocess; they do not send prompts to Microsoft. Wheels and source archives are written to `dist/`. Build inputs explicitly exclude profiles, captures, and private setup files. Install a wheel elsewhere with `python -m pip install /path/to/teams_copilot_mcp-0.1.0-py3-none-any.whl`, then initialize a profile and configure the installed executable as above.

TDQS

A3.6/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a distinct purpose: copilot_ask sends a prompt, copilot_status reads conversation state, and copilot_new_chat resets the conversation. There is no overlap in action or resource, so an agent can select correctly without ambiguity.

Naming Consistency5/5

All names use a consistent copilot_ prefix followed by a short action identifier in snake_case (copilot_ask, copilot_status, copilot_new_chat). The pattern is predictable and uniform across the entire set.

Tool Count4/5

Three tools is a reasonable minimal surface for a focused Copilot conversation client, and each earns its place. However, it sits at the lower end and leaves little room for secondary operations, making it slightly under-scoped rather than optimally sized.

Completeness3/5

The core lifecycle of starting a chat, asking a question, and checking status is covered, but there is no way to retrieve prior conversation transcripts, list retained chats, cancel an in-flight request, or delete conversations despite the note that prior chats are retained. These are notable gaps for a conversational service.

Maintenance

ActivityMaintained
ResponsivenessNo issues