Skip to content

Commit c01c76a

Browse files
Claude/slack teams mcp
2 parents 1c6ee0c + edbb6a6 commit c01c76a

File tree

13 files changed

+2828
-30
lines changed

13 files changed

+2828
-30
lines changed

MCP_INTEGRATION_PLAN.md

Lines changed: 1248 additions & 0 deletions
Large diffs are not rendered by default.

backend/.env.example

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,17 @@ BULL_REDIS_PORT=6379
5959

6060
# Logging
6161
LOG_LEVEL=debug
62+
63+
# MCP (Model Context Protocol)
64+
MCP_ENABLED=false
65+
MCP_TIMEOUT_MS=30000
66+
MCP_RETRY_ATTEMPTS=3
67+
68+
# Slack MCP Server
69+
SLACK_MCP_ENABLED=false
70+
SLACK_TEAM_ID=T01234567
71+
72+
# Teams MCP Server
73+
TEAMS_MCP_ENABLED=false
74+
TEAMS_APP_ID=your_teams_app_id
75+
TEAMS_APP_PASSWORD=your_teams_app_password

backend/package.json

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,49 +26,50 @@
2626
"prisma:studio": "npx prisma studio"
2727
},
2828
"dependencies": {
29+
"@aws-sdk/client-s3": "^3.478.0",
30+
"@microsoft/microsoft-graph-client": "^3.0.7",
31+
"@modelcontextprotocol/sdk": "^1.22.0",
32+
"@nestjs/bull": "^10.0.1",
2933
"@nestjs/common": "^10.0.0",
30-
"@nestjs/core": "^10.0.0",
31-
"@nestjs/platform-express": "^10.0.0",
3234
"@nestjs/config": "^3.1.1",
35+
"@nestjs/core": "^10.0.0",
3336
"@nestjs/jwt": "^10.2.0",
3437
"@nestjs/passport": "^10.0.3",
35-
"@nestjs/swagger": "^7.1.17",
38+
"@nestjs/platform-express": "^10.0.0",
3639
"@nestjs/schedule": "^4.0.0",
37-
"@nestjs/bull": "^10.0.1",
40+
"@nestjs/swagger": "^7.1.17",
3841
"@prisma/client": "^5.7.1",
39-
"passport": "^0.7.0",
40-
"passport-jwt": "^4.0.1",
41-
"passport-oauth2": "^1.8.0",
42+
"@slack/events-api": "^3.0.1",
43+
"@slack/web-api": "^6.11.2",
44+
"axios": "^1.6.2",
4245
"bcrypt": "^5.1.1",
4346
"bull": "^4.12.0",
44-
"class-validator": "^0.14.0",
4547
"class-transformer": "^0.5.1",
46-
"ioredis": "^5.3.2",
47-
"@microsoft/microsoft-graph-client": "^3.0.7",
48-
"@slack/web-api": "^6.11.2",
49-
"@slack/events-api": "^3.0.1",
50-
"axios": "^1.6.2",
51-
"openai": "^4.20.1",
48+
"class-validator": "^0.14.0",
49+
"date-fns": "^3.0.6",
5250
"form-data": "^4.0.0",
51+
"ioredis": "^5.3.2",
52+
"microsoft-cognitiveservices-speech-sdk": "^1.34.1",
5353
"multer": "^1.4.5-lts.1",
54-
"uuid": "^9.0.1",
55-
"date-fns": "^3.0.6",
54+
"openai": "^4.20.1",
55+
"passport": "^0.7.0",
56+
"passport-jwt": "^4.0.1",
57+
"passport-oauth2": "^1.8.0",
5658
"reflect-metadata": "^0.1.13",
5759
"rxjs": "^7.8.1",
58-
"@aws-sdk/client-s3": "^3.478.0",
59-
"microsoft-cognitiveservices-speech-sdk": "^1.34.1"
60+
"uuid": "^9.0.1"
6061
},
6162
"devDependencies": {
6263
"@nestjs/cli": "^10.0.0",
6364
"@nestjs/schematics": "^10.0.0",
6465
"@nestjs/testing": "^10.0.0",
66+
"@types/bcrypt": "^5.0.2",
6567
"@types/express": "^4.17.17",
6668
"@types/jest": "^29.5.2",
67-
"@types/node": "^20.3.1",
68-
"@types/supertest": "^2.0.12",
69-
"@types/bcrypt": "^5.0.2",
7069
"@types/multer": "^1.4.11",
70+
"@types/node": "^20.3.1",
7171
"@types/passport-jwt": "^4.0.0",
72+
"@types/supertest": "^2.0.12",
7273
"@types/uuid": "^9.0.7",
7374
"@typescript-eslint/eslint-plugin": "^6.0.0",
7475
"@typescript-eslint/parser": "^6.0.0",

backend/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { NlpModule } from './modules/nlp/nlp.module';
1616
import { WebhooksModule } from './modules/webhooks/webhooks.module';
1717
import { AdminModule } from './modules/admin/admin.module';
1818
import { AnalyticsModule } from './modules/analytics/analytics.module';
19+
import { MCPModule } from './modules/mcp/mcp.module';
1920

2021
@Module({
2122
imports: [
@@ -53,6 +54,7 @@ import { AnalyticsModule } from './modules/analytics/analytics.module';
5354
WebhooksModule,
5455
AdminModule,
5556
AnalyticsModule,
57+
MCPModule,
5658
],
5759
controllers: [AppController],
5860
providers: [AppService],
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { MCPServerConfig } from '../interfaces/mcp.interface';
2+
3+
/**
4+
* MCP Configuration Constants
5+
*/
6+
export const MCP_CONFIG = {
7+
DEFAULT_TIMEOUT: 30000, // 30 seconds
8+
DEFAULT_RETRY_ATTEMPTS: 3,
9+
RETRY_DELAY_MS: 2000, // 2 seconds
10+
};
11+
12+
/**
13+
* Get MCP Servers Configuration from Environment
14+
*/
15+
export function getMCPServersConfig(): MCPServerConfig[] {
16+
const servers: MCPServerConfig[] = [];
17+
18+
// Slack MCP Server Configuration
19+
if (process.env.SLACK_MCP_ENABLED === 'true') {
20+
servers.push({
21+
name: 'slack',
22+
command: 'npx',
23+
args: ['-y', '@modelcontextprotocol/server-slack'],
24+
env: {
25+
SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN || '',
26+
SLACK_TEAM_ID: process.env.SLACK_TEAM_ID || '',
27+
},
28+
transport: 'stdio',
29+
enabled: true,
30+
timeout: parseInt(process.env.MCP_TIMEOUT_MS || String(MCP_CONFIG.DEFAULT_TIMEOUT), 10),
31+
retryAttempts: parseInt(
32+
process.env.MCP_RETRY_ATTEMPTS || String(MCP_CONFIG.DEFAULT_RETRY_ATTEMPTS),
33+
10,
34+
),
35+
});
36+
}
37+
38+
// Teams MCP Server Configuration
39+
if (process.env.TEAMS_MCP_ENABLED === 'true') {
40+
servers.push({
41+
name: 'teams',
42+
command: 'npx',
43+
args: ['-y', '@inditextech/mcp-teams-server'],
44+
env: {
45+
TEAMS_APP_ID: process.env.TEAMS_APP_ID || '',
46+
TEAMS_APP_PASSWORD: process.env.TEAMS_APP_PASSWORD || '',
47+
TEAMS_TENANT_ID: process.env.TEAMS_TENANT_ID || '',
48+
},
49+
transport: 'stdio',
50+
enabled: true,
51+
timeout: parseInt(process.env.MCP_TIMEOUT_MS || String(MCP_CONFIG.DEFAULT_TIMEOUT), 10),
52+
retryAttempts: parseInt(
53+
process.env.MCP_RETRY_ATTEMPTS || String(MCP_CONFIG.DEFAULT_RETRY_ATTEMPTS),
54+
10,
55+
),
56+
});
57+
}
58+
59+
return servers;
60+
}
61+
62+
/**
63+
* Validate MCP Server Configuration
64+
*/
65+
export function validateMCPServerConfig(config: MCPServerConfig): boolean {
66+
if (!config.name || !config.command || !config.args) {
67+
return false;
68+
}
69+
70+
if (config.transport !== 'stdio' && config.transport !== 'sse') {
71+
return false;
72+
}
73+
74+
// Validate required environment variables
75+
if (config.name === 'slack') {
76+
if (!config.env?.SLACK_BOT_TOKEN) {
77+
console.warn('Slack MCP: SLACK_BOT_TOKEN is not configured');
78+
return false;
79+
}
80+
}
81+
82+
if (config.name === 'teams') {
83+
if (!config.env?.TEAMS_APP_ID || !config.env?.TEAMS_APP_PASSWORD) {
84+
console.warn('Teams MCP: TEAMS_APP_ID or TEAMS_APP_PASSWORD is not configured');
85+
return false;
86+
}
87+
}
88+
89+
return true;
90+
}
91+
92+
/**
93+
* Check if MCP is enabled globally
94+
*/
95+
export function isMCPEnabled(): boolean {
96+
return process.env.MCP_ENABLED === 'true';
97+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { HttpException, HttpStatus } from '@nestjs/common';
2+
3+
/**
4+
* Base MCP Exception
5+
*/
6+
export class MCPException extends HttpException {
7+
constructor(message: string, status: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR) {
8+
super(message, status);
9+
this.name = 'MCPException';
10+
}
11+
}
12+
13+
/**
14+
* Thrown when MCP server connection fails
15+
*/
16+
export class MCPConnectionException extends MCPException {
17+
constructor(serverName: string, reason?: string) {
18+
const message = reason
19+
? `Failed to connect to MCP server '${serverName}': ${reason}`
20+
: `Failed to connect to MCP server '${serverName}'`;
21+
super(message, HttpStatus.SERVICE_UNAVAILABLE);
22+
this.name = 'MCPConnectionException';
23+
}
24+
}
25+
26+
/**
27+
* Thrown when MCP server is not found
28+
*/
29+
export class MCPServerNotFoundException extends MCPException {
30+
constructor(serverName: string) {
31+
super(`MCP server '${serverName}' not found`, HttpStatus.NOT_FOUND);
32+
this.name = 'MCPServerNotFoundException';
33+
}
34+
}
35+
36+
/**
37+
* Thrown when MCP tool execution fails
38+
*/
39+
export class MCPToolException extends MCPException {
40+
constructor(toolName: string, reason: string) {
41+
super(`MCP tool '${toolName}' execution failed: ${reason}`, HttpStatus.BAD_REQUEST);
42+
this.name = 'MCPToolException';
43+
}
44+
}
45+
46+
/**
47+
* Thrown when MCP tool is not found
48+
*/
49+
export class MCPToolNotFoundException extends MCPException {
50+
constructor(toolName: string, serverName: string) {
51+
super(
52+
`MCP tool '${toolName}' not found on server '${serverName}'`,
53+
HttpStatus.NOT_FOUND,
54+
);
55+
this.name = 'MCPToolNotFoundException';
56+
}
57+
}
58+
59+
/**
60+
* Thrown when MCP configuration is invalid
61+
*/
62+
export class MCPConfigurationException extends MCPException {
63+
constructor(message: string) {
64+
super(`MCP configuration error: ${message}`, HttpStatus.INTERNAL_SERVER_ERROR);
65+
this.name = 'MCPConfigurationException';
66+
}
67+
}
68+
69+
/**
70+
* Thrown when MCP timeout occurs
71+
*/
72+
export class MCPTimeoutException extends MCPException {
73+
constructor(operation: string, timeoutMs: number) {
74+
super(
75+
`MCP operation '${operation}' timed out after ${timeoutMs}ms`,
76+
HttpStatus.REQUEST_TIMEOUT,
77+
);
78+
this.name = 'MCPTimeoutException';
79+
}
80+
}

backend/src/modules/mcp/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Module
2+
export * from './mcp.module';
3+
4+
// Services
5+
export * from './services/mcp-client.service';
6+
export * from './services/mcp-connection-manager.service';
7+
export * from './services/mcp-tool-invoker.service';
8+
9+
// Interfaces
10+
export * from './interfaces/mcp.interface';
11+
12+
// Exceptions
13+
export * from './exceptions/mcp.exceptions';
14+
15+
// Config
16+
export * from './config/mcp.config';
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* MCP Server Configuration Interface
3+
*/
4+
export interface MCPServerConfig {
5+
name: string;
6+
command: string;
7+
args: string[];
8+
env?: Record<string, string>;
9+
transport: 'stdio' | 'sse';
10+
enabled?: boolean;
11+
timeout?: number;
12+
retryAttempts?: number;
13+
}
14+
15+
/**
16+
* MCP Connection Status
17+
*/
18+
export enum MCPConnectionStatus {
19+
CONNECTED = 'connected',
20+
DISCONNECTED = 'disconnected',
21+
CONNECTING = 'connecting',
22+
ERROR = 'error',
23+
}
24+
25+
/**
26+
* MCP Tool Definition
27+
*/
28+
export interface MCPTool {
29+
name: string;
30+
description?: string;
31+
inputSchema: {
32+
type: string;
33+
properties?: Record<string, any>;
34+
required?: string[];
35+
};
36+
}
37+
38+
/**
39+
* MCP Resource Definition
40+
*/
41+
export interface MCPResource {
42+
uri: string;
43+
name: string;
44+
description?: string;
45+
mimeType?: string;
46+
}
47+
48+
/**
49+
* MCP Tool Invocation Request
50+
*/
51+
export interface MCPToolInvocationRequest {
52+
serverName: string;
53+
toolName: string;
54+
parameters: Record<string, any>;
55+
}
56+
57+
/**
58+
* MCP Tool Invocation Response
59+
*/
60+
export interface MCPToolInvocationResponse {
61+
success: boolean;
62+
data?: any;
63+
error?: string;
64+
metadata?: Record<string, any>;
65+
}
66+
67+
/**
68+
* MCP Connection Info
69+
*/
70+
export interface MCPConnectionInfo {
71+
serverName: string;
72+
status: MCPConnectionStatus;
73+
connectedAt?: Date;
74+
lastError?: string;
75+
availableTools?: MCPTool[];
76+
availableResources?: MCPResource[];
77+
}
78+
79+
/**
80+
* MCP Server Instance
81+
*/
82+
export interface MCPServerInstance {
83+
name: string;
84+
config: MCPServerConfig;
85+
client: any; // MCP Client instance
86+
status: MCPConnectionStatus;
87+
connectedAt?: Date;
88+
lastError?: string;
89+
}

0 commit comments

Comments
 (0)