mcp-teams-reader 1.0.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 ADDED
@@ -0,0 +1,81 @@
1
+ # teams-mcp-server
2
+
3
+ MCP Server for Microsoft Teams — search, read and summarize messages via Claude / AI assistants.
4
+
5
+ ## Features
6
+
7
+ - List joined Teams and channels
8
+ - Read channel messages (with time range filter)
9
+ - List and read 1:1 / group chats
10
+ - Search messages by keyword across chats and channels
11
+ - Deep links to open messages in Teams
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ npx teams-mcp-server
17
+ ```
18
+
19
+ ## Setup
20
+
21
+ ### 1. Register an Azure AD App
22
+
23
+ 1. Go to [Azure Portal](https://portal.azure.com) → **App registrations** → **New registration**
24
+ 2. Set **Supported account types** to your org (single tenant)
25
+ 3. Under **Authentication**, enable **Allow public client flows** (for Device Code Flow)
26
+ 4. Under **API permissions**, add these **Delegated** permissions for Microsoft Graph:
27
+ - `Chat.Read`
28
+ - `ChannelMessage.Read.All`
29
+ - `Team.ReadBasic.All`
30
+ - `Channel.ReadBasic.All`
31
+ - `User.Read`
32
+ 5. Click **Grant admin consent** (or ask your admin)
33
+
34
+ ### 2. Configure MCP Client
35
+
36
+ Add to your MCP client config (e.g. Claude Code `settings.json` or `claude_desktop_config.json`):
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "teams": {
42
+ "command": "npx",
43
+ "args": ["teams-mcp-server"],
44
+ "env": {
45
+ "TEAMS_CLIENT_ID": "your-azure-app-client-id",
46
+ "TEAMS_TENANT_ID": "your-azure-tenant-id"
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### 3. Authenticate
54
+
55
+ On first use, any tool call will return a Device Code login prompt. Open the URL in your browser, enter the code, and sign in. The token is cached locally for future use.
56
+
57
+ ## Available Tools
58
+
59
+ | Tool | Description |
60
+ |------|-------------|
61
+ | `list_my_teams` | List all Teams you've joined |
62
+ | `list_channels` | List channels in a team |
63
+ | `get_channel_messages` | Get channel messages (with optional time filter) |
64
+ | `list_my_chats` | List recent 1:1 and group chats |
65
+ | `get_chat_messages` | Get messages from a chat |
66
+ | `search_messages` | Search messages by keyword across chats |
67
+ | `search_channel_messages` | Search messages by keyword in a channel |
68
+
69
+ ## Development
70
+
71
+ ```bash
72
+ git clone <repo-url>
73
+ cd teams-mcp-server
74
+ npm install
75
+ cp .env.example .env # fill in your Client ID and Tenant ID
76
+ npm run dev
77
+ ```
78
+
79
+ ## License
80
+
81
+ MIT
package/dist/auth.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Error thrown when device code authentication is needed.
3
+ * The message contains the device code instructions for the user.
4
+ */
5
+ export declare class DeviceCodeRequiredError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ /**
9
+ * Get an access token, using cached token if available.
10
+ * If device code auth is needed, throws DeviceCodeRequiredError
11
+ * with the auth instructions, and starts the flow in the background.
12
+ * On next call, if auth completed, returns the token.
13
+ */
14
+ export declare function getAccessToken(): Promise<string>;
package/dist/auth.js ADDED
@@ -0,0 +1,128 @@
1
+ import { PublicClientApplication, } from "@azure/msal-node";
2
+ import { readFile, writeFile } from "fs/promises";
3
+ import { existsSync } from "fs";
4
+ import path from "path";
5
+ const TOKEN_CACHE_PATH = path.join(import.meta.dirname ?? ".", "..", "token-cache.json");
6
+ const SCOPES = [
7
+ "Chat.Read",
8
+ "ChannelMessage.Read.All",
9
+ "Team.ReadBasic.All",
10
+ "Channel.ReadBasic.All",
11
+ "User.Read",
12
+ ];
13
+ let msalClient = null;
14
+ // Background auth state
15
+ let pendingAuthPromise = null;
16
+ let pendingDeviceCodeMessage = null;
17
+ function getConfig() {
18
+ const clientId = process.env.TEAMS_CLIENT_ID;
19
+ const tenantId = process.env.TEAMS_TENANT_ID;
20
+ if (!clientId || !tenantId) {
21
+ throw new Error("Missing TEAMS_CLIENT_ID or TEAMS_TENANT_ID environment variables");
22
+ }
23
+ return { clientId, tenantId };
24
+ }
25
+ async function getMsalClient() {
26
+ if (msalClient)
27
+ return msalClient;
28
+ const { clientId, tenantId } = getConfig();
29
+ msalClient = new PublicClientApplication({
30
+ auth: {
31
+ clientId,
32
+ authority: `https://login.microsoftonline.com/${tenantId}`,
33
+ },
34
+ cache: {
35
+ cachePlugin: {
36
+ beforeCacheAccess: async (ctx) => {
37
+ if (existsSync(TOKEN_CACHE_PATH)) {
38
+ const data = await readFile(TOKEN_CACHE_PATH, "utf-8");
39
+ ctx.tokenCache.deserialize(data);
40
+ }
41
+ },
42
+ afterCacheAccess: async (ctx) => {
43
+ if (ctx.cacheHasChanged) {
44
+ await writeFile(TOKEN_CACHE_PATH, ctx.tokenCache.serialize(), "utf-8");
45
+ }
46
+ },
47
+ },
48
+ },
49
+ });
50
+ return msalClient;
51
+ }
52
+ /**
53
+ * Error thrown when device code authentication is needed.
54
+ * The message contains the device code instructions for the user.
55
+ */
56
+ export class DeviceCodeRequiredError extends Error {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = "DeviceCodeRequiredError";
60
+ }
61
+ }
62
+ /**
63
+ * Get an access token, using cached token if available.
64
+ * If device code auth is needed, throws DeviceCodeRequiredError
65
+ * with the auth instructions, and starts the flow in the background.
66
+ * On next call, if auth completed, returns the token.
67
+ */
68
+ export async function getAccessToken() {
69
+ const client = await getMsalClient();
70
+ // Try silent acquisition first (cached token)
71
+ const accounts = await client.getTokenCache().getAllAccounts();
72
+ if (accounts.length > 0) {
73
+ try {
74
+ const result = await client.acquireTokenSilent({
75
+ account: accounts[0],
76
+ scopes: SCOPES,
77
+ });
78
+ if (result?.accessToken)
79
+ return result.accessToken;
80
+ }
81
+ catch {
82
+ // Silent acquisition failed, fall through to device code
83
+ }
84
+ }
85
+ // If there's a pending auth, check if it completed
86
+ if (pendingAuthPromise) {
87
+ try {
88
+ const token = await Promise.race([
89
+ pendingAuthPromise,
90
+ // Give it 2 seconds to check if already done
91
+ new Promise((_, reject) => setTimeout(() => reject(new Error("still_pending")), 2000)),
92
+ ]);
93
+ pendingAuthPromise = null;
94
+ pendingDeviceCodeMessage = null;
95
+ return token;
96
+ }
97
+ catch (err) {
98
+ if (err?.message === "still_pending") {
99
+ // Still waiting for user to authenticate
100
+ throw new DeviceCodeRequiredError(`⏳ 认证进行中,请先在浏览器中完成登录,然后再试一次。\n\n${pendingDeviceCodeMessage ?? ""}`);
101
+ }
102
+ // Auth failed, reset and try again
103
+ pendingAuthPromise = null;
104
+ pendingDeviceCodeMessage = null;
105
+ }
106
+ }
107
+ // Start device code flow in background
108
+ const deviceCodeRequest = {
109
+ scopes: SCOPES,
110
+ deviceCodeCallback: (response) => {
111
+ pendingDeviceCodeMessage = response.message;
112
+ },
113
+ };
114
+ pendingAuthPromise = client
115
+ .acquireTokenByDeviceCode(deviceCodeRequest)
116
+ .then((result) => {
117
+ if (!result?.accessToken) {
118
+ throw new Error("Failed to acquire access token");
119
+ }
120
+ return result.accessToken;
121
+ });
122
+ // Wait briefly for the device code callback to fire
123
+ await new Promise((resolve) => setTimeout(resolve, 3000));
124
+ const message = pendingDeviceCodeMessage
125
+ ? `🔐 需要登录 Microsoft 账号:\n\n${pendingDeviceCodeMessage}\n\n完成登录后,请再次调用此工具。`
126
+ : "🔐 正在获取认证信息,请稍后再试...";
127
+ throw new DeviceCodeRequiredError(message);
128
+ }
@@ -0,0 +1,57 @@
1
+ interface Team {
2
+ id: string;
3
+ displayName: string;
4
+ description?: string;
5
+ }
6
+ interface Channel {
7
+ id: string;
8
+ displayName: string;
9
+ description?: string;
10
+ webUrl?: string;
11
+ }
12
+ interface ChatMessage {
13
+ id: string;
14
+ createdDateTime: string;
15
+ body: {
16
+ content: string;
17
+ contentType: string;
18
+ };
19
+ from?: {
20
+ user?: {
21
+ displayName: string;
22
+ id: string;
23
+ };
24
+ };
25
+ webUrl?: string;
26
+ channelIdentity?: {
27
+ teamId: string;
28
+ channelId: string;
29
+ };
30
+ }
31
+ interface Chat {
32
+ id: string;
33
+ topic?: string;
34
+ chatType: string;
35
+ lastUpdatedDateTime?: string;
36
+ members?: Array<{
37
+ displayName?: string;
38
+ }>;
39
+ }
40
+ /** List all teams the user is a member of */
41
+ export declare function listMyTeams(): Promise<Team[]>;
42
+ /** List channels in a team */
43
+ export declare function listChannels(teamId: string): Promise<Channel[]>;
44
+ /** Get messages from a team channel, optionally filtered by date */
45
+ export declare function getChannelMessages(teamId: string, channelId: string, since?: string, top?: number): Promise<ChatMessage[]>;
46
+ /** List the user's 1:1 and group chats */
47
+ export declare function listMyChats(top?: number): Promise<Chat[]>;
48
+ /** Get messages from a 1:1 or group chat */
49
+ export declare function getChatMessages(chatId: string, since?: string, top?: number): Promise<ChatMessage[]>;
50
+ /** Search messages across chats using keyword */
51
+ export declare function searchMessages(keyword: string, daysBack?: number): Promise<Array<ChatMessage & {
52
+ chatId: string;
53
+ chatTopic?: string;
54
+ }>>;
55
+ /** Build a deep link to a Teams message */
56
+ export declare function buildTeamsLink(teamId: string, channelId: string, messageId: string): string;
57
+ export {};
package/dist/graph.js ADDED
@@ -0,0 +1,78 @@
1
+ import { getAccessToken } from "./auth.js";
2
+ const GRAPH_BASE = "https://graph.microsoft.com/v1.0";
3
+ async function graphFetch(endpoint) {
4
+ const token = await getAccessToken();
5
+ const res = await fetch(`${GRAPH_BASE}${endpoint}`, {
6
+ headers: { Authorization: `Bearer ${token}` },
7
+ });
8
+ if (!res.ok) {
9
+ const errText = await res.text();
10
+ throw new Error(`Graph API error ${res.status}: ${errText}`);
11
+ }
12
+ return res.json();
13
+ }
14
+ /** List all teams the user is a member of */
15
+ export async function listMyTeams() {
16
+ const data = await graphFetch("/me/joinedTeams");
17
+ return data.value;
18
+ }
19
+ /** List channels in a team */
20
+ export async function listChannels(teamId) {
21
+ const data = await graphFetch(`/teams/${teamId}/channels`);
22
+ return data.value;
23
+ }
24
+ /** Get messages from a team channel, optionally filtered by date */
25
+ export async function getChannelMessages(teamId, channelId, since, top = 50) {
26
+ let endpoint = `/teams/${teamId}/channels/${channelId}/messages?$top=${top}`;
27
+ const data = await graphFetch(endpoint);
28
+ let messages = data.value;
29
+ // Client-side filter by date if `since` is provided
30
+ if (since) {
31
+ const sinceDate = new Date(since);
32
+ messages = messages.filter((m) => new Date(m.createdDateTime) >= sinceDate);
33
+ }
34
+ return messages;
35
+ }
36
+ /** List the user's 1:1 and group chats */
37
+ export async function listMyChats(top = 50) {
38
+ const data = await graphFetch(`/me/chats?$top=${top}`);
39
+ return data.value;
40
+ }
41
+ /** Get messages from a 1:1 or group chat */
42
+ export async function getChatMessages(chatId, since, top = 50) {
43
+ const data = await graphFetch(`/me/chats/${chatId}/messages?$top=${top}`);
44
+ let messages = data.value;
45
+ if (since) {
46
+ const sinceDate = new Date(since);
47
+ messages = messages.filter((m) => new Date(m.createdDateTime) >= sinceDate);
48
+ }
49
+ return messages;
50
+ }
51
+ /** Search messages across chats using keyword */
52
+ export async function searchMessages(keyword, daysBack = 7) {
53
+ const chats = await listMyChats(50);
54
+ const since = new Date();
55
+ since.setDate(since.getDate() - daysBack);
56
+ const sinceISO = since.toISOString();
57
+ const results = [];
58
+ const lowerKeyword = keyword.toLowerCase();
59
+ // Search through recent chats
60
+ for (const chat of chats.slice(0, 20)) {
61
+ try {
62
+ const messages = await getChatMessages(chat.id, sinceISO, 50);
63
+ for (const msg of messages) {
64
+ if (msg.body.content.toLowerCase().includes(lowerKeyword)) {
65
+ results.push({ ...msg, chatId: chat.id, chatTopic: chat.topic ?? undefined });
66
+ }
67
+ }
68
+ }
69
+ catch {
70
+ // Skip chats that error out
71
+ }
72
+ }
73
+ return results;
74
+ }
75
+ /** Build a deep link to a Teams message */
76
+ export function buildTeamsLink(teamId, channelId, messageId) {
77
+ return `https://teams.microsoft.com/l/message/${channelId}/${messageId}?tenantId=&groupId=${teamId}&parentMessageId=${messageId}`;
78
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { listMyTeams, listChannels, getChannelMessages, listMyChats, getChatMessages, searchMessages, buildTeamsLink, } from "./graph.js";
6
+ import { DeviceCodeRequiredError } from "./auth.js";
7
+ function handleError(err) {
8
+ if (err instanceof DeviceCodeRequiredError) {
9
+ return { content: [{ type: "text", text: err.message }], isError: true };
10
+ }
11
+ const msg = err instanceof Error ? err.message : String(err);
12
+ return { content: [{ type: "text", text: `错误: ${msg}` }], isError: true };
13
+ }
14
+ function stripHtml(html) {
15
+ return html
16
+ .replace(/<[^>]*>/g, "")
17
+ .replace(/&nbsp;/g, " ")
18
+ .replace(/&amp;/g, "&")
19
+ .replace(/&lt;/g, "<")
20
+ .replace(/&gt;/g, ">")
21
+ .replace(/&quot;/g, '"')
22
+ .trim();
23
+ }
24
+ function formatDate(iso) {
25
+ return new Date(iso).toLocaleString("zh-CN", { timeZone: "Asia/Tokyo" });
26
+ }
27
+ const server = new McpServer({
28
+ name: "teams-mcp-server",
29
+ version: "1.0.0",
30
+ });
31
+ // ============ Tool: list_my_teams ============
32
+ server.tool("list_my_teams", "列出我加入的所有 Teams 团队", {}, async () => {
33
+ try {
34
+ const teams = await listMyTeams();
35
+ const text = teams
36
+ .map((t) => `- **${t.displayName}** (ID: ${t.id})${t.description ? ` — ${t.description}` : ""}`)
37
+ .join("\n");
38
+ return {
39
+ content: [{ type: "text", text: text || "没有找到任何团队。" }],
40
+ };
41
+ }
42
+ catch (err) {
43
+ return handleError(err);
44
+ }
45
+ });
46
+ // ============ Tool: list_channels ============
47
+ server.tool("list_channels", "列出某个团队下的所有频道", { team_id: z.string().describe("团队 ID,可通过 list_my_teams 获取") }, async ({ team_id }) => {
48
+ try {
49
+ const channels = await listChannels(team_id);
50
+ const text = channels
51
+ .map((c) => `- **${c.displayName}** (ID: ${c.id})${c.description ? ` — ${c.description}` : ""}`)
52
+ .join("\n");
53
+ return {
54
+ content: [{ type: "text", text: text || "该团队没有频道。" }],
55
+ };
56
+ }
57
+ catch (err) {
58
+ return handleError(err);
59
+ }
60
+ });
61
+ // ============ Tool: get_channel_messages ============
62
+ server.tool("get_channel_messages", "获取团队频道的消息,可指定时间范围。用于总结频道讨论内容。", {
63
+ team_id: z.string().describe("团队 ID"),
64
+ channel_id: z.string().describe("频道 ID"),
65
+ since: z
66
+ .string()
67
+ .optional()
68
+ .describe("起始时间 ISO 格式,如 2026-03-06T00:00:00Z,不填则获取最近消息"),
69
+ limit: z.number().optional().default(50).describe("最大消息数,默认 50"),
70
+ }, async ({ team_id, channel_id, since, limit }) => {
71
+ try {
72
+ const messages = await getChannelMessages(team_id, channel_id, since, limit);
73
+ if (messages.length === 0) {
74
+ return { content: [{ type: "text", text: "该时间范围内没有消息。" }] };
75
+ }
76
+ const text = messages
77
+ .map((m) => {
78
+ const sender = m.from?.user?.displayName ?? "Unknown";
79
+ const time = formatDate(m.createdDateTime);
80
+ const body = stripHtml(m.body.content);
81
+ const link = buildTeamsLink(team_id, channel_id, m.id);
82
+ return `**${sender}** (${time}):\n${body}\n[打开消息](${link})`;
83
+ })
84
+ .join("\n\n---\n\n");
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: `找到 ${messages.length} 条消息:\n\n${text}`,
90
+ },
91
+ ],
92
+ };
93
+ }
94
+ catch (err) {
95
+ return handleError(err);
96
+ }
97
+ });
98
+ // ============ Tool: list_my_chats ============
99
+ server.tool("list_my_chats", "列出我最近的私聊和群聊", {
100
+ limit: z.number().optional().default(20).describe("返回数量,默认 20"),
101
+ }, async ({ limit }) => {
102
+ try {
103
+ const chats = await listMyChats(limit);
104
+ const text = chats
105
+ .map((c) => {
106
+ const topic = c.topic || `(${c.chatType} chat)`;
107
+ const updated = c.lastUpdatedDateTime
108
+ ? formatDate(c.lastUpdatedDateTime)
109
+ : "";
110
+ return `- **${topic}** (ID: ${c.id}) — 最后更新: ${updated}`;
111
+ })
112
+ .join("\n");
113
+ return {
114
+ content: [{ type: "text", text: text || "没有找到聊天。" }],
115
+ };
116
+ }
117
+ catch (err) {
118
+ return handleError(err);
119
+ }
120
+ });
121
+ // ============ Tool: get_chat_messages ============
122
+ server.tool("get_chat_messages", "获取某个私聊/群聊的消息", {
123
+ chat_id: z.string().describe("聊天 ID,可通过 list_my_chats 获取"),
124
+ since: z.string().optional().describe("起始时间 ISO 格式"),
125
+ limit: z.number().optional().default(50).describe("最大消息数"),
126
+ }, async ({ chat_id, since, limit }) => {
127
+ try {
128
+ const messages = await getChatMessages(chat_id, since, limit);
129
+ if (messages.length === 0) {
130
+ return { content: [{ type: "text", text: "没有消息。" }] };
131
+ }
132
+ const text = messages
133
+ .map((m) => {
134
+ const sender = m.from?.user?.displayName ?? "Unknown";
135
+ const time = formatDate(m.createdDateTime);
136
+ const body = stripHtml(m.body.content);
137
+ return `**${sender}** (${time}):\n${body}`;
138
+ })
139
+ .join("\n\n---\n\n");
140
+ return {
141
+ content: [{ type: "text", text: `${messages.length} 条消息:\n\n${text}` }],
142
+ };
143
+ }
144
+ catch (err) {
145
+ return handleError(err);
146
+ }
147
+ });
148
+ // ============ Tool: search_messages ============
149
+ server.tool("search_messages", "按关键词搜索最近的聊天消息(搜索私聊和群聊)", {
150
+ keyword: z.string().describe("搜索关键词"),
151
+ days_back: z.number().optional().default(7).describe("搜索最近几天,默认 7 天"),
152
+ }, async ({ keyword, days_back }) => {
153
+ try {
154
+ const results = await searchMessages(keyword, days_back);
155
+ if (results.length === 0) {
156
+ return {
157
+ content: [
158
+ { type: "text", text: `没有找到包含"${keyword}"的消息。` },
159
+ ],
160
+ };
161
+ }
162
+ const text = results
163
+ .map((m) => {
164
+ const sender = m.from?.user?.displayName ?? "Unknown";
165
+ const time = formatDate(m.createdDateTime);
166
+ const body = stripHtml(m.body.content);
167
+ const chatInfo = m.chatTopic ? `[${m.chatTopic}]` : `[Chat: ${m.chatId.slice(0, 8)}...]`;
168
+ const link = m.webUrl
169
+ ? m.webUrl
170
+ : `https://teams.microsoft.com/l/message/${m.chatId}/${m.id}?context=${encodeURIComponent(JSON.stringify({ contextType: "chat" }))}`;
171
+ return `${chatInfo} **${sender}** (${time}):\n${body}\n🔗 ${link}`;
172
+ })
173
+ .join("\n\n---\n\n");
174
+ return {
175
+ content: [
176
+ {
177
+ type: "text",
178
+ text: `找到 ${results.length} 条包含"${keyword}"的消息:\n\n${text}`,
179
+ },
180
+ ],
181
+ };
182
+ }
183
+ catch (err) {
184
+ return handleError(err);
185
+ }
186
+ });
187
+ // ============ Tool: search_channel_messages ============
188
+ server.tool("search_channel_messages", "在指定团队频道中按关键词搜索消息", {
189
+ team_id: z.string().describe("团队 ID"),
190
+ channel_id: z.string().describe("频道 ID"),
191
+ keyword: z.string().describe("搜索关键词"),
192
+ since: z.string().optional().describe("起始时间 ISO 格式"),
193
+ }, async ({ team_id, channel_id, keyword, since }) => {
194
+ try {
195
+ const messages = await getChannelMessages(team_id, channel_id, since, 50);
196
+ const lowerKeyword = keyword.toLowerCase();
197
+ const matched = messages.filter((m) => stripHtml(m.body.content).toLowerCase().includes(lowerKeyword));
198
+ if (matched.length === 0) {
199
+ return {
200
+ content: [
201
+ { type: "text", text: `频道中没有找到包含"${keyword}"的消息。` },
202
+ ],
203
+ };
204
+ }
205
+ const text = matched
206
+ .map((m) => {
207
+ const sender = m.from?.user?.displayName ?? "Unknown";
208
+ const time = formatDate(m.createdDateTime);
209
+ const body = stripHtml(m.body.content);
210
+ const link = buildTeamsLink(team_id, channel_id, m.id);
211
+ return `**${sender}** (${time}):\n${body}\n[打开消息](${link})`;
212
+ })
213
+ .join("\n\n---\n\n");
214
+ return {
215
+ content: [
216
+ {
217
+ type: "text",
218
+ text: `找到 ${matched.length} 条匹配消息:\n\n${text}`,
219
+ },
220
+ ],
221
+ };
222
+ }
223
+ catch (err) {
224
+ return handleError(err);
225
+ }
226
+ });
227
+ // ============ Start Server ============
228
+ async function main() {
229
+ const transport = new StdioServerTransport();
230
+ await server.connect(transport);
231
+ console.error("Teams MCP Server is running");
232
+ }
233
+ main().catch((err) => {
234
+ console.error("Fatal error:", err);
235
+ process.exit(1);
236
+ });
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "mcp-teams-reader",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server for Microsoft Teams - search, read and summarize messages",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mcp-teams-reader": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "dev": "tsx src/index.ts",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "microsoft-teams",
22
+ "teams",
23
+ "claude",
24
+ "ai",
25
+ "model-context-protocol"
26
+ ],
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": ""
31
+ },
32
+ "dependencies": {
33
+ "@azure/msal-node": "^2.16.0",
34
+ "@modelcontextprotocol/sdk": "^1.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.0.0",
38
+ "tsx": "^4.19.0",
39
+ "typescript": "^5.6.0"
40
+ }
41
+ }