mcp-teams 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -2
- package/dist/bridge.js +485 -0
- package/dist/bridge.js.map +1 -0
- package/dist/clients.js +97 -0
- package/dist/clients.js.map +1 -0
- package/dist/config.js +28 -0
- package/dist/config.js.map +1 -0
- package/dist/extension.js +61 -0
- package/dist/extension.js.map +1 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/dist/setup.js +232 -0
- package/dist/setup.js.map +1 -0
- package/dist/tools.js +199 -0
- package/dist/tools.js.map +1 -0
- package/extension/background.js +955 -0
- package/extension/confirm.html +32 -0
- package/extension/confirm.js +54 -0
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-32.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/icons/icon.svg +14 -0
- package/extension/manifest.json +50 -0
- package/extension/popup.html +41 -0
- package/extension/popup.js +33 -0
- package/extension/teams.js +835 -0
- package/package.json +44 -4
package/README.md
CHANGED
|
@@ -1,3 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mcp-teams
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An MCP server that lets AI assistants (Claude Code, Claude Desktop, Cursor and others) work with **Microsoft Teams on the web** in Chrome or Edge. It needs no Azure app registration and no admin consent, because it uses the session of the Teams tab you already have open.
|
|
4
|
+
|
|
5
|
+
It supports work and school (Microsoft 365) accounts. Personal Teams (teams.live.com) isn't supported.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
AI app ────stdio(MCP)──► mcp-teams server ──ws://127.0.0.1:47393──► browser extension ──► Teams tab
|
|
9
|
+
(loopback only; our (runs Teams' own
|
|
10
|
+
extension's fixed id) web API in the tab)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Tools
|
|
14
|
+
|
|
15
|
+
| Tool | What it does |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `get_version` | Server and extension versions, the connection status, and which Teams services have a session |
|
|
18
|
+
| `reload_extension` | Reloads the browser extension from disk after an update and waits until it is back |
|
|
19
|
+
| `list_teams` | Your teams with their channels (`include_ids: true` adds ids) |
|
|
20
|
+
| `list_chats` | Your 1:1, group and meeting chats, most recent first: title (or the other members' names), last message, unread. Filter with `query` or `unread_only`. |
|
|
21
|
+
| `read_messages` | The latest messages of a chat or channel, oldest first: sender, time, text, shared files, reactions. In channels, replies are nested under their post; `reply_to` reads one post's thread; `before` pages further back. |
|
|
22
|
+
| `search_messages` | Searches all chats and channels with Microsoft Search, like the search box in Teams |
|
|
23
|
+
| `find_people` | Looks people up by name or email: name, email, job title, department, office |
|
|
24
|
+
| `list_meetings` | Your meetings (each has a meeting chat), most recent first: subject, scheduled time, organizer, whether there is a transcript, and whether another organization hosted it |
|
|
25
|
+
| `get_meeting` | The calls held in a meeting (one per occurrence of a recurring one): start, end, who joined, the recording's link, whether there is a transcript |
|
|
26
|
+
| `read_transcript` | What was said: `[hh:mm:ss] Speaker: text` lines and each speaker's talk time. Long transcripts continue with `from`. |
|
|
27
|
+
| `search_transcripts` | Finds lines in the transcripts of your recent meetings that contain all the words of a query, each with the line before it |
|
|
28
|
+
| `api_traffic` | Diagnostic: the endpoints the Teams tab has called (method, host, path, status, header **names**, auth scheme). Header and query **values** are never recorded. |
|
|
29
|
+
| `send_message` | Asks to post in a chat or channel, to reply to a channel post, or to start a chat with people (`to`: one person for a 1:1 chat, several for a group chat, with an optional `title`). **You must click Send.** |
|
|
30
|
+
|
|
31
|
+
People in `to` are given by name or email and looked up like Teams' people picker; each must mean exactly one person in your organization, otherwise the matches are listed. If a chat with exactly those people already exists (for a group with a `title`, also that title), the message goes there instead of starting another one.
|
|
32
|
+
|
|
33
|
+
A conversation can be given as an id (`19:…`), as `Team/Channel`, as a chat title, or as a person's name for your 1:1 chat with them. A name must match exactly one conversation; otherwise the matches are listed.
|
|
34
|
+
|
|
35
|
+
### Meetings and transcripts
|
|
36
|
+
|
|
37
|
+
Meeting data comes from the meeting chat. Teams posts an event there for each call, saying who joined and when, and a message for each recording and transcript. Transcripts are stored in the organizer's OneDrive, so `read_transcript` reads them with your SharePoint sign-in in a SharePoint tab. If none is open, it opens your OneDrive in a pinned background tab. You can read the transcripts of meetings you were invited to, as in Teams. There is one only when someone turned on transcription in the meeting.
|
|
38
|
+
|
|
39
|
+
Teams reports the call's length, not each person's time in it, so attendance lists who joined. Meeting chats of another organization's meetings (`external: true`) usually can't be read once you've left, which is how Teams works too.
|
|
40
|
+
|
|
41
|
+
### Compared with Microsoft's Microsoft 365 connector for Claude
|
|
42
|
+
|
|
43
|
+
| | mcp-teams | Microsoft 365 connector |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| Setup | Your browser session; no admin involvement | A Microsoft Entra Global Administrator must consent for the tenant; write tools need an admin to turn them on |
|
|
46
|
+
| Read chats, channels, search | Yes | Yes |
|
|
47
|
+
| Send, reply, start 1:1 and group chats | Yes, after you click Send in a confirmation window | Yes, if an admin allows it ("Ask" per send) |
|
|
48
|
+
| Meeting transcripts | Read with speakers and timestamps, page through long ones, search across meetings | Read |
|
|
49
|
+
| Attendance | Who joined each call, from the meeting chat | Yes, from Teams attendance reports |
|
|
50
|
+
| AI meeting recap | No | Yes, with a Copilot or Teams Premium license |
|
|
51
|
+
| Works when Conditional Access blocks cloud apps | Yes: requests come from your browser | No, if the policy blocks Anthropic's servers |
|
|
52
|
+
| AI apps | Claude Code, Claude Desktop, Cursor, VS Code, Windsurf, Gemini CLI, Codex | Claude only |
|
|
53
|
+
|
|
54
|
+
### Sending always needs your click
|
|
55
|
+
|
|
56
|
+
The AI can only **request** a send. The extension then opens its own window showing where the message goes (for a new chat: who will be in it, and its title), the post it replies to and the message itself. The message is posted, and a new chat created, only if you click **Send** there. Closing the window, clicking *Don't send*, or waiting 5 minutes all mean nothing is posted.
|
|
57
|
+
|
|
58
|
+
## Setup
|
|
59
|
+
|
|
60
|
+
You need [Node.js](https://nodejs.org) 20 or later, and Chrome or Edge. Then, in a terminal:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
npx mcp-teams setup
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`setup` does the rest and can be run again at any time:
|
|
67
|
+
|
|
68
|
+
- It registers the server with the AI apps it finds: Claude Code, Claude Desktop, Cursor, VS Code (Copilot), Windsurf, Gemini CLI and Codex. It shows what it will change and asks first (`--yes` skips the question). It only adds or updates its own `teams` entry, and keeps a `.mcp-teams.bak` copy of each file it changes. For other MCP clients, register `npx -y mcp-teams@latest` as a stdio server.
|
|
69
|
+
- It installs the extension in `~/.mcp-teams/extension`.
|
|
70
|
+
- It opens the Extensions page of Chrome and/or Edge, and that folder. The one manual step is in the browser you use Teams in: turn on *Developer mode* and click *Load unpacked* on the folder.
|
|
71
|
+
- It waits until the extension connects and Teams answers, then says *Ready*.
|
|
72
|
+
|
|
73
|
+
Restart the AI apps it registered (in Claude Code: `/mcp`).
|
|
74
|
+
|
|
75
|
+
You don't need to keep Teams open. When no Teams tab is open, the extension opens one itself (pinned, in the background) and waits up to a minute for it to sign in.
|
|
76
|
+
|
|
77
|
+
## How it reaches Teams
|
|
78
|
+
|
|
79
|
+
Teams on the web talks to several services, each with its own sign-in token: chats and messages (`chatsvc`), the team and chat list (`csa`), people (`mt`) and search (`substrate.office.com`). The extension notes the tokens Teams sends on its own requests and replays them from inside the Teams tab. They are kept in the browser's session memory and never leave the browser. Teams calls most of these services as soon as it loads. Search may only be signed in once you have used Teams' search box; until then `search_messages` and `find_people` say so.
|
|
80
|
+
|
|
81
|
+
## Several sessions at once
|
|
82
|
+
|
|
83
|
+
Every AI session starts its own mcp-teams server, and they share the one extension connection. The first server owns the bridge port (`role: "hub"` in `get_version`), and later ones forward their calls through it (`role: "peer"`). When the hub's session ends, a peer takes over within a second. A newer server takes over from an older one.
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
`~/.mcp-teams/config.json` is created on first run:
|
|
88
|
+
|
|
89
|
+
| Key | Env override | Default |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `token` | — | random, generated once; used by peers |
|
|
92
|
+
| `port` | `TEAMS_MCP_PORT` | `47393` (also set it in the extension popup) |
|
|
93
|
+
|
|
94
|
+
## Security notes
|
|
95
|
+
|
|
96
|
+
- The bridge listens only on `127.0.0.1`. Web pages can't connect to it, because only extension origins are accepted. This extension is recognized by its fixed id; other extensions and local programs need the token in `~/.mcp-teams/config.json`.
|
|
97
|
+
- Messages can contain prompt injection. The send-confirmation window is the safeguard, so read it before clicking Send.
|
|
98
|
+
- The extension may observe requests to Microsoft hosts (`*.microsoft.com`, `*.office.com`, `*.sharepoint.com` and similar), but it only records requests made by the Teams tab, and only names, for `api_traffic`. It runs requests only in Teams tabs, and in SharePoint tabs to read transcripts.
|
|
99
|
+
- If you also give an AI direct control of your browser or desktop, it could click the confirmation button itself. Don't combine the two if you rely on the confirmation step.
|
|
100
|
+
|
|
101
|
+
## Limitations
|
|
102
|
+
|
|
103
|
+
- It relies on Teams' internal web APIs, which Microsoft doesn't document and can change. All request shapes live in `extension/background.js` and the parsing in `extension/teams.js`.
|
|
104
|
+
- It can't edit or delete messages, mention people, or add people to an existing chat. New chats are only with people in your organization (or ones Teams can reach from it).
|
|
105
|
+
- Files shared in Teams live in SharePoint/OneDrive. `read_messages` returns their links; use mcp-onedrive to download them.
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
git clone https://github.com/HeeyunCho/mcp-teams.git
|
|
111
|
+
cd mcp-teams
|
|
112
|
+
npm install && npm run build
|
|
113
|
+
node dist/index.js setup # registers this copy (node dist/index.js) instead of npx
|
|
114
|
+
npm test # builds, then runs unit + end-to-end tests (fake extension)
|
|
115
|
+
npm run probe -- status # calls one extension method through the bridge (see scripts/probe.mjs)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Versions are bumped automatically. On every push to `main` (except Markdown-only changes), the **Version bump** GitHub Action runs the tests, then bumps `package.json`, `package-lock.json` and `extension/manifest.json`, commits `chore(release): vX.Y.Z` and tags it. `feat:` commits bump the minor version, anything else the patch. Stay on 0.x: don't use `type!:` or a `BREAKING CHANGE:` footer.
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
3
|
+
import { EXTENSION_ORIGIN as TRUSTED_EXTENSION_ORIGIN } from "./extension.js";
|
|
4
|
+
/** How long the hub trusts what it learned about which browser has Teams signed in. */
|
|
5
|
+
const PICK_STATUS_TIMEOUT_MS = 3_000;
|
|
6
|
+
const EXTENSION_ORIGIN = /^(chrome-extension|extension):\/\/[a-z0-9]+$/i;
|
|
7
|
+
const RETRY_MS = 2_000;
|
|
8
|
+
/** Largest message accepted on the bridge. Attachments travel one per message as base64 (+33%). */
|
|
9
|
+
export const MAX_MESSAGE_BYTES = 256 * 1024 * 1024;
|
|
10
|
+
const JOIN_TIMEOUT_MS = 3_000;
|
|
11
|
+
/** A hub handing over to a newer server waits this long for its own calls to finish. */
|
|
12
|
+
const STEP_DOWN_MAX_WAIT_MS = 5 * 60_000;
|
|
13
|
+
/** True when version a is newer than b (x.y.z; anything else never counts as newer). */
|
|
14
|
+
export function isNewer(a, b) {
|
|
15
|
+
const parse = (v) => (typeof v === "string" && /^\d+\.\d+\.\d+$/.test(v) ? v.split(".").map(Number) : null);
|
|
16
|
+
const x = parse(a);
|
|
17
|
+
const y = parse(b);
|
|
18
|
+
if (!x || !y)
|
|
19
|
+
return false;
|
|
20
|
+
for (let i = 0; i < 3; i++)
|
|
21
|
+
if (x[i] !== y[i])
|
|
22
|
+
return x[i] > y[i];
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function describeSocketError(err, limit) {
|
|
26
|
+
return err.code === "WS_ERR_UNSUPPORTED_MESSAGE_LENGTH"
|
|
27
|
+
? `The response was too large (over ${Math.round(limit / 1024 / 1024)} MB) and was dropped. The connection is re-established automatically.`
|
|
28
|
+
: `Bridge connection error: ${err.message}`;
|
|
29
|
+
}
|
|
30
|
+
export class ExtensionBridge {
|
|
31
|
+
port;
|
|
32
|
+
token;
|
|
33
|
+
serverVersion;
|
|
34
|
+
maxMessageBytes;
|
|
35
|
+
role = "none";
|
|
36
|
+
/** The extension calls go to (with several browsers: the preferred one). */
|
|
37
|
+
extension;
|
|
38
|
+
/** Every connected extension, one per browser profile. */
|
|
39
|
+
extensions = [];
|
|
40
|
+
listenError;
|
|
41
|
+
/** Peer only: version of the hub server it forwards through. */
|
|
42
|
+
hubVersion;
|
|
43
|
+
wss;
|
|
44
|
+
sockets = new Map(); // hub: the extensions
|
|
45
|
+
preferred; // hub: the extension that last answered a call
|
|
46
|
+
hub; // peer: the hub
|
|
47
|
+
peers = new Set();
|
|
48
|
+
pending = new Map();
|
|
49
|
+
keepAlive;
|
|
50
|
+
retry;
|
|
51
|
+
closed = false;
|
|
52
|
+
steppingDown = false;
|
|
53
|
+
alive = new WeakSet(); // hub: extensions that answered the last ping
|
|
54
|
+
constructor(port, token, serverVersion, maxMessageBytes = MAX_MESSAGE_BYTES) {
|
|
55
|
+
this.port = port;
|
|
56
|
+
this.token = token;
|
|
57
|
+
this.serverVersion = serverVersion;
|
|
58
|
+
this.maxMessageBytes = maxMessageBytes;
|
|
59
|
+
}
|
|
60
|
+
/** Becomes the hub, or a peer of the existing hub. Keeps retrying in the background if neither works. */
|
|
61
|
+
async start() {
|
|
62
|
+
if (this.closed)
|
|
63
|
+
return;
|
|
64
|
+
if (await this.listen())
|
|
65
|
+
return this.setRole("hub");
|
|
66
|
+
if (await this.join())
|
|
67
|
+
return this.setRole("peer");
|
|
68
|
+
this.role = "none";
|
|
69
|
+
clearTimeout(this.retry);
|
|
70
|
+
this.retry = setTimeout(() => void this.start(), RETRY_MS);
|
|
71
|
+
this.retry.unref();
|
|
72
|
+
}
|
|
73
|
+
get connected() {
|
|
74
|
+
if (this.role === "hub")
|
|
75
|
+
return [...this.sockets.keys()].some((s) => s.readyState === WebSocket.OPEN);
|
|
76
|
+
if (this.role === "peer")
|
|
77
|
+
return this.hub?.readyState === WebSocket.OPEN && !!this.extension;
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
setRole(role) {
|
|
81
|
+
this.role = role;
|
|
82
|
+
this.listenError = undefined;
|
|
83
|
+
}
|
|
84
|
+
// ------------------------------------------------------------------ hub
|
|
85
|
+
listen() {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
const wss = new WebSocketServer({
|
|
88
|
+
host: "127.0.0.1",
|
|
89
|
+
port: this.port,
|
|
90
|
+
maxPayload: this.maxMessageBytes,
|
|
91
|
+
verifyClient: ({ origin }) => !origin || EXTENSION_ORIGIN.test(origin),
|
|
92
|
+
});
|
|
93
|
+
let listening = false;
|
|
94
|
+
wss.once("listening", () => {
|
|
95
|
+
listening = true;
|
|
96
|
+
this.wss = wss;
|
|
97
|
+
resolve(true);
|
|
98
|
+
});
|
|
99
|
+
wss.on("error", (err) => {
|
|
100
|
+
if (listening)
|
|
101
|
+
return this.restartHub(`bridge server error: ${err.message}`);
|
|
102
|
+
if (err.code !== "EADDRINUSE")
|
|
103
|
+
this.listenError = err.message;
|
|
104
|
+
wss.close();
|
|
105
|
+
resolve(false);
|
|
106
|
+
});
|
|
107
|
+
wss.on("connection", (ws, req) => this.onConnection(ws, req.headers.origin));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
onConnection(ws, origin) {
|
|
111
|
+
let authed = false;
|
|
112
|
+
const authTimer = setTimeout(() => !authed && ws.close(4401, "auth timeout"), 5000);
|
|
113
|
+
ws.on("message", (raw) => {
|
|
114
|
+
let msg;
|
|
115
|
+
try {
|
|
116
|
+
msg = JSON.parse(raw.toString());
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!msg || typeof msg !== "object")
|
|
122
|
+
return;
|
|
123
|
+
if (!authed) {
|
|
124
|
+
const trusted = msg.type === "hello" && origin === TRUSTED_EXTENSION_ORIGIN;
|
|
125
|
+
if ((!trusted && msg.token !== this.token) || (msg.type !== "hello" && msg.type !== "peer-hello")) {
|
|
126
|
+
ws.close(4401, "bad token");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
authed = true;
|
|
130
|
+
clearTimeout(authTimer);
|
|
131
|
+
if (msg.type === "hello")
|
|
132
|
+
this.acceptExtension(ws, msg, origin);
|
|
133
|
+
else
|
|
134
|
+
this.acceptPeer(ws, msg.version);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (this.sockets.has(ws))
|
|
138
|
+
this.onExtensionMessage(msg);
|
|
139
|
+
else if (this.peers.has(ws))
|
|
140
|
+
this.onPeerMessage(ws, msg);
|
|
141
|
+
});
|
|
142
|
+
// Without a listener, a socket error (e.g. a message over MAX_MESSAGE_BYTES) would be
|
|
143
|
+
// an unhandled 'error' event and end the whole process. The socket closes afterwards.
|
|
144
|
+
ws.on("error", (err) => {
|
|
145
|
+
if (this.sockets.has(ws))
|
|
146
|
+
this.rejectAll(describeSocketError(err, this.maxMessageBytes), ws);
|
|
147
|
+
});
|
|
148
|
+
ws.on("close", () => {
|
|
149
|
+
clearTimeout(authTimer);
|
|
150
|
+
this.peers.delete(ws);
|
|
151
|
+
if (this.sockets.delete(ws)) {
|
|
152
|
+
if (this.preferred === ws)
|
|
153
|
+
this.preferred = undefined;
|
|
154
|
+
this.rejectAll("Browser extension disconnected", ws);
|
|
155
|
+
this.updateExtensions();
|
|
156
|
+
if (!this.sockets.size)
|
|
157
|
+
clearInterval(this.keepAlive);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Several browsers (or profiles) can be connected at once, one connection each. A
|
|
163
|
+
* reconnect from the same one (same instance, or for older extensions the same origin
|
|
164
|
+
* and browser) replaces its old connection. While our extension (fixed id) is
|
|
165
|
+
* connected, copies with another id (e.g. an older one loaded from another folder) are
|
|
166
|
+
* turned away, and connecting ours replaces them.
|
|
167
|
+
*/
|
|
168
|
+
acceptExtension(ws, msg, origin) {
|
|
169
|
+
const info = {
|
|
170
|
+
version: String(msg.version ?? "unknown"),
|
|
171
|
+
browser: msg.browser,
|
|
172
|
+
...(origin ? { origin } : {}),
|
|
173
|
+
...(typeof msg.instance === "string" ? { instance: msg.instance } : {}),
|
|
174
|
+
};
|
|
175
|
+
const ours = origin === TRUSTED_EXTENSION_ORIGIN;
|
|
176
|
+
const others = [...this.sockets].filter(([s]) => s !== ws);
|
|
177
|
+
if (!ours && others.some(([, i]) => i.origin === TRUSTED_EXTENSION_ORIGIN)) {
|
|
178
|
+
ws.close(4409, "another copy of the extension is connected");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const key = (i) => i.instance ?? `${i.origin}|${i.browser}`;
|
|
182
|
+
for (const [s, i] of others) {
|
|
183
|
+
if (key(i) === key(info) || (ours && i.origin !== TRUSTED_EXTENSION_ORIGIN) || (!ours && !i.instance)) {
|
|
184
|
+
s.close(4000, "replaced");
|
|
185
|
+
// Its close event comes later and no longer finds it, so fail its calls now.
|
|
186
|
+
this.sockets.delete(s);
|
|
187
|
+
this.rejectAll("Browser extension reconnected; try again.", s);
|
|
188
|
+
if (this.preferred === s)
|
|
189
|
+
this.preferred = undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
this.sockets.set(ws, info);
|
|
193
|
+
ws.send(JSON.stringify({ type: "welcome", serverVersion: this.serverVersion }));
|
|
194
|
+
this.startKeepAlive(ws);
|
|
195
|
+
this.updateExtensions();
|
|
196
|
+
}
|
|
197
|
+
/** Refreshes `extension`/`extensions` after a change and tells the peers. */
|
|
198
|
+
updateExtensions() {
|
|
199
|
+
this.extensions = [...this.sockets.values()];
|
|
200
|
+
this.extension = (this.preferred && this.sockets.get(this.preferred)) ?? this.extensions.at(-1);
|
|
201
|
+
this.broadcastState();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The extension a call goes to: the only one, the one that answered last, or else the
|
|
205
|
+
* one whose browser has Teams signed in (then: has a Teams tab, then: newest).
|
|
206
|
+
*/
|
|
207
|
+
async pickExtension() {
|
|
208
|
+
const open = [...this.sockets.keys()].filter((s) => s.readyState === WebSocket.OPEN);
|
|
209
|
+
if (open.length <= 1)
|
|
210
|
+
return open[0];
|
|
211
|
+
if (this.preferred && open.includes(this.preferred))
|
|
212
|
+
return this.preferred;
|
|
213
|
+
const statuses = await Promise.all(open.map((s) => this.send(s, "status", {}, PICK_STATUS_TIMEOUT_MS).catch(() => null)));
|
|
214
|
+
const score = (st) => !st ? -1 : st.sessions?.some((x) => x.sessionCaptured) ? 2 : (st.teamsTabs ?? 0) > 0 ? 1 : 0;
|
|
215
|
+
let best = open.length - 1; // newest first among equals
|
|
216
|
+
for (let i = open.length - 1; i >= 0; i--)
|
|
217
|
+
if (score(statuses[i]) > score(statuses[best]))
|
|
218
|
+
best = i;
|
|
219
|
+
return open[best];
|
|
220
|
+
}
|
|
221
|
+
acceptPeer(ws, peerVersion) {
|
|
222
|
+
this.peers.add(ws);
|
|
223
|
+
ws.send(JSON.stringify({ type: "peer-welcome", serverVersion: this.serverVersion, extension: this.extension ?? null, extensions: this.extensions }));
|
|
224
|
+
// A newer server joined: hand the port to it, so its fixes (and what the newer
|
|
225
|
+
// extension expects) take effect without restarting this AI session.
|
|
226
|
+
if (isNewer(peerVersion, this.serverVersion))
|
|
227
|
+
void this.stepDown(String(peerVersion));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Hub: stops serving (after this server's own calls finish) and joins again as a peer.
|
|
231
|
+
* Peers see the hub close and race for the port, retrying after 0.1–0.5 s; this one
|
|
232
|
+
* waits longer, so a peer takes over. If an older peer wins, the newer one steps it
|
|
233
|
+
* down in turn when it joins.
|
|
234
|
+
*/
|
|
235
|
+
async stepDown(newer) {
|
|
236
|
+
if (this.steppingDown || this.role !== "hub")
|
|
237
|
+
return;
|
|
238
|
+
this.steppingDown = true;
|
|
239
|
+
const end = Date.now() + STEP_DOWN_MAX_WAIT_MS;
|
|
240
|
+
while (this.pending.size && Date.now() < end && !this.closed)
|
|
241
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
242
|
+
if (this.closed || this.role !== "hub")
|
|
243
|
+
return void (this.steppingDown = false);
|
|
244
|
+
console.error(`[mcp-teams ${this.serverVersion}] handing the bridge to mcp-teams ${newer}`);
|
|
245
|
+
this.restartHub("mcp-teams was updated; try again in a few seconds.", RETRY_MS);
|
|
246
|
+
this.steppingDown = false;
|
|
247
|
+
}
|
|
248
|
+
/** Hub: closes the server and every connection, then becomes a hub or peer again. */
|
|
249
|
+
restartHub(reason, delayMs = 100 + Math.random() * 400) {
|
|
250
|
+
const wss = this.wss;
|
|
251
|
+
this.wss = undefined;
|
|
252
|
+
this.role = "none";
|
|
253
|
+
clearInterval(this.keepAlive);
|
|
254
|
+
this.rejectAll(reason);
|
|
255
|
+
for (const s of this.sockets.keys())
|
|
256
|
+
s.close(1012, "restarting");
|
|
257
|
+
for (const peer of this.peers)
|
|
258
|
+
peer.close(1012, "restarting");
|
|
259
|
+
this.sockets.clear();
|
|
260
|
+
this.peers.clear();
|
|
261
|
+
this.preferred = undefined;
|
|
262
|
+
this.extension = undefined;
|
|
263
|
+
this.extensions = [];
|
|
264
|
+
wss?.close();
|
|
265
|
+
if (this.closed)
|
|
266
|
+
return;
|
|
267
|
+
clearTimeout(this.retry);
|
|
268
|
+
this.retry = setTimeout(() => void this.start(), delayMs);
|
|
269
|
+
this.retry.unref();
|
|
270
|
+
}
|
|
271
|
+
onExtensionMessage(msg) {
|
|
272
|
+
if (msg.type === "response" && typeof msg.id === "string")
|
|
273
|
+
this.settle(msg);
|
|
274
|
+
}
|
|
275
|
+
/** Runs a peer's call against the extension and sends back the outcome. */
|
|
276
|
+
onPeerMessage(ws, msg) {
|
|
277
|
+
if (msg.type !== "request" || typeof msg.id !== "string")
|
|
278
|
+
return;
|
|
279
|
+
const reply = (body) => {
|
|
280
|
+
try {
|
|
281
|
+
if (ws.readyState === WebSocket.OPEN)
|
|
282
|
+
ws.send(JSON.stringify({ type: "response", id: msg.id, ...body }));
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
console.error(`[mcp-teams] could not answer a peer: ${err.message}`);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
this.call(String(msg.method), msg.params ?? {}, Number(msg.timeoutMs) || undefined).then((result) => reply({ ok: true, result }), (err) => reply({ ok: false, error: err.message }));
|
|
289
|
+
}
|
|
290
|
+
broadcastState() {
|
|
291
|
+
const state = JSON.stringify({ type: "ext-state", extension: this.extension ?? null, extensions: this.extensions });
|
|
292
|
+
for (const peer of this.peers)
|
|
293
|
+
if (peer.readyState === WebSocket.OPEN)
|
|
294
|
+
peer.send(state);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Pings keep the MV3 service worker alive while connected. A connection that didn't
|
|
298
|
+
* answer the previous WebSocket ping (a suspended or crashed browser) is dropped.
|
|
299
|
+
*/
|
|
300
|
+
startKeepAlive(ws) {
|
|
301
|
+
if (ws) {
|
|
302
|
+
this.alive.add(ws);
|
|
303
|
+
ws.on("pong", () => this.alive.add(ws));
|
|
304
|
+
}
|
|
305
|
+
clearInterval(this.keepAlive);
|
|
306
|
+
this.keepAlive = setInterval(() => {
|
|
307
|
+
for (const s of this.sockets.keys()) {
|
|
308
|
+
if (s.readyState !== WebSocket.OPEN)
|
|
309
|
+
continue;
|
|
310
|
+
if (!this.alive.has(s)) {
|
|
311
|
+
s.terminate();
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
this.alive.delete(s);
|
|
315
|
+
s.ping();
|
|
316
|
+
s.send(JSON.stringify({ type: "ping" }));
|
|
317
|
+
}
|
|
318
|
+
}, 20_000);
|
|
319
|
+
this.keepAlive.unref();
|
|
320
|
+
}
|
|
321
|
+
// ------------------------------------------------------------------ peer
|
|
322
|
+
join() {
|
|
323
|
+
return new Promise((resolve) => {
|
|
324
|
+
const ws = new WebSocket(`ws://127.0.0.1:${this.port}`, { maxPayload: this.maxMessageBytes });
|
|
325
|
+
let joined = false;
|
|
326
|
+
const timer = setTimeout(() => ws.terminate(), JOIN_TIMEOUT_MS);
|
|
327
|
+
ws.on("open", () => ws.send(JSON.stringify({ type: "peer-hello", token: this.token, version: this.serverVersion })));
|
|
328
|
+
ws.on("message", (raw) => {
|
|
329
|
+
let msg;
|
|
330
|
+
try {
|
|
331
|
+
msg = JSON.parse(raw.toString());
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (!msg || typeof msg !== "object")
|
|
337
|
+
return;
|
|
338
|
+
if (msg.type === "peer-welcome" && !joined) {
|
|
339
|
+
joined = true;
|
|
340
|
+
clearTimeout(timer);
|
|
341
|
+
this.hub = ws;
|
|
342
|
+
this.hubVersion = String(msg.serverVersion ?? "unknown");
|
|
343
|
+
this.extension = msg.extension ?? undefined;
|
|
344
|
+
this.extensions = msg.extensions ?? (msg.extension ? [msg.extension] : []);
|
|
345
|
+
resolve(true);
|
|
346
|
+
}
|
|
347
|
+
else if (msg.type === "ext-state") {
|
|
348
|
+
this.extension = msg.extension ?? undefined;
|
|
349
|
+
this.extensions = msg.extensions ?? (msg.extension ? [msg.extension] : []);
|
|
350
|
+
}
|
|
351
|
+
else if (msg.type === "response" && typeof msg.id === "string") {
|
|
352
|
+
this.settle(msg);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
ws.on("error", (err) => {
|
|
356
|
+
if (joined && this.hub === ws)
|
|
357
|
+
this.rejectAll(describeSocketError(err, this.maxMessageBytes));
|
|
358
|
+
});
|
|
359
|
+
ws.on("close", (code) => {
|
|
360
|
+
clearTimeout(timer);
|
|
361
|
+
if (!joined) {
|
|
362
|
+
this.listenError =
|
|
363
|
+
code === 4401
|
|
364
|
+
? `The mcp-teams server on port ${this.port} rejected this server's token.`
|
|
365
|
+
: `Port ${this.port} is in use by another program.`;
|
|
366
|
+
return resolve(false);
|
|
367
|
+
}
|
|
368
|
+
if (this.hub !== ws)
|
|
369
|
+
return;
|
|
370
|
+
// The hub went away: take over the port or join whoever did.
|
|
371
|
+
this.hub = undefined;
|
|
372
|
+
this.hubVersion = undefined;
|
|
373
|
+
this.extension = undefined;
|
|
374
|
+
this.extensions = [];
|
|
375
|
+
this.role = "none";
|
|
376
|
+
this.rejectAll("The mcp-teams hub restarted; try again in a few seconds.");
|
|
377
|
+
if (!this.closed) {
|
|
378
|
+
clearTimeout(this.retry);
|
|
379
|
+
this.retry = setTimeout(() => void this.start(), 100 + Math.random() * 400);
|
|
380
|
+
this.retry.unref();
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
// ------------------------------------------------------------------ calls
|
|
386
|
+
async call(method, params = {}, timeoutMs = 60_000) {
|
|
387
|
+
if (method === "reloadAll" && this.role === "hub")
|
|
388
|
+
return this.reloadAll(timeoutMs);
|
|
389
|
+
const target = this.role === "hub" ? await this.pickExtension() : this.role === "peer" ? this.hub : undefined;
|
|
390
|
+
if (!this.connected || !target) {
|
|
391
|
+
const reason = this.role === "none"
|
|
392
|
+
? (this.listenError ?? "Connecting to the mcp-teams bridge; try again in a few seconds.")
|
|
393
|
+
: "Browser extension is not connected. Open Chrome or Edge with the mcp-teams extension installed (run `mcp-teams setup`).";
|
|
394
|
+
throw new Error(reason);
|
|
395
|
+
}
|
|
396
|
+
const result = await this.send(target, method, params, timeoutMs);
|
|
397
|
+
if (this.role === "hub" && this.preferred !== target && this.sockets.has(target)) {
|
|
398
|
+
this.preferred = target;
|
|
399
|
+
this.updateExtensions();
|
|
400
|
+
}
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
/** Hub: asks every connected browser's extension to reload itself. */
|
|
404
|
+
async reloadAll(timeoutMs) {
|
|
405
|
+
const open = [...this.sockets.keys()].filter((s) => s.readyState === WebSocket.OPEN);
|
|
406
|
+
if (!open.length)
|
|
407
|
+
throw new Error("Browser extension is not connected.");
|
|
408
|
+
const results = await Promise.allSettled(open.map((s) => this.send(s, "reload", {}, timeoutMs)));
|
|
409
|
+
const failed = results.filter((r) => r.status === "rejected");
|
|
410
|
+
if (failed.length === results.length)
|
|
411
|
+
throw failed[0].reason;
|
|
412
|
+
return { reloading: results.length - failed.length };
|
|
413
|
+
}
|
|
414
|
+
/** Sends one request on a connection (an extension for the hub, the hub for a peer). */
|
|
415
|
+
send(target, method, params, timeoutMs) {
|
|
416
|
+
const id = randomUUID();
|
|
417
|
+
// A peer waits a little longer than the hub, so the hub's own timeout message wins.
|
|
418
|
+
const wait = this.role === "peer" ? timeoutMs + 5_000 : timeoutMs;
|
|
419
|
+
return new Promise((resolve, reject) => {
|
|
420
|
+
const timer = setTimeout(() => {
|
|
421
|
+
this.pending.delete(id);
|
|
422
|
+
reject(new Error(`Extension call '${method}' timed out`));
|
|
423
|
+
}, wait);
|
|
424
|
+
this.pending.set(id, { resolve: resolve, reject, timer, socket: this.role === "hub" ? target : undefined });
|
|
425
|
+
target.send(JSON.stringify({ type: "request", id, method, params, timeoutMs }));
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Has the extension reload itself from disk, then waits until it is connected again.
|
|
430
|
+
* Returns its version before and after.
|
|
431
|
+
*/
|
|
432
|
+
async reloadExtension(timeoutMs = 30_000) {
|
|
433
|
+
const before = this.extension?.version ?? null;
|
|
434
|
+
await this.call("reloadAll");
|
|
435
|
+
// The extensions reload 250 ms after answering; wait for the old connections to go,
|
|
436
|
+
// then for every browser to be back. Copies with another id are turned away (4409)
|
|
437
|
+
// once ours is back, so only ours count when ours is connected.
|
|
438
|
+
const ours = this.extensions.filter((e) => e.origin === TRUSTED_EXTENSION_ORIGIN).length;
|
|
439
|
+
const count = Math.max(1, ours || this.extensions.length);
|
|
440
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
441
|
+
await sleep(1_000);
|
|
442
|
+
const end = Date.now() + timeoutMs;
|
|
443
|
+
for (; Date.now() < end; await sleep(250)) {
|
|
444
|
+
if (this.connected && this.extension && this.extensions.length >= count)
|
|
445
|
+
return { before, after: this.extension.version };
|
|
446
|
+
}
|
|
447
|
+
if (this.connected && this.extension)
|
|
448
|
+
return { before, after: this.extension.version };
|
|
449
|
+
throw new Error(`The extension did not reconnect within ${timeoutMs / 1000} s. Check chrome://extensions for errors.`);
|
|
450
|
+
}
|
|
451
|
+
settle(msg) {
|
|
452
|
+
const p = this.pending.get(msg.id);
|
|
453
|
+
if (!p)
|
|
454
|
+
return;
|
|
455
|
+
this.pending.delete(msg.id);
|
|
456
|
+
clearTimeout(p.timer);
|
|
457
|
+
if (msg.ok)
|
|
458
|
+
p.resolve(msg.result);
|
|
459
|
+
else
|
|
460
|
+
p.reject(new Error(msg.error ?? "extension error"));
|
|
461
|
+
}
|
|
462
|
+
/** Fails the pending calls (only those sent on `socket`, when given). */
|
|
463
|
+
rejectAll(reason, socket) {
|
|
464
|
+
for (const [id, p] of this.pending) {
|
|
465
|
+
if (socket && p.socket !== socket)
|
|
466
|
+
continue;
|
|
467
|
+
clearTimeout(p.timer);
|
|
468
|
+
p.reject(new Error(reason));
|
|
469
|
+
this.pending.delete(id);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async close() {
|
|
473
|
+
this.closed = true;
|
|
474
|
+
clearTimeout(this.retry);
|
|
475
|
+
clearInterval(this.keepAlive);
|
|
476
|
+
this.rejectAll("mcp-teams is shutting down");
|
|
477
|
+
this.hub?.close();
|
|
478
|
+
for (const s of this.sockets.keys())
|
|
479
|
+
s.close();
|
|
480
|
+
for (const peer of this.peers)
|
|
481
|
+
peer.close();
|
|
482
|
+
await new Promise((r) => (this.wss ? this.wss.close(() => r()) : r()));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|