msteams-mcp 0.25.1 → 0.27.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 +54 -25
- package/dist/auth/session-store.d.ts +5 -0
- package/dist/auth/session-store.js +29 -2
- package/dist/auth/session-store.test.d.ts +5 -0
- package/dist/auth/session-store.test.js +69 -0
- package/dist/cli.d.ts +20 -0
- package/dist/{test/mcp-harness.js → cli.js} +18 -15
- package/package.json +4 -3
- package/dist/api/graph-api.d.ts +0 -55
- package/dist/api/graph-api.js +0 -148
- package/dist/api/index.d.ts +0 -11
- package/dist/api/index.js +0 -11
- package/dist/auth/index.d.ts +0 -7
- package/dist/auth/index.js +0 -7
- package/dist/browser/chrome-cookie-import.d.ts +0 -27
- package/dist/browser/chrome-cookie-import.js +0 -294
- package/dist/browser/chrome-cookie-import.test.d.ts +0 -1
- package/dist/browser/chrome-cookie-import.test.js +0 -122
- package/dist/research/auth-research.d.ts +0 -10
- package/dist/research/auth-research.js +0 -175
- package/dist/research/explore.d.ts +0 -11
- package/dist/research/explore.js +0 -287
- package/dist/research/search-research.d.ts +0 -17
- package/dist/research/search-research.js +0 -317
- package/dist/test/debug-search.d.ts +0 -10
- package/dist/test/debug-search.js +0 -147
- package/dist/test/dump-tokens.d.ts +0 -7
- package/dist/test/dump-tokens.js +0 -90
- package/dist/test/mcp-harness.d.ts +0 -17
- package/dist/test/test-transcript.d.ts +0 -6
- package/dist/test/test-transcript.js +0 -47
- package/dist/tools/graph-tools.d.ts +0 -42
- package/dist/tools/graph-tools.js +0 -99
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Teams MCP Server
|
|
1
|
+
# Teams MCP Server & CLI
|
|
2
2
|
|
|
3
3
|
[](https://github.com/m0nkmaster/msteams-mcp/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -68,6 +68,24 @@ Then configure your MCP client:
|
|
|
68
68
|
|
|
69
69
|
The server uses your system's Chrome (macOS/Linux) or Edge (Windows) for authentication.
|
|
70
70
|
|
|
71
|
+
### CLI (alternative to the MCP server)
|
|
72
|
+
|
|
73
|
+
The same functionality is also available as a standalone command-line tool, `msteams`. This is useful when you want Teams from a shell or script instead of an MCP client (for example, if an MCP integration is unreliable in your environment).
|
|
74
|
+
|
|
75
|
+
Install it globally from npm:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install -g msteams-mcp
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This installs two binaries: `msteams-mcp` (the MCP server) and `msteams` (the CLI). Or run it without installing:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx -y msteams-mcp msteams status
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
See [CLI Usage](#cli-usage) for commands.
|
|
88
|
+
|
|
71
89
|
## Available Tools
|
|
72
90
|
|
|
73
91
|
### Search & Discovery
|
|
@@ -180,48 +198,59 @@ The server also exposes passive resources for context discovery:
|
|
|
180
198
|
| `teams://me/favorites` | Pinned conversations |
|
|
181
199
|
| `teams://status` | Authentication status |
|
|
182
200
|
|
|
183
|
-
## CLI
|
|
201
|
+
## CLI Usage
|
|
202
|
+
|
|
203
|
+
`msteams` exposes every tool the MCP server does - full parity, same authentication, same session files. Run with no arguments to list all tools and shortcuts.
|
|
184
204
|
|
|
185
|
-
|
|
205
|
+
If you installed globally (`npm install -g msteams-mcp`), invoke it directly:
|
|
186
206
|
|
|
187
207
|
```bash
|
|
208
|
+
# List available tools and shortcuts
|
|
209
|
+
msteams
|
|
210
|
+
|
|
188
211
|
# Check authentication status
|
|
189
|
-
|
|
212
|
+
msteams status
|
|
213
|
+
|
|
214
|
+
# Log in (opens a browser; tries silent SSO first)
|
|
215
|
+
msteams login
|
|
216
|
+
msteams login --force # clear session and re-login
|
|
190
217
|
|
|
191
218
|
# Search messages
|
|
192
|
-
|
|
193
|
-
|
|
219
|
+
msteams search "meeting notes"
|
|
220
|
+
msteams search "project" --from 0 --size 50
|
|
194
221
|
|
|
195
222
|
# Search emails
|
|
196
|
-
|
|
223
|
+
msteams teams_search_email --query "from:sarah@company.com"
|
|
224
|
+
|
|
225
|
+
# Send a message (default: your own notes/self-chat)
|
|
226
|
+
msteams send "Hello from Teams MCP!"
|
|
227
|
+
msteams send "Message" --to "conversation-id"
|
|
197
228
|
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
229
|
+
# People, contacts, favourites, activity, unread
|
|
230
|
+
msteams people "john smith"
|
|
231
|
+
msteams favorites
|
|
232
|
+
msteams activity
|
|
233
|
+
msteams unread
|
|
201
234
|
|
|
202
|
-
#
|
|
203
|
-
|
|
235
|
+
# Any tool by name (the teams_ prefix is optional)
|
|
236
|
+
msteams teams_search_emoji --query "heart"
|
|
237
|
+
msteams find_channel --query "support"
|
|
204
238
|
|
|
205
|
-
#
|
|
206
|
-
|
|
239
|
+
# Machine-readable output
|
|
240
|
+
msteams search "query" --json
|
|
207
241
|
```
|
|
208
242
|
|
|
209
|
-
|
|
243
|
+
**Command form:** `msteams <command> [primaryArg] [--key value ...]`. Any unrecognised command is treated as a tool name (`teams_` is added automatically). Common flags like `--to`, `--from`, `--size`, `--query`, `--force` map to the matching tool parameters; run `msteams` with no arguments to see the full list.
|
|
210
244
|
|
|
211
|
-
|
|
245
|
+
### From a repo clone
|
|
212
246
|
|
|
213
|
-
|
|
214
|
-
# List available tools
|
|
215
|
-
npm run cli
|
|
247
|
+
If you're working from source, the same CLI is wired to `npm run cli` (runs via `tsx`, no build needed):
|
|
216
248
|
|
|
217
|
-
|
|
249
|
+
```bash
|
|
250
|
+
npm run cli # list tools
|
|
218
251
|
npm run cli -- search "your query"
|
|
219
252
|
npm run cli -- status
|
|
220
|
-
npm run cli --
|
|
221
|
-
npm run cli -- favorites
|
|
222
|
-
npm run cli -- activity # Get activity feed
|
|
223
|
-
npm run cli -- unread # Check unread counts
|
|
224
|
-
npm run cli -- teams_search_emoji --query "heart" # Search emojis
|
|
253
|
+
npm run cli -- send "Hi" --to "conversation-id"
|
|
225
254
|
```
|
|
226
255
|
|
|
227
256
|
## Limitations
|
|
@@ -83,5 +83,10 @@ export declare function clearTokenCache(): void;
|
|
|
83
83
|
/**
|
|
84
84
|
* Gets the Teams origin from session state.
|
|
85
85
|
* Checks multiple known Teams domains to support government clouds.
|
|
86
|
+
*
|
|
87
|
+
* Prefers an origin that has a SubstrateSearch token so Outlook/email and
|
|
88
|
+
* Substrate search keep working when both teams.cloud.microsoft and
|
|
89
|
+
* teams.microsoft.com are present in the saved session (common after the
|
|
90
|
+
* New Teams host migration).
|
|
86
91
|
*/
|
|
87
92
|
export declare function getTeamsOrigin(state: SessionState): SessionState['origins'][number] | null;
|
|
@@ -208,18 +208,45 @@ export function clearTokenCache() {
|
|
|
208
208
|
* Used to find the correct origin in session state.
|
|
209
209
|
*/
|
|
210
210
|
const TEAMS_ORIGINS = [
|
|
211
|
-
'https://teams.microsoft
|
|
211
|
+
'https://teams.cloud.microsoft', // New Teams URL (often holds SubstrateSearch tokens)
|
|
212
|
+
'https://teams.microsoft.com', // Commercial legacy
|
|
212
213
|
'https://teams.microsoft.us', // GCC-High
|
|
213
214
|
'https://dod.teams.microsoft.us', // DoD
|
|
214
|
-
'https://teams.cloud.microsoft', // New Teams URL
|
|
215
215
|
];
|
|
216
|
+
/**
|
|
217
|
+
* Returns true if localStorage has a SubstrateSearch MSAL token entry.
|
|
218
|
+
*/
|
|
219
|
+
function hasSubstrateSearchToken(localStorage) {
|
|
220
|
+
for (const item of localStorage ?? []) {
|
|
221
|
+
try {
|
|
222
|
+
const entry = JSON.parse(item.value);
|
|
223
|
+
if (entry.target?.includes('SubstrateSearch') &&
|
|
224
|
+
typeof entry.secret === 'string' &&
|
|
225
|
+
entry.secret.startsWith('ey')) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// ignore non-JSON localStorage values
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
216
235
|
/**
|
|
217
236
|
* Gets the Teams origin from session state.
|
|
218
237
|
* Checks multiple known Teams domains to support government clouds.
|
|
238
|
+
*
|
|
239
|
+
* Prefers an origin that has a SubstrateSearch token so Outlook/email and
|
|
240
|
+
* Substrate search keep working when both teams.cloud.microsoft and
|
|
241
|
+
* teams.microsoft.com are present in the saved session (common after the
|
|
242
|
+
* New Teams host migration).
|
|
219
243
|
*/
|
|
220
244
|
export function getTeamsOrigin(state) {
|
|
221
245
|
if (!state.origins)
|
|
222
246
|
return null;
|
|
247
|
+
const withSubstrate = state.origins.find(o => hasSubstrateSearchToken(o.localStorage));
|
|
248
|
+
if (withSubstrate)
|
|
249
|
+
return withSubstrate;
|
|
223
250
|
// Try known Teams origins in priority order
|
|
224
251
|
for (const knownOrigin of TEAMS_ORIGINS) {
|
|
225
252
|
const origin = state.origins.find(o => o.origin === knownOrigin);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for getTeamsOrigin — especially New Teams (teams.cloud.microsoft)
|
|
3
|
+
* SubstrateSearch token selection when multiple origins are present.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { getTeamsOrigin } from './session-store.js';
|
|
7
|
+
function makeOrigin(origin, localStorage = []) {
|
|
8
|
+
return { origin, localStorage };
|
|
9
|
+
}
|
|
10
|
+
function makeSubstrateEntry(target = 'https://substrate.office.com/SubstrateSearch-Internal.ReadWrite') {
|
|
11
|
+
return {
|
|
12
|
+
name: 'msal-substrate-token',
|
|
13
|
+
value: JSON.stringify({
|
|
14
|
+
credentialType: 'AccessToken',
|
|
15
|
+
target,
|
|
16
|
+
// JWT-shaped secret (header.payload.sig) — extractor only checks prefix
|
|
17
|
+
secret: 'eyJhbGciOiJub25lIn0.eyJzdWIiOiJ0ZXN0In0.sig',
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function makeChatSvcEntry() {
|
|
22
|
+
return {
|
|
23
|
+
name: 'msal-chatsvc-token',
|
|
24
|
+
value: JSON.stringify({
|
|
25
|
+
credentialType: 'AccessToken',
|
|
26
|
+
target: 'https://chatsvcagg.teams.microsoft.com/.default',
|
|
27
|
+
secret: 'eyJhbGciOiJub25lIn0.eyJzdWIiOiJjaGF0In0.sig',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
describe('getTeamsOrigin', () => {
|
|
32
|
+
it('prefers the origin that holds a SubstrateSearch token', () => {
|
|
33
|
+
const state = {
|
|
34
|
+
cookies: [],
|
|
35
|
+
origins: [
|
|
36
|
+
makeOrigin('https://teams.microsoft.com', [makeChatSvcEntry()]),
|
|
37
|
+
makeOrigin('https://teams.cloud.microsoft', [makeSubstrateEntry()]),
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
const chosen = getTeamsOrigin(state);
|
|
41
|
+
expect(chosen?.origin).toBe('https://teams.cloud.microsoft');
|
|
42
|
+
});
|
|
43
|
+
it('falls back to teams.cloud.microsoft when no Substrate token exists', () => {
|
|
44
|
+
const state = {
|
|
45
|
+
cookies: [],
|
|
46
|
+
origins: [
|
|
47
|
+
makeOrigin('https://teams.microsoft.com', [makeChatSvcEntry()]),
|
|
48
|
+
makeOrigin('https://teams.cloud.microsoft', [makeChatSvcEntry()]),
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
const chosen = getTeamsOrigin(state);
|
|
52
|
+
expect(chosen?.origin).toBe('https://teams.cloud.microsoft');
|
|
53
|
+
});
|
|
54
|
+
it('returns teams.microsoft.com when it is the only known origin', () => {
|
|
55
|
+
const state = {
|
|
56
|
+
cookies: [],
|
|
57
|
+
origins: [makeOrigin('https://teams.microsoft.com', [makeChatSvcEntry()])],
|
|
58
|
+
};
|
|
59
|
+
const chosen = getTeamsOrigin(state);
|
|
60
|
+
expect(chosen?.origin).toBe('https://teams.microsoft.com');
|
|
61
|
+
});
|
|
62
|
+
it('returns null when no Teams origins are present', () => {
|
|
63
|
+
const state = {
|
|
64
|
+
cookies: [],
|
|
65
|
+
origins: [makeOrigin('https://login.microsoftonline.com', [])],
|
|
66
|
+
};
|
|
67
|
+
expect(getTeamsOrigin(state)).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
});
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Teams MCP CLI
|
|
4
|
+
*
|
|
5
|
+
* A standalone command-line interface with full parity to the MCP server.
|
|
6
|
+
* It runs the same in-process server over an in-memory transport and speaks
|
|
7
|
+
* to it through the real MCP protocol, so every registered tool is callable.
|
|
8
|
+
*
|
|
9
|
+
* Usage (installed):
|
|
10
|
+
* msteams # List tools and check status
|
|
11
|
+
* msteams search "query" # Search for messages (shortcut)
|
|
12
|
+
* msteams teams_search --query "q" # Generic tool call
|
|
13
|
+
* msteams --json # Output as JSON
|
|
14
|
+
*
|
|
15
|
+
* Usage (from the repo):
|
|
16
|
+
* npm run cli -- search "query"
|
|
17
|
+
*
|
|
18
|
+
* Any unrecognised command is treated as a tool name. Use --key value for parameters.
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* MCP
|
|
3
|
+
* Teams MCP CLI
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* protocol
|
|
5
|
+
* A standalone command-line interface with full parity to the MCP server.
|
|
6
|
+
* It runs the same in-process server over an in-memory transport and speaks
|
|
7
|
+
* to it through the real MCP protocol, so every registered tool is callable.
|
|
8
8
|
*
|
|
9
|
-
* Usage:
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Usage (installed):
|
|
10
|
+
* msteams # List tools and check status
|
|
11
|
+
* msteams search "query" # Search for messages (shortcut)
|
|
12
|
+
* msteams teams_search --query "q" # Generic tool call
|
|
13
|
+
* msteams --json # Output as JSON
|
|
14
|
+
*
|
|
15
|
+
* Usage (from the repo):
|
|
16
|
+
* npm run cli -- search "query"
|
|
14
17
|
*
|
|
15
18
|
* Any unrecognised command is treated as a tool name. Use --key value for parameters.
|
|
16
19
|
*/
|
|
17
20
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
18
21
|
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
19
|
-
import { createServer } from '
|
|
22
|
+
import { createServer } from './server.js';
|
|
20
23
|
// Shortcuts map command names to tool names and parameter mappings
|
|
21
24
|
const SHORTCUTS = {
|
|
22
25
|
search: { tool: 'teams_search', primaryArg: 'query' },
|
|
@@ -160,7 +163,7 @@ async function createTestClient() {
|
|
|
160
163
|
// Connect the server to its transport
|
|
161
164
|
await server.connect(serverTransport);
|
|
162
165
|
// Create and connect the client
|
|
163
|
-
const client = new Client({ name: '
|
|
166
|
+
const client = new Client({ name: 'msteams', version: '1.0.0' }, { capabilities: {} });
|
|
164
167
|
await client.connect(clientTransport);
|
|
165
168
|
const cleanup = async () => {
|
|
166
169
|
await client.close();
|
|
@@ -438,8 +441,8 @@ function extractSenderName(sender) {
|
|
|
438
441
|
async function main() {
|
|
439
442
|
const parsed = parseArgs();
|
|
440
443
|
if (!parsed.json) {
|
|
441
|
-
console.log('\n
|
|
442
|
-
console.log('
|
|
444
|
+
console.log('\n💬 Teams MCP CLI');
|
|
445
|
+
console.log('================');
|
|
443
446
|
}
|
|
444
447
|
let cleanup = null;
|
|
445
448
|
try {
|
|
@@ -456,7 +459,7 @@ async function main() {
|
|
|
456
459
|
}
|
|
457
460
|
if (!parsed.json) {
|
|
458
461
|
logSection('Complete');
|
|
459
|
-
log('
|
|
462
|
+
log('Done.');
|
|
460
463
|
}
|
|
461
464
|
}
|
|
462
465
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "msteams-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "MCP server for Microsoft Teams - search messages, send replies, manage favourites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"msteams-mcp": "dist/index.js"
|
|
8
|
+
"msteams-mcp": "dist/index.js",
|
|
9
|
+
"msteams": "dist/cli.js"
|
|
9
10
|
},
|
|
10
11
|
"files": [
|
|
11
12
|
"dist",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"test": "vitest run",
|
|
30
31
|
"test:watch": "vitest",
|
|
31
32
|
"test:coverage": "vitest run --coverage",
|
|
32
|
-
"cli": "tsx src/
|
|
33
|
+
"cli": "tsx src/cli.ts",
|
|
33
34
|
"typecheck": "tsc --noEmit",
|
|
34
35
|
"lint": "eslint src/",
|
|
35
36
|
"lint:fix": "eslint src/ --fix"
|
package/dist/api/graph-api.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Microsoft Graph API - Spike for sending messages.
|
|
3
|
-
*
|
|
4
|
-
* This is a spike/experiment to test whether we can use the Graph API
|
|
5
|
-
* with tokens extracted from the Teams browser session (no Azure App
|
|
6
|
-
* registration required).
|
|
7
|
-
*
|
|
8
|
-
* Graph API send message endpoint:
|
|
9
|
-
* POST https://graph.microsoft.com/v1.0/chats/{chatId}/messages
|
|
10
|
-
*
|
|
11
|
-
* Note: Graph API uses a different chat ID format than chatsvc. The
|
|
12
|
-
* conversationId from Teams (e.g., "19:xxx@thread.v2") should work
|
|
13
|
-
* directly as the Graph chatId.
|
|
14
|
-
*/
|
|
15
|
-
import { type Result } from '../types/result.js';
|
|
16
|
-
/** Result of sending a message via Graph API. */
|
|
17
|
-
export interface GraphSendMessageResult {
|
|
18
|
-
/** The Graph-assigned message ID. */
|
|
19
|
-
id: string;
|
|
20
|
-
/** When the message was created (ISO 8601). */
|
|
21
|
-
createdDateTime?: string;
|
|
22
|
-
/** The web URL for the message (if returned). */
|
|
23
|
-
webUrl?: string;
|
|
24
|
-
/** Raw Graph API response for debugging in spike. */
|
|
25
|
-
_raw?: unknown;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Sends a message to a Teams chat via the Microsoft Graph API.
|
|
29
|
-
*
|
|
30
|
-
* This is a spike to test Graph API access using tokens from the Teams
|
|
31
|
-
* browser session. The Graph API endpoint is:
|
|
32
|
-
* POST /v1.0/chats/{chatId}/messages
|
|
33
|
-
*
|
|
34
|
-
* For channel messages, the endpoint would be:
|
|
35
|
-
* POST /v1.0/teams/{teamId}/channels/{channelId}/messages
|
|
36
|
-
*
|
|
37
|
-
* @param chatId - The Teams conversation ID (e.g., "19:xxx@thread.v2")
|
|
38
|
-
* @param content - The message content (plain text or HTML)
|
|
39
|
-
* @param contentType - "text" for plain text, "html" for HTML (default: "text")
|
|
40
|
-
*/
|
|
41
|
-
export declare function graphSendMessage(chatId: string, content: string, contentType?: 'text' | 'html'): Promise<Result<GraphSendMessageResult>>;
|
|
42
|
-
/**
|
|
43
|
-
* Sends a message to a Teams channel via the Microsoft Graph API.
|
|
44
|
-
*
|
|
45
|
-
* POST /v1.0/teams/{teamId}/channels/{channelId}/messages
|
|
46
|
-
*
|
|
47
|
-
* Note: For channels, we need the teamId (group ID) and channelId separately.
|
|
48
|
-
* This is different from the chatsvc API which uses a single conversationId.
|
|
49
|
-
*
|
|
50
|
-
* @param teamId - The team's group ID (GUID)
|
|
51
|
-
* @param channelId - The channel ID (e.g., "19:xxx@thread.tacv2")
|
|
52
|
-
* @param content - The message content
|
|
53
|
-
* @param contentType - "text" for plain text, "html" for HTML (default: "text")
|
|
54
|
-
*/
|
|
55
|
-
export declare function graphSendChannelMessage(teamId: string, channelId: string, content: string, contentType?: 'text' | 'html'): Promise<Result<GraphSendMessageResult>>;
|
package/dist/api/graph-api.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Microsoft Graph API - Spike for sending messages.
|
|
3
|
-
*
|
|
4
|
-
* This is a spike/experiment to test whether we can use the Graph API
|
|
5
|
-
* with tokens extracted from the Teams browser session (no Azure App
|
|
6
|
-
* registration required).
|
|
7
|
-
*
|
|
8
|
-
* Graph API send message endpoint:
|
|
9
|
-
* POST https://graph.microsoft.com/v1.0/chats/{chatId}/messages
|
|
10
|
-
*
|
|
11
|
-
* Note: Graph API uses a different chat ID format than chatsvc. The
|
|
12
|
-
* conversationId from Teams (e.g., "19:xxx@thread.v2") should work
|
|
13
|
-
* directly as the Graph chatId.
|
|
14
|
-
*/
|
|
15
|
-
import { httpRequest } from '../utils/http.js';
|
|
16
|
-
import { ErrorCode, createError } from '../types/errors.js';
|
|
17
|
-
import { ok, err } from '../types/result.js';
|
|
18
|
-
import { requireGraphAuth } from '../utils/auth-guards.js';
|
|
19
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
-
// Constants
|
|
21
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
const GRAPH_BASE_URL = 'https://graph.microsoft.com/v1.0';
|
|
23
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
-
// Send Message via Graph API
|
|
25
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
-
/**
|
|
27
|
-
* Sends a message to a Teams chat via the Microsoft Graph API.
|
|
28
|
-
*
|
|
29
|
-
* This is a spike to test Graph API access using tokens from the Teams
|
|
30
|
-
* browser session. The Graph API endpoint is:
|
|
31
|
-
* POST /v1.0/chats/{chatId}/messages
|
|
32
|
-
*
|
|
33
|
-
* For channel messages, the endpoint would be:
|
|
34
|
-
* POST /v1.0/teams/{teamId}/channels/{channelId}/messages
|
|
35
|
-
*
|
|
36
|
-
* @param chatId - The Teams conversation ID (e.g., "19:xxx@thread.v2")
|
|
37
|
-
* @param content - The message content (plain text or HTML)
|
|
38
|
-
* @param contentType - "text" for plain text, "html" for HTML (default: "text")
|
|
39
|
-
*/
|
|
40
|
-
export async function graphSendMessage(chatId, content, contentType = 'text') {
|
|
41
|
-
const authResult = requireGraphAuth();
|
|
42
|
-
if (!authResult.ok) {
|
|
43
|
-
return authResult;
|
|
44
|
-
}
|
|
45
|
-
const { graphToken } = authResult.value;
|
|
46
|
-
const url = `${GRAPH_BASE_URL}/chats/${encodeURIComponent(chatId)}/messages`;
|
|
47
|
-
const body = {
|
|
48
|
-
body: {
|
|
49
|
-
contentType,
|
|
50
|
-
content,
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
const response = await httpRequest(url, {
|
|
54
|
-
method: 'POST',
|
|
55
|
-
headers: {
|
|
56
|
-
'Content-Type': 'application/json',
|
|
57
|
-
'Authorization': `Bearer ${graphToken}`,
|
|
58
|
-
},
|
|
59
|
-
body: JSON.stringify(body),
|
|
60
|
-
// Don't retry auth errors - they indicate the token doesn't have the right scopes
|
|
61
|
-
maxRetries: 1,
|
|
62
|
-
});
|
|
63
|
-
if (!response.ok) {
|
|
64
|
-
// Enhance error message with Graph-specific context
|
|
65
|
-
const errorMessage = response.error.message;
|
|
66
|
-
// Check for common Graph API permission errors
|
|
67
|
-
if (errorMessage.includes('Authorization_RequestDenied') ||
|
|
68
|
-
errorMessage.includes('Forbidden') ||
|
|
69
|
-
errorMessage.includes('403')) {
|
|
70
|
-
return err(createError(ErrorCode.AUTH_REQUIRED, `Graph API permission denied. The token from the Teams session may not have Chat.ReadWrite scope. Error: ${errorMessage}`, {
|
|
71
|
-
retryable: false,
|
|
72
|
-
suggestions: [
|
|
73
|
-
'The Teams SPA client ID may not have delegated Chat.ReadWrite permission',
|
|
74
|
-
'Check the token scopes by decoding the JWT at jwt.ms',
|
|
75
|
-
],
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
return response;
|
|
79
|
-
}
|
|
80
|
-
const data = response.value.data;
|
|
81
|
-
// Check if the response is an error response
|
|
82
|
-
if ('error' in data && data.error) {
|
|
83
|
-
return err(createError(ErrorCode.API_ERROR, `Graph API error: ${data.error.code}: ${data.error.message}`, { retryable: false }));
|
|
84
|
-
}
|
|
85
|
-
const msgData = data;
|
|
86
|
-
if (!msgData.id) {
|
|
87
|
-
return err(createError(ErrorCode.UNKNOWN, 'Graph API returned success but no message ID', { retryable: false }));
|
|
88
|
-
}
|
|
89
|
-
return ok({
|
|
90
|
-
id: msgData.id,
|
|
91
|
-
createdDateTime: msgData.createdDateTime,
|
|
92
|
-
webUrl: msgData.webUrl,
|
|
93
|
-
_raw: msgData,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Sends a message to a Teams channel via the Microsoft Graph API.
|
|
98
|
-
*
|
|
99
|
-
* POST /v1.0/teams/{teamId}/channels/{channelId}/messages
|
|
100
|
-
*
|
|
101
|
-
* Note: For channels, we need the teamId (group ID) and channelId separately.
|
|
102
|
-
* This is different from the chatsvc API which uses a single conversationId.
|
|
103
|
-
*
|
|
104
|
-
* @param teamId - The team's group ID (GUID)
|
|
105
|
-
* @param channelId - The channel ID (e.g., "19:xxx@thread.tacv2")
|
|
106
|
-
* @param content - The message content
|
|
107
|
-
* @param contentType - "text" for plain text, "html" for HTML (default: "text")
|
|
108
|
-
*/
|
|
109
|
-
export async function graphSendChannelMessage(teamId, channelId, content, contentType = 'text') {
|
|
110
|
-
const authResult = requireGraphAuth();
|
|
111
|
-
if (!authResult.ok) {
|
|
112
|
-
return authResult;
|
|
113
|
-
}
|
|
114
|
-
const { graphToken } = authResult.value;
|
|
115
|
-
const url = `${GRAPH_BASE_URL}/teams/${encodeURIComponent(teamId)}/channels/${encodeURIComponent(channelId)}/messages`;
|
|
116
|
-
const body = {
|
|
117
|
-
body: {
|
|
118
|
-
contentType,
|
|
119
|
-
content,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
const response = await httpRequest(url, {
|
|
123
|
-
method: 'POST',
|
|
124
|
-
headers: {
|
|
125
|
-
'Content-Type': 'application/json',
|
|
126
|
-
'Authorization': `Bearer ${graphToken}`,
|
|
127
|
-
},
|
|
128
|
-
body: JSON.stringify(body),
|
|
129
|
-
maxRetries: 1,
|
|
130
|
-
});
|
|
131
|
-
if (!response.ok) {
|
|
132
|
-
return response;
|
|
133
|
-
}
|
|
134
|
-
const data = response.value.data;
|
|
135
|
-
if ('error' in data && data.error) {
|
|
136
|
-
return err(createError(ErrorCode.API_ERROR, `Graph API error: ${data.error.code}: ${data.error.message}`, { retryable: false }));
|
|
137
|
-
}
|
|
138
|
-
const msgData = data;
|
|
139
|
-
if (!msgData.id) {
|
|
140
|
-
return err(createError(ErrorCode.UNKNOWN, 'Graph API returned success but no message ID', { retryable: false }));
|
|
141
|
-
}
|
|
142
|
-
return ok({
|
|
143
|
-
id: msgData.id,
|
|
144
|
-
createdDateTime: msgData.createdDateTime,
|
|
145
|
-
webUrl: msgData.webUrl,
|
|
146
|
-
_raw: msgData,
|
|
147
|
-
});
|
|
148
|
-
}
|
package/dist/api/index.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API module exports.
|
|
3
|
-
*/
|
|
4
|
-
export * from './substrate-api.js';
|
|
5
|
-
export * from './chatsvc-api.js';
|
|
6
|
-
export * from './csa-api.js';
|
|
7
|
-
export * from './calendar-api.js';
|
|
8
|
-
export * from './transcript-api.js';
|
|
9
|
-
export * from './files-api.js';
|
|
10
|
-
export * from './tags-api.js';
|
|
11
|
-
export * from './profile-api.js';
|
package/dist/api/index.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API module exports.
|
|
3
|
-
*/
|
|
4
|
-
export * from './substrate-api.js';
|
|
5
|
-
export * from './chatsvc-api.js';
|
|
6
|
-
export * from './csa-api.js';
|
|
7
|
-
export * from './calendar-api.js';
|
|
8
|
-
export * from './transcript-api.js';
|
|
9
|
-
export * from './files-api.js';
|
|
10
|
-
export * from './tags-api.js';
|
|
11
|
-
export * from './profile-api.js';
|
package/dist/auth/index.d.ts
DELETED
package/dist/auth/index.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Import Microsoft SSO cookies from the user's Chrome profile into a Playwright context.
|
|
3
|
-
*
|
|
4
|
-
* When the Teams MCP server needs to open a visible browser for login,
|
|
5
|
-
* the Playwright profile is isolated from the user's real Chrome — so Microsoft
|
|
6
|
-
* can't recognise the user via SSO. This module copies the relevant Microsoft
|
|
7
|
-
* cookies from the user's actual Chrome work profile, enabling silent SSO in
|
|
8
|
-
* the Playwright browser and eliminating the need to re-type credentials.
|
|
9
|
-
*
|
|
10
|
-
* macOS only (Chrome cookies are encrypted with a Keychain-backed key).
|
|
11
|
-
* Fails gracefully on other platforms or when Chrome isn't available.
|
|
12
|
-
*
|
|
13
|
-
* Configuration:
|
|
14
|
-
* TEAMS_MCP_CHROME_PROFILE env var — Chrome profile directory name
|
|
15
|
-
* (e.g. "Profile 1"). If unset, auto-detects from Chrome's Local State.
|
|
16
|
-
*/
|
|
17
|
-
import type { BrowserContext } from 'playwright';
|
|
18
|
-
/**
|
|
19
|
-
* Imports Microsoft SSO cookies from the user's Chrome profile into a Playwright context.
|
|
20
|
-
*
|
|
21
|
-
* This enables SSO in the Playwright browser so the user doesn't have to type
|
|
22
|
-
* credentials when Microsoft redirects to the login page.
|
|
23
|
-
*
|
|
24
|
-
* Fails silently — cookie import is best-effort. If it doesn't work,
|
|
25
|
-
* the user just has to log in manually as before.
|
|
26
|
-
*/
|
|
27
|
-
export declare function importMicrosoftCookies(context: BrowserContext): Promise<void>;
|