@musashishao/agent-kit 1.9.0 → 1.9.1
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/.agent/agents/ai-asset-factory.md +700 -0
- package/.agent/agents/ai-audio-factory.md +503 -0
- package/.agent/agents/game-developer.md +4 -4
- package/.agent/agents/orchestrator.md +113 -3
- package/.agent/agents/project-planner.md +67 -0
- package/.agent/agents/unity-mobile-master.md +949 -0
- package/.agent/mcp/config/registry.json +65 -51
- package/.agent/mcp/servers/notebooklm/README.md +114 -0
- package/.agent/mcp/servers/notebooklm/package.json +35 -0
- package/.agent/mcp/servers/notebooklm/src/auth/chrome.ts +225 -0
- package/.agent/mcp/servers/notebooklm/src/auth/index.ts +1 -0
- package/.agent/mcp/servers/notebooklm/src/index.ts +516 -0
- package/.agent/mcp/servers/notebooklm/src/services/index.ts +3 -0
- package/.agent/mcp/servers/notebooklm/src/services/library.ts +217 -0
- package/.agent/mcp/servers/notebooklm/src/services/notebooklm.ts +380 -0
- package/.agent/mcp/servers/notebooklm/tsconfig.json +15 -0
- package/.agent/mcp-gateway/README.md +169 -20
- package/.agent/mcp-gateway/package.json +22 -7
- package/.agent/mcp-gateway/src/auth/index.ts +55 -0
- package/.agent/mcp-gateway/src/auth/middleware.ts +242 -0
- package/.agent/mcp-gateway/src/auth/oauth.ts +462 -0
- package/.agent/mcp-gateway/src/auth/scopes.ts +227 -0
- package/.agent/mcp-gateway/src/index.ts +252 -105
- package/.agent/mcp-gateway/src/observability/index.ts +5 -0
- package/.agent/mcp-gateway/src/observability/otel.ts +405 -0
- package/.agent/mcp-gateway/src/transports/index.ts +5 -0
- package/.agent/mcp-gateway/src/transports/streamableHttp.ts +235 -0
- package/.agent/rules/CODEX.md +89 -0
- package/.agent/rules/CODE_RULES.md +73 -0
- package/.agent/rules/GEMINI.md +25 -0
- package/.agent/rules/MEMORY_STATE.md +110 -0
- package/.agent/rules/REFERENCE.md +33 -141
- package/.agent/rules/REF_SKILLS.md +116 -0
- package/.agent/rules/REF_WORKFLOWS.md +81 -0
- package/.agent/scripts/ak_cli.py +106 -5
- package/.agent/scripts/memory_manager.py +48 -9
- package/.agent/skills/anti-hallucination/SKILL.md +295 -0
- package/.agent/skills/anti-hallucination/scripts/check_hallucination.py +299 -0
- package/.agent/skills/bifurcation-analysis/SKILL.md +56 -0
- package/.agent/skills/brainstorming/SKILL.md +80 -6
- package/.agent/skills/decision-memory/SKILL.md +317 -0
- package/.agent/skills/emergence-detector/SKILL.md +230 -0
- package/.agent/skills/emergence-detector/scripts/check_emergence.py +265 -0
- package/.agent/skills/explained-qa/SKILL.md +142 -0
- package/.agent/skills/explained-qa/game-terminology.md +214 -0
- package/.agent/skills/game-development/ai-dialogue-engine/SKILL.md +442 -0
- package/.agent/skills/game-development/ai-graphics-generator/SKILL.md +463 -0
- package/.agent/skills/game-development/ai-playtest-framework/SKILL.md +570 -0
- package/.agent/skills/game-development/camera-systems/SKILL.md +607 -0
- package/.agent/skills/game-development/card-battle-engine/SKILL.md +618 -0
- package/.agent/skills/game-development/character-controller-3d/SKILL.md +908 -0
- package/.agent/skills/game-development/cloud-save-sync/SKILL.md +527 -0
- package/.agent/skills/game-development/combat-system/SKILL.md +748 -0
- package/.agent/skills/game-development/compliance-rating/SKILL.md +277 -0
- package/.agent/skills/game-development/crossplatform-build/SKILL.md +386 -0
- package/.agent/skills/game-development/cultivation-progression/SKILL.md +520 -0
- package/.agent/skills/game-development/data-driven-balance/SKILL.md +535 -0
- package/.agent/skills/game-development/game-analytics-integrator/SKILL.md +410 -0
- package/.agent/skills/game-development/game-audio-advanced/SKILL.md +646 -0
- package/.agent/skills/game-development/game-economy-designer/SKILL.md +375 -0
- package/.agent/skills/game-development/game-marketing/SKILL.md +85 -0
- package/.agent/skills/game-development/game-state-manager/SKILL.md +883 -0
- package/.agent/skills/game-development/hybrid-game-spec/SKILL.md +220 -0
- package/.agent/skills/game-development/inventory-quest/SKILL.md +747 -0
- package/.agent/skills/game-development/liveops/SKILL.md +308 -0
- package/.agent/skills/game-development/localization/SKILL.md +286 -0
- package/.agent/skills/game-development/mobile-input-patterns/SKILL.md +343 -0
- package/.agent/skills/game-development/monetization-strategy/SKILL.md +94 -0
- package/.agent/skills/game-development/multiplayer-master/SKILL.md +727 -0
- package/.agent/skills/game-development/narrative-branching/SKILL.md +593 -0
- package/.agent/skills/game-development/procedural-level-ai/SKILL.md +367 -0
- package/.agent/skills/game-development/prototyping-rapid/SKILL.md +205 -0
- package/.agent/skills/game-development/spec-ecosystem/SKILL.md +155 -0
- package/.agent/skills/game-development/spec-ecosystem/decision-log-format.md +129 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/PLAN-template.md +178 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/SPEC-template.md +110 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/TASKS-template.md +156 -0
- package/.agent/skills/game-development/survival-systems/SKILL.md +493 -0
- package/.agent/skills/game-development/testing-qa/SKILL.md +270 -0
- package/.agent/skills/game-development/unity-mobile-optimization/SKILL.md +271 -0
- package/.agent/skills/intent-capture/SKILL.md +65 -0
- package/.agent/skills/mcp-composition/SKILL.md +362 -0
- package/.agent/skills/mcp-observability/SKILL.md +323 -0
- package/.agent/skills/mcp-security/SKILL.md +314 -0
- package/.agent/skills/trust-spectrum/SKILL.md +291 -0
- package/.agent/skills/vibe-coding-guard/SKILL.md +328 -0
- package/.agent/templates/AGENTS.game.md +63 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.en.md +100 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.vi.md +100 -0
- package/.agent/workflows/ai-agent.md +2 -0
- package/.agent/workflows/autofix.md +1 -0
- package/.agent/workflows/brainstorm.md +1 -0
- package/.agent/workflows/context.md +1 -0
- package/.agent/workflows/create.md +39 -8
- package/.agent/workflows/dashboard.md +1 -0
- package/.agent/workflows/debug.md +14 -0
- package/.agent/workflows/deploy.md +14 -0
- package/.agent/workflows/enhance.md +44 -0
- package/.agent/workflows/gamekit-init.md +177 -0
- package/.agent/workflows/gamekit-launch.md +338 -0
- package/.agent/workflows/gamekit-plan.md +204 -0
- package/.agent/workflows/gamekit-qa.md +153 -0
- package/.agent/workflows/gamekit-spec.md +243 -0
- package/.agent/workflows/gamekit-tasks.md +208 -0
- package/.agent/workflows/marketing.md +2 -0
- package/.agent/workflows/next.md +1 -0
- package/.agent/workflows/orchestrate.md +12 -0
- package/.agent/workflows/pentest.md +2 -0
- package/.agent/workflows/plan.md +42 -0
- package/.agent/workflows/preview.md +1 -0
- package/.agent/workflows/quality.md +1 -0
- package/.agent/workflows/saas.md +2 -0
- package/.agent/workflows/spec.md +42 -0
- package/.agent/workflows/status.md +1 -0
- package/.agent/workflows/test.md +14 -0
- package/.agent/workflows/ui-ux-pro-max.md +1 -0
- package/bin/cli.js +411 -111
- package/package.json +1 -2
- package/.agent/agents/game-asset-curator.md +0 -317
- package/.agent/agents/game-narrative-designer.md +0 -310
- package/.agent/agents/game-qa-agent.md +0 -441
- package/.agent/workflows/game-prototype.md +0 -154
- package/docs/AI_DATA_INFRASTRUCTURE.md +0 -288
- package/docs/CHANGELOG_AI_INFRA.md +0 -141
- package/docs/MIGRATION_GUIDE_V1.9.md +0 -55
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Authentication for MCP Gateway
|
|
3
|
+
*
|
|
4
|
+
* Implements:
|
|
5
|
+
* - OAuth 2.1 IETF DRAFT compliance
|
|
6
|
+
* - PKCE (Proof Key for Code Exchange) - mandatory
|
|
7
|
+
* - Dynamic Client Registration (RFC 7591)
|
|
8
|
+
* - Resource Indicators (RFC 8707)
|
|
9
|
+
* - Scope-based access control
|
|
10
|
+
*
|
|
11
|
+
* @see https://modelcontextprotocol.io/docs/authorization
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { IncomingMessage, ServerResponse } from "http";
|
|
15
|
+
import * as crypto from "crypto";
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Types
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
export interface OAuthConfig {
|
|
22
|
+
/** Issuer URL for token validation */
|
|
23
|
+
issuer: string;
|
|
24
|
+
/** Client ID for this MCP server */
|
|
25
|
+
clientId: string;
|
|
26
|
+
/** Client secret (for confidential clients) */
|
|
27
|
+
clientSecret?: string;
|
|
28
|
+
/** Redirect URIs for authorization flow */
|
|
29
|
+
redirectUris: string[];
|
|
30
|
+
/** Supported scopes */
|
|
31
|
+
scopes: string[];
|
|
32
|
+
/** Token endpoint */
|
|
33
|
+
tokenEndpoint?: string;
|
|
34
|
+
/** Authorization endpoint */
|
|
35
|
+
authorizationEndpoint?: string;
|
|
36
|
+
/** JWKS endpoint for token validation */
|
|
37
|
+
jwksUri?: string;
|
|
38
|
+
/** Token expiration in seconds */
|
|
39
|
+
tokenExpiration?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TokenPayload {
|
|
43
|
+
/** Subject (user/client ID) */
|
|
44
|
+
sub: string;
|
|
45
|
+
/** Issuer */
|
|
46
|
+
iss: string;
|
|
47
|
+
/** Audience (this MCP server) */
|
|
48
|
+
aud: string;
|
|
49
|
+
/** Expiration timestamp */
|
|
50
|
+
exp: number;
|
|
51
|
+
/** Issued at timestamp */
|
|
52
|
+
iat: number;
|
|
53
|
+
/** Scopes granted */
|
|
54
|
+
scope: string;
|
|
55
|
+
/** Client ID */
|
|
56
|
+
client_id: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AuthResult {
|
|
60
|
+
valid: boolean;
|
|
61
|
+
payload?: TokenPayload;
|
|
62
|
+
error?: string;
|
|
63
|
+
scopes?: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PKCEChallenge {
|
|
67
|
+
codeVerifier: string;
|
|
68
|
+
codeChallenge: string;
|
|
69
|
+
codeChallengeMethod: "S256";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// MCP Scopes Definition
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
export const MCP_SCOPES = {
|
|
77
|
+
// Read-only scopes
|
|
78
|
+
"read:project": "Read project context and structure",
|
|
79
|
+
"read:graph": "Read dependency graph",
|
|
80
|
+
"read:search": "Execute search queries",
|
|
81
|
+
|
|
82
|
+
// Write scopes
|
|
83
|
+
"write:sync": "Trigger sync operations",
|
|
84
|
+
"write:cache": "Modify cache",
|
|
85
|
+
|
|
86
|
+
// Execute scopes
|
|
87
|
+
"execute:tools": "Execute MCP tools",
|
|
88
|
+
"execute:analysis": "Run code analysis",
|
|
89
|
+
|
|
90
|
+
// Admin scopes
|
|
91
|
+
"admin:config": "Modify server configuration",
|
|
92
|
+
"admin:users": "Manage user access",
|
|
93
|
+
"admin:*": "Full administrative access",
|
|
94
|
+
} as const;
|
|
95
|
+
|
|
96
|
+
export type MCPScope = keyof typeof MCP_SCOPES;
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// PKCE Helpers
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generate PKCE code verifier and challenge
|
|
104
|
+
*/
|
|
105
|
+
export function generatePKCE(): PKCEChallenge {
|
|
106
|
+
// Generate 32-byte random verifier (43 chars base64url)
|
|
107
|
+
const codeVerifier = crypto.randomBytes(32)
|
|
108
|
+
.toString("base64url")
|
|
109
|
+
.replace(/[^a-zA-Z0-9]/g, "")
|
|
110
|
+
.substring(0, 43);
|
|
111
|
+
|
|
112
|
+
// SHA256 hash of verifier, base64url encoded
|
|
113
|
+
const codeChallenge = crypto
|
|
114
|
+
.createHash("sha256")
|
|
115
|
+
.update(codeVerifier)
|
|
116
|
+
.digest("base64url");
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
codeVerifier,
|
|
120
|
+
codeChallenge,
|
|
121
|
+
codeChallengeMethod: "S256",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Verify PKCE code challenge
|
|
127
|
+
*/
|
|
128
|
+
export function verifyPKCE(codeVerifier: string, codeChallenge: string): boolean {
|
|
129
|
+
const computed = crypto
|
|
130
|
+
.createHash("sha256")
|
|
131
|
+
.update(codeVerifier)
|
|
132
|
+
.digest("base64url");
|
|
133
|
+
|
|
134
|
+
return crypto.timingSafeEqual(
|
|
135
|
+
Buffer.from(computed),
|
|
136
|
+
Buffer.from(codeChallenge)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Token Management
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
// In-memory token store (replace with Redis/DB in production)
|
|
145
|
+
const tokenStore = new Map<string, TokenPayload>();
|
|
146
|
+
const authCodeStore = new Map<string, {
|
|
147
|
+
clientId: string;
|
|
148
|
+
scopes: string[];
|
|
149
|
+
codeChallenge: string;
|
|
150
|
+
expiresAt: number;
|
|
151
|
+
}>();
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Generate access token
|
|
155
|
+
*/
|
|
156
|
+
export function generateAccessToken(
|
|
157
|
+
config: OAuthConfig,
|
|
158
|
+
clientId: string,
|
|
159
|
+
scopes: string[],
|
|
160
|
+
subject: string = clientId
|
|
161
|
+
): string {
|
|
162
|
+
const now = Math.floor(Date.now() / 1000);
|
|
163
|
+
const expiration = config.tokenExpiration || 3600; // 1 hour default
|
|
164
|
+
|
|
165
|
+
const payload: TokenPayload = {
|
|
166
|
+
sub: subject,
|
|
167
|
+
iss: config.issuer,
|
|
168
|
+
aud: config.clientId,
|
|
169
|
+
exp: now + expiration,
|
|
170
|
+
iat: now,
|
|
171
|
+
scope: scopes.join(" "),
|
|
172
|
+
client_id: clientId,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Generate random token ID
|
|
176
|
+
const tokenId = crypto.randomBytes(32).toString("base64url");
|
|
177
|
+
|
|
178
|
+
// Store token for validation
|
|
179
|
+
tokenStore.set(tokenId, payload);
|
|
180
|
+
|
|
181
|
+
// In production, use JWT with RS256
|
|
182
|
+
// For now, return opaque token
|
|
183
|
+
return tokenId;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Validate access token
|
|
188
|
+
*/
|
|
189
|
+
export function validateAccessToken(token: string): AuthResult {
|
|
190
|
+
const payload = tokenStore.get(token);
|
|
191
|
+
|
|
192
|
+
if (!payload) {
|
|
193
|
+
return { valid: false, error: "Invalid token" };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const now = Math.floor(Date.now() / 1000);
|
|
197
|
+
if (payload.exp < now) {
|
|
198
|
+
tokenStore.delete(token);
|
|
199
|
+
return { valid: false, error: "Token expired" };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
valid: true,
|
|
204
|
+
payload,
|
|
205
|
+
scopes: payload.scope.split(" "),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Revoke access token
|
|
211
|
+
*/
|
|
212
|
+
export function revokeAccessToken(token: string): boolean {
|
|
213
|
+
return tokenStore.delete(token);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// Authorization Code Flow
|
|
218
|
+
// ============================================================================
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Generate authorization code
|
|
222
|
+
*/
|
|
223
|
+
export function generateAuthorizationCode(
|
|
224
|
+
clientId: string,
|
|
225
|
+
scopes: string[],
|
|
226
|
+
codeChallenge: string
|
|
227
|
+
): string {
|
|
228
|
+
const code = crypto.randomBytes(32).toString("base64url");
|
|
229
|
+
|
|
230
|
+
authCodeStore.set(code, {
|
|
231
|
+
clientId,
|
|
232
|
+
scopes,
|
|
233
|
+
codeChallenge,
|
|
234
|
+
expiresAt: Date.now() + 600000, // 10 minutes
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return code;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Exchange authorization code for tokens
|
|
242
|
+
*/
|
|
243
|
+
export function exchangeAuthorizationCode(
|
|
244
|
+
config: OAuthConfig,
|
|
245
|
+
code: string,
|
|
246
|
+
clientId: string,
|
|
247
|
+
codeVerifier: string
|
|
248
|
+
): { accessToken?: string; error?: string } {
|
|
249
|
+
const authCode = authCodeStore.get(code);
|
|
250
|
+
|
|
251
|
+
if (!authCode) {
|
|
252
|
+
return { error: "Invalid authorization code" };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (authCode.expiresAt < Date.now()) {
|
|
256
|
+
authCodeStore.delete(code);
|
|
257
|
+
return { error: "Authorization code expired" };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (authCode.clientId !== clientId) {
|
|
261
|
+
return { error: "Client ID mismatch" };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Verify PKCE
|
|
265
|
+
if (!verifyPKCE(codeVerifier, authCode.codeChallenge)) {
|
|
266
|
+
return { error: "PKCE verification failed" };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Delete used code
|
|
270
|
+
authCodeStore.delete(code);
|
|
271
|
+
|
|
272
|
+
// Generate access token
|
|
273
|
+
const accessToken = generateAccessToken(config, clientId, authCode.scopes);
|
|
274
|
+
|
|
275
|
+
return { accessToken };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// Scope Validation
|
|
280
|
+
// ============================================================================
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Check if token has required scope
|
|
284
|
+
*/
|
|
285
|
+
export function hasScope(authResult: AuthResult, requiredScope: MCPScope): boolean {
|
|
286
|
+
if (!authResult.valid || !authResult.scopes) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Admin wildcard
|
|
291
|
+
if (authResult.scopes.includes("admin:*")) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check prefix wildcards (e.g., "read:*" matches "read:project")
|
|
296
|
+
const [category] = requiredScope.split(":");
|
|
297
|
+
if (authResult.scopes.includes(`${category}:*`)) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return authResult.scopes.includes(requiredScope);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Check if token has all required scopes
|
|
306
|
+
*/
|
|
307
|
+
export function hasAllScopes(authResult: AuthResult, requiredScopes: MCPScope[]): boolean {
|
|
308
|
+
return requiredScopes.every(scope => hasScope(authResult, scope));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check if token has any of the required scopes
|
|
313
|
+
*/
|
|
314
|
+
export function hasAnyScope(authResult: AuthResult, requiredScopes: MCPScope[]): boolean {
|
|
315
|
+
return requiredScopes.some(scope => hasScope(authResult, scope));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ============================================================================
|
|
319
|
+
// HTTP Middleware
|
|
320
|
+
// ============================================================================
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Extract bearer token from request
|
|
324
|
+
*/
|
|
325
|
+
export function extractBearerToken(req: IncomingMessage): string | null {
|
|
326
|
+
const authHeader = req.headers.authorization;
|
|
327
|
+
|
|
328
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return authHeader.substring(7);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Authentication middleware
|
|
337
|
+
*/
|
|
338
|
+
export function createAuthMiddleware(config: OAuthConfig) {
|
|
339
|
+
return function authMiddleware(
|
|
340
|
+
req: IncomingMessage,
|
|
341
|
+
res: ServerResponse,
|
|
342
|
+
next: () => void
|
|
343
|
+
): void {
|
|
344
|
+
const token = extractBearerToken(req);
|
|
345
|
+
|
|
346
|
+
if (!token) {
|
|
347
|
+
res.writeHead(401, {
|
|
348
|
+
"Content-Type": "application/json",
|
|
349
|
+
"WWW-Authenticate": 'Bearer realm="MCP Server"',
|
|
350
|
+
});
|
|
351
|
+
res.end(JSON.stringify({ error: "Authentication required" }));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const result = validateAccessToken(token);
|
|
356
|
+
|
|
357
|
+
if (!result.valid) {
|
|
358
|
+
res.writeHead(401, {
|
|
359
|
+
"Content-Type": "application/json",
|
|
360
|
+
"WWW-Authenticate": `Bearer realm="MCP Server", error="${result.error}"`,
|
|
361
|
+
});
|
|
362
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Attach auth result to request for scope checking
|
|
367
|
+
(req as any).auth = result;
|
|
368
|
+
next();
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Scope enforcement middleware
|
|
374
|
+
*/
|
|
375
|
+
export function requireScopes(...requiredScopes: MCPScope[]) {
|
|
376
|
+
return function scopeMiddleware(
|
|
377
|
+
req: IncomingMessage,
|
|
378
|
+
res: ServerResponse,
|
|
379
|
+
next: () => void
|
|
380
|
+
): void {
|
|
381
|
+
const authResult = (req as any).auth as AuthResult | undefined;
|
|
382
|
+
|
|
383
|
+
if (!authResult || !hasAllScopes(authResult, requiredScopes)) {
|
|
384
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
385
|
+
res.end(JSON.stringify({
|
|
386
|
+
error: "Insufficient scope",
|
|
387
|
+
required: requiredScopes,
|
|
388
|
+
granted: authResult?.scopes || [],
|
|
389
|
+
}));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
next();
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ============================================================================
|
|
398
|
+
// API Key Authentication (Simple Alternative)
|
|
399
|
+
// ============================================================================
|
|
400
|
+
|
|
401
|
+
const apiKeyStore = new Map<string, { scopes: string[]; name: string }>();
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Create API key
|
|
405
|
+
*/
|
|
406
|
+
export function createApiKey(name: string, scopes: string[]): string {
|
|
407
|
+
const key = `ak_${crypto.randomBytes(24).toString("base64url")}`;
|
|
408
|
+
apiKeyStore.set(key, { name, scopes });
|
|
409
|
+
return key;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Validate API key
|
|
414
|
+
*/
|
|
415
|
+
export function validateApiKey(key: string): AuthResult {
|
|
416
|
+
const data = apiKeyStore.get(key);
|
|
417
|
+
|
|
418
|
+
if (!data) {
|
|
419
|
+
return { valid: false, error: "Invalid API key" };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
valid: true,
|
|
424
|
+
scopes: data.scopes,
|
|
425
|
+
payload: {
|
|
426
|
+
sub: data.name,
|
|
427
|
+
iss: "api-key",
|
|
428
|
+
aud: "mcp-gateway",
|
|
429
|
+
exp: Number.MAX_SAFE_INTEGER,
|
|
430
|
+
iat: Math.floor(Date.now() / 1000),
|
|
431
|
+
scope: data.scopes.join(" "),
|
|
432
|
+
client_id: data.name,
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Extract API key from request (X-API-Key header)
|
|
439
|
+
*/
|
|
440
|
+
export function extractApiKey(req: IncomingMessage): string | null {
|
|
441
|
+
return req.headers["x-api-key"] as string || null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export default {
|
|
445
|
+
generatePKCE,
|
|
446
|
+
verifyPKCE,
|
|
447
|
+
generateAccessToken,
|
|
448
|
+
validateAccessToken,
|
|
449
|
+
revokeAccessToken,
|
|
450
|
+
generateAuthorizationCode,
|
|
451
|
+
exchangeAuthorizationCode,
|
|
452
|
+
hasScope,
|
|
453
|
+
hasAllScopes,
|
|
454
|
+
hasAnyScope,
|
|
455
|
+
extractBearerToken,
|
|
456
|
+
createAuthMiddleware,
|
|
457
|
+
requireScopes,
|
|
458
|
+
createApiKey,
|
|
459
|
+
validateApiKey,
|
|
460
|
+
extractApiKey,
|
|
461
|
+
MCP_SCOPES,
|
|
462
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Scope Definitions and Tool-to-Scope Mapping
|
|
3
|
+
*
|
|
4
|
+
* Defines the scope requirements for each MCP tool,
|
|
5
|
+
* enabling fine-grained access control.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { MCPScope } from "./oauth.js";
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Tool-to-Scope Mapping
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Maps each tool to its required scopes
|
|
16
|
+
* Tools can require multiple scopes (AND logic)
|
|
17
|
+
*/
|
|
18
|
+
export const TOOL_SCOPES: Record<string, MCPScope[]> = {
|
|
19
|
+
// Read-only tools
|
|
20
|
+
"get_project_context": ["read:project"],
|
|
21
|
+
"get_project_intelligence": ["read:project"],
|
|
22
|
+
"analyze_dependencies": ["read:graph"],
|
|
23
|
+
"get_impact_zone": ["read:graph"],
|
|
24
|
+
"search_knowledge": ["read:search"],
|
|
25
|
+
"search_code_logic": ["read:search"],
|
|
26
|
+
|
|
27
|
+
// Write tools
|
|
28
|
+
"force_sync": ["write:sync"],
|
|
29
|
+
"clear_cache": ["write:cache"],
|
|
30
|
+
|
|
31
|
+
// Execution tools
|
|
32
|
+
"execute_analysis": ["execute:analysis"],
|
|
33
|
+
|
|
34
|
+
// Admin tools
|
|
35
|
+
"update_config": ["admin:config"],
|
|
36
|
+
"manage_users": ["admin:users"],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Default scopes for new clients (minimal access)
|
|
41
|
+
*/
|
|
42
|
+
export const DEFAULT_SCOPES: MCPScope[] = [
|
|
43
|
+
"read:project",
|
|
44
|
+
"read:graph",
|
|
45
|
+
"read:search",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Full read access scopes
|
|
50
|
+
*/
|
|
51
|
+
export const READ_ALL_SCOPES: MCPScope[] = [
|
|
52
|
+
"read:project",
|
|
53
|
+
"read:graph",
|
|
54
|
+
"read:search",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Scopes for trusted agents (read + execute)
|
|
59
|
+
*/
|
|
60
|
+
export const AGENT_SCOPES: MCPScope[] = [
|
|
61
|
+
"read:project",
|
|
62
|
+
"read:graph",
|
|
63
|
+
"read:search",
|
|
64
|
+
"execute:tools",
|
|
65
|
+
"execute:analysis",
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Full access scopes (for admin clients)
|
|
70
|
+
*/
|
|
71
|
+
export const FULL_ACCESS_SCOPES: MCPScope[] = [
|
|
72
|
+
"read:project",
|
|
73
|
+
"read:graph",
|
|
74
|
+
"read:search",
|
|
75
|
+
"write:sync",
|
|
76
|
+
"write:cache",
|
|
77
|
+
"execute:tools",
|
|
78
|
+
"execute:analysis",
|
|
79
|
+
"admin:config",
|
|
80
|
+
"admin:users",
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Scope Helpers
|
|
85
|
+
// ============================================================================
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get required scopes for a tool
|
|
89
|
+
*/
|
|
90
|
+
export function getRequiredScopes(toolName: string): MCPScope[] {
|
|
91
|
+
return TOOL_SCOPES[toolName] || ["execute:tools"];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if scopes are sufficient for a tool
|
|
96
|
+
*/
|
|
97
|
+
export function hasToolAccess(grantedScopes: string[], toolName: string): boolean {
|
|
98
|
+
const required = getRequiredScopes(toolName);
|
|
99
|
+
|
|
100
|
+
// Admin wildcard check
|
|
101
|
+
if (grantedScopes.includes("admin:*")) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check each required scope
|
|
106
|
+
return required.every(scope => {
|
|
107
|
+
// Direct match
|
|
108
|
+
if (grantedScopes.includes(scope)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Category wildcard (e.g., "read:*" matches "read:project")
|
|
113
|
+
const [category] = scope.split(":");
|
|
114
|
+
if (grantedScopes.includes(`${category}:*`)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return false;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get human-readable scope description
|
|
124
|
+
*/
|
|
125
|
+
export function getScopeDescription(scope: MCPScope): string {
|
|
126
|
+
const descriptions: Record<MCPScope, string> = {
|
|
127
|
+
"read:project": "Read project context and structure",
|
|
128
|
+
"read:graph": "Read dependency graph",
|
|
129
|
+
"read:search": "Execute search queries",
|
|
130
|
+
"write:sync": "Trigger sync operations",
|
|
131
|
+
"write:cache": "Modify cache",
|
|
132
|
+
"execute:tools": "Execute MCP tools",
|
|
133
|
+
"execute:analysis": "Run code analysis",
|
|
134
|
+
"admin:config": "Modify server configuration",
|
|
135
|
+
"admin:users": "Manage user access",
|
|
136
|
+
"admin:*": "Full administrative access",
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return descriptions[scope] || scope;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Validate scope string format
|
|
144
|
+
*/
|
|
145
|
+
export function isValidScope(scope: string): scope is MCPScope {
|
|
146
|
+
const validScopes = Object.keys(TOOL_SCOPES).flatMap(tool => TOOL_SCOPES[tool]);
|
|
147
|
+
const uniqueScopes = [...new Set(validScopes), "admin:*", "read:*", "write:*", "execute:*"];
|
|
148
|
+
return uniqueScopes.includes(scope as MCPScope);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Parse scope string (space-separated) into array
|
|
153
|
+
*/
|
|
154
|
+
export function parseScopes(scopeString: string): MCPScope[] {
|
|
155
|
+
return scopeString
|
|
156
|
+
.split(" ")
|
|
157
|
+
.filter(s => s.length > 0)
|
|
158
|
+
.filter(isValidScope);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Serialize scopes to string
|
|
163
|
+
*/
|
|
164
|
+
export function serializeScopes(scopes: MCPScope[]): string {
|
|
165
|
+
return scopes.join(" ");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Scope Request Helpers
|
|
170
|
+
// ============================================================================
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get minimum scopes needed for a set of tools
|
|
174
|
+
*/
|
|
175
|
+
export function getScopesForTools(toolNames: string[]): MCPScope[] {
|
|
176
|
+
const allScopes = toolNames.flatMap(tool => getRequiredScopes(tool));
|
|
177
|
+
return [...new Set(allScopes)]; // deduplicate
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check if scope request is within allowed limits
|
|
182
|
+
*/
|
|
183
|
+
export function validateScopeRequest(
|
|
184
|
+
requestedScopes: MCPScope[],
|
|
185
|
+
allowedScopes: MCPScope[]
|
|
186
|
+
): { valid: boolean; denied: MCPScope[] } {
|
|
187
|
+
const denied = requestedScopes.filter(scope => {
|
|
188
|
+
// Admin wildcard allows all
|
|
189
|
+
if (allowedScopes.includes("admin:*")) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Direct match
|
|
194
|
+
if (allowedScopes.includes(scope)) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Category wildcard
|
|
199
|
+
const [category] = scope.split(":");
|
|
200
|
+
if (allowedScopes.includes(`${category}:*` as MCPScope)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return true;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
valid: denied.length === 0,
|
|
209
|
+
denied,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default {
|
|
214
|
+
TOOL_SCOPES,
|
|
215
|
+
DEFAULT_SCOPES,
|
|
216
|
+
READ_ALL_SCOPES,
|
|
217
|
+
AGENT_SCOPES,
|
|
218
|
+
FULL_ACCESS_SCOPES,
|
|
219
|
+
getRequiredScopes,
|
|
220
|
+
hasToolAccess,
|
|
221
|
+
getScopeDescription,
|
|
222
|
+
isValidScope,
|
|
223
|
+
parseScopes,
|
|
224
|
+
serializeScopes,
|
|
225
|
+
getScopesForTools,
|
|
226
|
+
validateScopeRequest,
|
|
227
|
+
};
|