Skip to content

Commit 094ba46

Browse files
committed
Implement Phase 3: Teams MCP Integration
This commit implements comprehensive Microsoft Teams integration via Model Context Protocol: **New Modules:** - Teams MCP Module with complete message and recording retrieval - McpTeamsService: Core service for Teams operations via MCP - TeamsRecordingProcessor: Background meeting recording processing - McpTeamsController: REST API endpoints for Teams MCP operations **Key Features:** - Team and channel listing using MCP tools - Channel message retrieval with pagination - Message reply support - Team member and user information retrieval - Online meeting discovery - Meeting recording retrieval and processing - Automated recording transcription workflow - Integration with existing NLP processing pipeline - Bulk sync capabilities for historical data **API Endpoints:** - GET /teams-mcp/status - Check MCP availability - GET /teams-mcp/teams - List all teams - GET /teams-mcp/teams/:id/channels - List team channels - GET /teams-mcp/teams/:id/members - Get team members - GET /teams-mcp/messages - Get channel messages - POST /teams-mcp/sync - Sync channel to database - GET /teams-mcp/users/:id - Get user info - GET /teams-mcp/meetings - Get online meetings - GET /teams-mcp/recordings - Get meeting recordings - POST /teams-mcp/recordings/process - Process recording - POST /teams-mcp/recordings/sync - Bulk recording sync **Technical Details:** - Proper error handling and retry logic - Integration with Bull queues for async processing - Uses existing TranscriptionService for Azure Speech integration - Automatic NLP processing after message/recording storage - TypeScript interfaces for all Teams entities - DTOs with validation for API endpoints - Parser methods for Graph API response normalization All code compiles successfully and follows existing patterns.
1 parent 0133acf commit 094ba46

File tree

8 files changed

+1507
-0
lines changed

8 files changed

+1507
-0
lines changed

backend/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { AdminModule } from './modules/admin/admin.module';
1818
import { AnalyticsModule } from './modules/analytics/analytics.module';
1919
import { MCPModule } from './modules/mcp/mcp.module';
2020
import { McpSlackModule } from './modules/mcp-slack/mcp-slack.module';
21+
import { McpTeamsModule } from './modules/mcp-teams/mcp-teams.module';
2122

2223
@Module({
2324
imports: [
@@ -57,6 +58,7 @@ import { McpSlackModule } from './modules/mcp-slack/mcp-slack.module';
5758
AnalyticsModule,
5859
MCPModule,
5960
McpSlackModule,
61+
McpTeamsModule,
6062
],
6163
controllers: [AppController],
6264
providers: [AppService],
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
import {
2+
Controller,
3+
Get,
4+
Post,
5+
Body,
6+
Query,
7+
Param,
8+
HttpException,
9+
HttpStatus,
10+
} from '@nestjs/common';
11+
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
12+
import { McpTeamsService } from '../services/mcp-teams.service';
13+
import {
14+
SyncChannelDto,
15+
GetMessagesDto,
16+
GetMeetingsDto,
17+
ProcessRecordingDto,
18+
SyncRecordingsDto,
19+
GetTeamMembersDto,
20+
} from '../dto/teams-mcp.dto';
21+
22+
/**
23+
* Teams MCP Controller
24+
* REST API endpoints for Microsoft Teams MCP integration
25+
*/
26+
@ApiTags('teams-mcp')
27+
@Controller('teams-mcp')
28+
export class McpTeamsController {
29+
constructor(private readonly mcpTeamsService: McpTeamsService) {}
30+
31+
/**
32+
* Check if Teams MCP is available
33+
*/
34+
@Get('status')
35+
@ApiOperation({ summary: 'Check Teams MCP status' })
36+
@ApiResponse({ status: 200, description: 'MCP status retrieved' })
37+
async getStatus() {
38+
const isAvailable = this.mcpTeamsService.isAvailable();
39+
40+
return {
41+
available: isAvailable,
42+
server: 'teams',
43+
timestamp: new Date().toISOString(),
44+
};
45+
}
46+
47+
/**
48+
* List all Teams
49+
*/
50+
@Get('teams')
51+
@ApiOperation({ summary: 'List all Microsoft Teams' })
52+
@ApiResponse({ status: 200, description: 'Teams retrieved successfully' })
53+
async listTeams() {
54+
try {
55+
const teams = await this.mcpTeamsService.listTeams();
56+
57+
return {
58+
success: true,
59+
count: teams.length,
60+
teams,
61+
};
62+
} catch (error) {
63+
throw new HttpException(
64+
{
65+
success: false,
66+
message: 'Failed to list teams',
67+
error: error instanceof Error ? error.message : 'Unknown error',
68+
},
69+
HttpStatus.INTERNAL_SERVER_ERROR,
70+
);
71+
}
72+
}
73+
74+
/**
75+
* List channels in a team
76+
*/
77+
@Get('teams/:teamId/channels')
78+
@ApiOperation({ summary: 'List channels in a Microsoft Team' })
79+
@ApiResponse({ status: 200, description: 'Channels retrieved successfully' })
80+
async listChannels(@Param('teamId') teamId: string) {
81+
try {
82+
const channels = await this.mcpTeamsService.listChannels(teamId);
83+
84+
return {
85+
success: true,
86+
teamId,
87+
count: channels.length,
88+
channels,
89+
};
90+
} catch (error) {
91+
throw new HttpException(
92+
{
93+
success: false,
94+
message: `Failed to list channels for team ${teamId}`,
95+
error: error instanceof Error ? error.message : 'Unknown error',
96+
},
97+
HttpStatus.INTERNAL_SERVER_ERROR,
98+
);
99+
}
100+
}
101+
102+
/**
103+
* Get channel messages
104+
*/
105+
@Get('messages')
106+
@ApiOperation({ summary: 'Get messages from a Teams channel' })
107+
@ApiResponse({ status: 200, description: 'Messages retrieved successfully' })
108+
async getMessages(@Query() query: GetMessagesDto) {
109+
try {
110+
const messages = await this.mcpTeamsService.getChannelMessages(
111+
query.teamId,
112+
query.channelId,
113+
{
114+
top: query.top,
115+
skip: query.skip,
116+
},
117+
);
118+
119+
return {
120+
success: true,
121+
teamId: query.teamId,
122+
channelId: query.channelId,
123+
count: messages.length,
124+
messages,
125+
};
126+
} catch (error) {
127+
throw new HttpException(
128+
{
129+
success: false,
130+
message: 'Failed to get messages',
131+
error: error instanceof Error ? error.message : 'Unknown error',
132+
},
133+
HttpStatus.INTERNAL_SERVER_ERROR,
134+
);
135+
}
136+
}
137+
138+
/**
139+
* Sync channel messages to database
140+
*/
141+
@Post('sync')
142+
@ApiOperation({ summary: 'Sync Teams channel messages to database' })
143+
@ApiResponse({ status: 200, description: 'Sync completed successfully' })
144+
async syncChannel(@Body() dto: SyncChannelDto) {
145+
try {
146+
const result = await this.mcpTeamsService.syncChannelHistory(dto.teamId, dto.channelId, {
147+
top: dto.top,
148+
filter: dto.filter,
149+
});
150+
151+
return {
152+
success: true,
153+
result,
154+
};
155+
} catch (error) {
156+
throw new HttpException(
157+
{
158+
success: false,
159+
message: 'Sync failed',
160+
error: error instanceof Error ? error.message : 'Unknown error',
161+
},
162+
HttpStatus.INTERNAL_SERVER_ERROR,
163+
);
164+
}
165+
}
166+
167+
/**
168+
* Get team members
169+
*/
170+
@Get('teams/:teamId/members')
171+
@ApiOperation({ summary: 'Get members of a Microsoft Team' })
172+
@ApiResponse({ status: 200, description: 'Members retrieved successfully' })
173+
async getTeamMembers(@Param() params: GetTeamMembersDto) {
174+
try {
175+
const members = await this.mcpTeamsService.getTeamMembers(params.teamId);
176+
177+
return {
178+
success: true,
179+
teamId: params.teamId,
180+
count: members.length,
181+
members,
182+
};
183+
} catch (error) {
184+
throw new HttpException(
185+
{
186+
success: false,
187+
message: `Failed to get members for team ${params.teamId}`,
188+
error: error instanceof Error ? error.message : 'Unknown error',
189+
},
190+
HttpStatus.INTERNAL_SERVER_ERROR,
191+
);
192+
}
193+
}
194+
195+
/**
196+
* Get user info
197+
*/
198+
@Get('users/:userId')
199+
@ApiOperation({ summary: 'Get Microsoft Teams user information' })
200+
@ApiResponse({ status: 200, description: 'User info retrieved' })
201+
async getUserInfo(@Param('userId') userId: string) {
202+
try {
203+
const user = await this.mcpTeamsService.getUserInfo(userId);
204+
205+
return {
206+
success: true,
207+
user,
208+
};
209+
} catch (error) {
210+
throw new HttpException(
211+
{
212+
success: false,
213+
message: `Failed to get user info for ${userId}`,
214+
error: error instanceof Error ? error.message : 'Unknown error',
215+
},
216+
HttpStatus.INTERNAL_SERVER_ERROR,
217+
);
218+
}
219+
}
220+
221+
/**
222+
* Get online meetings
223+
*/
224+
@Get('meetings')
225+
@ApiOperation({ summary: 'Get online meetings' })
226+
@ApiResponse({ status: 200, description: 'Meetings retrieved successfully' })
227+
async getMeetings(@Query() query: GetMeetingsDto) {
228+
try {
229+
const meetings = await this.mcpTeamsService.getOnlineMeetings({
230+
startDateTime: query.startDateTime,
231+
endDateTime: query.endDateTime,
232+
top: query.top,
233+
});
234+
235+
return {
236+
success: true,
237+
count: meetings.length,
238+
meetings,
239+
};
240+
} catch (error) {
241+
throw new HttpException(
242+
{
243+
success: false,
244+
message: 'Failed to get meetings',
245+
error: error instanceof Error ? error.message : 'Unknown error',
246+
},
247+
HttpStatus.INTERNAL_SERVER_ERROR,
248+
);
249+
}
250+
}
251+
252+
/**
253+
* Get meeting recordings
254+
*/
255+
@Get('recordings')
256+
@ApiOperation({ summary: 'Get meeting recordings' })
257+
@ApiResponse({ status: 200, description: 'Recordings retrieved successfully' })
258+
async getRecordings(@Query('since') since?: string) {
259+
try {
260+
const sinceDate = since ? new Date(since) : undefined;
261+
const recordings = await this.mcpTeamsService.getMeetingRecordings(sinceDate);
262+
263+
return {
264+
success: true,
265+
count: recordings.length,
266+
recordings,
267+
};
268+
} catch (error) {
269+
throw new HttpException(
270+
{
271+
success: false,
272+
message: 'Failed to get recordings',
273+
error: error instanceof Error ? error.message : 'Unknown error',
274+
},
275+
HttpStatus.INTERNAL_SERVER_ERROR,
276+
);
277+
}
278+
}
279+
280+
/**
281+
* Process a specific recording
282+
*/
283+
@Post('recordings/process')
284+
@ApiOperation({ summary: 'Process a Teams meeting recording' })
285+
@ApiResponse({ status: 200, description: 'Recording processing queued' })
286+
async processRecording(@Body() dto: ProcessRecordingDto) {
287+
try {
288+
await this.mcpTeamsService.processRecording(dto.recordingId);
289+
290+
return {
291+
success: true,
292+
message: `Recording ${dto.recordingId} queued for processing`,
293+
recordingId: dto.recordingId,
294+
};
295+
} catch (error) {
296+
throw new HttpException(
297+
{
298+
success: false,
299+
message: 'Failed to process recording',
300+
error: error instanceof Error ? error.message : 'Unknown error',
301+
},
302+
HttpStatus.INTERNAL_SERVER_ERROR,
303+
);
304+
}
305+
}
306+
307+
/**
308+
* Sync meeting recordings
309+
*/
310+
@Post('recordings/sync')
311+
@ApiOperation({ summary: 'Sync meeting recordings' })
312+
@ApiResponse({ status: 200, description: 'Recording sync started' })
313+
async syncRecordings(@Body() dto: SyncRecordingsDto) {
314+
try {
315+
const sinceDate = dto.since ? new Date(dto.since) : undefined;
316+
const recordings = await this.mcpTeamsService.getMeetingRecordings(sinceDate);
317+
318+
// Process each recording (limit to dto.limit if specified)
319+
const toProcess = dto.limit ? recordings.slice(0, dto.limit) : recordings;
320+
321+
for (const recording of toProcess) {
322+
try {
323+
await this.mcpTeamsService.processRecording(recording.recordingId);
324+
} catch (error) {
325+
// Continue processing other recordings even if one fails
326+
continue;
327+
}
328+
}
329+
330+
return {
331+
success: true,
332+
message: 'Recording sync started',
333+
totalFound: recordings.length,
334+
queued: toProcess.length,
335+
};
336+
} catch (error) {
337+
throw new HttpException(
338+
{
339+
success: false,
340+
message: 'Recording sync failed',
341+
error: error instanceof Error ? error.message : 'Unknown error',
342+
},
343+
HttpStatus.INTERNAL_SERVER_ERROR,
344+
);
345+
}
346+
}
347+
}

0 commit comments

Comments
 (0)