Skip to content

Commit c1ae5c2

Browse files
authored
Merge pull request #9275 from ever-co/develop
Stage
2 parents f457ec6 + 9da1d7a commit c1ae5c2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2053
-674
lines changed

apps/mcp/project.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"$schema": "../../node_modules/nx/schemas/project-schema.json",
44
"sourceRoot": "apps/mcp/src",
55
"projectType": "application",
6+
"implicitDependencies": ["mcp-server"],
67
"targets": {
78
"build": {
89
"executor": "@nx/js:tsc",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
"doc:build": "compodoc -p tsconfig.json -d dist/docs",
9797
"doc:serve": "compodoc -s -d dist/docs",
9898
"doc:build-serve": "compodoc -p tsconfig.json -d docs -s",
99-
"postinstall.manual": "yarn ts-node ./.scripts/postinstall.js",
99+
"postinstall.manual": "yarn node ./.scripts/postinstall.js",
100100
"postinstall.electron": "yarn electron-builder install-app-deps && yarn node tools/electron/postinstall",
101101
"postinstall.web": "yarn node ./decorate-angular-cli.js && yarn node tools/web/postinstall",
102102
"build:desktop": "cross-env NODE_ENV=production yarn run copy-files-i18n-desktop && yarn run postinstall.electron && yarn run config:prod && yarn run config:desktop:prod && yarn run build:package:all:prod && yarn run pack:desktop && yarn run generate:icons:desktop --environment=prod && yarn ng:prod run gauzy:desktop-ui --base-href ./ && yarn run prepare:desktop && yarn ng:prod run api:desktop-api && yarn ng:prod build desktop-api --output-path=dist/apps/desktop/desktop-api && yarn ng:prod build desktop --base-href ./ && yarn run copy-files-desktop && yarn run copy-assets-gauzy",

packages/mcp-server/package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,17 @@
1717
},
1818
"private": true,
1919
"type": "commonjs",
20-
"main": "./src/index.js",
21-
"types": "./src/index.d.ts",
20+
"main": "../../../dist/packages/mcp-server/src/index.js",
21+
"types": "../../../dist/packages/mcp-server/src/index.d.ts",
2222
"homepage": "https://ever.co",
2323
"license": "AGPL-3.0",
2424
"scripts": {
2525
"start": "nx serve mcp",
26-
"lib:build": "yarn nx build mcp-server",
27-
"lib:build:prod": "cross-env NODE_ENV=production yarn ts-node scripts/replace-env-files.ts --environment=prod && yarn nx build mcp-server",
28-
"lib:watch": "yarn nx build mcp-server --watch"
26+
"lib:build": "cross-env NODE_OPTIONS=--max-old-space-size=12288 yarn nx build mcp-server",
27+
"lib:build:prod": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=12288 yarn ts-node scripts/replace-env-files.ts --environment=prod && yarn nx build mcp-server",
28+
"lib:watch": "cross-env NODE_OPTIONS=--max-old-space-size=12288 yarn nx build mcp-server --watch"
2929
},
3030
"dependencies": {
31-
"@gauzy/auth": "^0.1.0",
3231
"@modelcontextprotocol/sdk": "^1.13.1",
3332
"@nestjs/common": "^11.1.0",
3433
"@nestjs/core": "^11.1.0",

packages/mcp-server/src/lib/common/api-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
22
import { Logger } from '@nestjs/common';
33
import { environment } from '../environments/environment';
44
import { authManager } from './auth-manager';
5-
import { sanitizeErrorMessage } from '@gauzy/auth';
5+
import { sanitizeErrorMessage } from './error-utils';
66

77
const logger = new Logger('ApiClient');
88

packages/mcp-server/src/lib/common/auth-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { environment } from '../environments/environment';
2-
import { sanitizeForLogging } from '@gauzy/auth';
2+
import { sanitizeForLogging } from './error-utils';
33
import { Logger } from '@nestjs/common';
44

55
const logger = new Logger('AuthManager');
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/**
2+
* Authorization types for MCP Server
3+
*
4+
* These types are defined locally to avoid importing from @gauzy/auth,
5+
* which causes TypeScript compilation performance issues due to its complex
6+
* dependency chain in the monorepo.
7+
*/
8+
9+
import { Request, Response } from 'express';
10+
11+
/**
12+
* Authorization server configuration
13+
*/
14+
export interface AuthorizationServerConfig {
15+
issuer: string;
16+
jwksUri?: string;
17+
tokenEndpoint?: string;
18+
authorizationEndpoint?: string;
19+
}
20+
21+
/**
22+
* Authorization configuration
23+
*/
24+
export interface AuthorizationConfig {
25+
enabled: boolean;
26+
requiredScopes: string[];
27+
resourceUri?: string;
28+
authorizationServers: AuthorizationServerConfig[];
29+
tokenValidation?: {
30+
audience?: string;
31+
issuer?: string | string[];
32+
clockTolerance?: number;
33+
};
34+
}
35+
36+
/**
37+
* OAuth 2.0 Protected Resource Metadata (RFC 9728)
38+
*/
39+
export interface ProtectedResourceMetadata {
40+
resource: string;
41+
authorizationServers: string[];
42+
scopesRequired?: string[];
43+
bearerMethodsSupported?: string[];
44+
policyUri?: string;
45+
}
46+
47+
/**
48+
* OAuth authorization error
49+
*/
50+
export interface OAuthAuthorizationError {
51+
error: string;
52+
errorDescription?: string;
53+
errorUri?: string;
54+
scope?: string;
55+
}
56+
57+
/**
58+
* Token validation result
59+
*/
60+
export interface TokenValidationResult {
61+
valid: boolean;
62+
error?: string;
63+
payload?: unknown;
64+
scopes?: string[];
65+
subject?: string;
66+
clientId?: string;
67+
}
68+
69+
/**
70+
* Load authorization configuration from environment
71+
*/
72+
export function loadAuthorizationConfig(): AuthorizationConfig {
73+
const enabled = process.env.MCP_AUTHORIZATION_ENABLED === 'true';
74+
75+
return {
76+
enabled,
77+
requiredScopes: (process.env.MCP_REQUIRED_SCOPES || 'mcp:read mcp:write').split(' ').filter(Boolean),
78+
resourceUri: process.env.MCP_RESOURCE_URI,
79+
authorizationServers: enabled
80+
? [
81+
{
82+
issuer: process.env.MCP_AUTH_ISSUER || 'https://auth.example.com',
83+
jwksUri: process.env.MCP_AUTH_JWKS_URI,
84+
tokenEndpoint: process.env.MCP_AUTH_TOKEN_ENDPOINT,
85+
authorizationEndpoint: process.env.MCP_AUTH_AUTHORIZATION_ENDPOINT
86+
}
87+
]
88+
: []
89+
};
90+
}
91+
92+
/**
93+
* OAuth validator for token validation
94+
*/
95+
export class OAuthValidator {
96+
constructor(private config: AuthorizationConfig) {}
97+
98+
/**
99+
* Extract bearer token from request
100+
*/
101+
extractBearerToken(req: Request): string | null {
102+
const authHeader = req.headers.authorization;
103+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
104+
return null;
105+
}
106+
return authHeader.substring(7);
107+
}
108+
109+
/**
110+
* Validate an OAuth token
111+
*/
112+
async validateToken(token: string, requiredScopes?: string[]): Promise<TokenValidationResult> {
113+
if (!this.config.enabled) {
114+
return { valid: true };
115+
}
116+
117+
// TODO: Implement actual JWT validation when authorization is enabled
118+
// For now, return invalid if authorization is enabled but no implementation
119+
return {
120+
valid: false,
121+
error: 'Token validation not implemented'
122+
};
123+
}
124+
125+
/**
126+
* Create authorization error object
127+
*/
128+
static createAuthorizationError(
129+
error: string,
130+
errorDescription?: string,
131+
scope?: string
132+
): OAuthAuthorizationError {
133+
return {
134+
error,
135+
errorDescription,
136+
scope
137+
};
138+
}
139+
140+
/**
141+
* Format WWW-Authenticate header
142+
*/
143+
static formatWWWAuthenticateHeader(resourceUri: string, error?: OAuthAuthorizationError): string {
144+
let header = 'Bearer';
145+
146+
if (resourceUri) {
147+
header += ` resource="${resourceUri}"`;
148+
}
149+
150+
if (error) {
151+
if (error.error) {
152+
header += ` error="${error.error}"`;
153+
}
154+
if (error.errorDescription) {
155+
header += ` error_description="${error.errorDescription}"`;
156+
}
157+
if (error.scope) {
158+
header += ` scope="${error.scope}"`;
159+
}
160+
}
161+
162+
return header;
163+
}
164+
}
165+
166+
/**
167+
* Response builder utilities
168+
*/
169+
export class ResponseBuilder {
170+
/**
171+
* Set security headers on response
172+
*/
173+
static setSecurityHeaders(res: Response): void {
174+
// Content-Type options
175+
res.setHeader('X-Content-Type-Options', 'nosniff');
176+
177+
// Frame options
178+
res.setHeader('X-Frame-Options', 'DENY');
179+
180+
// XSS protection
181+
res.setHeader('X-XSS-Protection', '1; mode=block');
182+
183+
// Referrer policy
184+
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
185+
186+
// Content Security Policy
187+
res.setHeader(
188+
'Content-Security-Policy',
189+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
190+
);
191+
192+
// Strict Transport Security (for HTTPS)
193+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
194+
195+
// Cache control for API responses
196+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
197+
res.setHeader('Pragma', 'no-cache');
198+
res.setHeader('Expires', '0');
199+
}
200+
201+
/**
202+
* Build success response
203+
*/
204+
static success<T>(data: T, message?: string): { success: true; data: T; message?: string } {
205+
return { success: true, data, message };
206+
}
207+
208+
/**
209+
* Build error response
210+
*/
211+
static error(message: string, code?: string): { success: false; error: string; code?: string } {
212+
return { success: false, error: message, code };
213+
}
214+
}

packages/mcp-server/src/lib/common/authorization-middleware.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
*/
77

88
import { Request, Response, NextFunction } from 'express';
9-
import { AuthorizationConfig, ProtectedResourceMetadata, OAuthValidator, SecurityLogger } from '@gauzy/auth';
9+
import { AuthorizationConfig, ProtectedResourceMetadata, OAuthValidator } from './auth-types';
10+
import { SecurityLogger } from './security-logger';
1011

1112
export interface AuthorizedRequest extends Request {
1213
/** OAuth token validation result */

packages/mcp-server/src/lib/common/authorization-utils.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77

88
import { URL } from 'node:url';
99
import { createHash, randomBytes } from 'node:crypto';
10-
import { SecurityLogger } from '@gauzy/auth';
11-
12-
const securityLogger = new SecurityLogger();
10+
import { SecurityLogger, securityLogger } from './security-logger';
1311

1412
/**
1513
* Validate canonical resource URI according to RFC 8707

0 commit comments

Comments
 (0)