@musashishao/agent-kit 1.8.2 → 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-architect.md +39 -0
- package/.agent/agents/ai-asset-factory.md +700 -0
- package/.agent/agents/ai-audio-factory.md +503 -0
- package/.agent/agents/cloud-engineer.md +39 -0
- package/.agent/agents/game-developer.md +190 -89
- package/.agent/agents/marketing-specialist.md +41 -0
- package/.agent/agents/orchestrator.md +113 -3
- package/.agent/agents/penetration-tester.md +15 -1
- 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 +115 -2
- package/.agent/rules/CODE_RULES.md +73 -0
- package/.agent/rules/GEMINI.md +26 -1
- package/.agent/rules/MEMORY_STATE.md +110 -0
- package/.agent/rules/REFERENCE.md +40 -58
- 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/3d-web-experience/SKILL.md +386 -0
- package/.agent/skills/DEPENDENCIES.md +54 -0
- package/.agent/skills/ab-test-setup/SKILL.md +77 -0
- package/.agent/skills/active-directory-attacks/SKILL.md +59 -0
- package/.agent/skills/agent-evaluation/SKILL.md +430 -0
- package/.agent/skills/agent-memory-systems/SKILL.md +426 -0
- package/.agent/skills/agent-tool-builder/SKILL.md +139 -0
- package/.agent/skills/ai-agents-architect/SKILL.md +115 -0
- package/.agent/skills/ai-product/SKILL.md +86 -0
- package/.agent/skills/ai-wrapper-product/SKILL.md +90 -0
- package/.agent/skills/analytics-tracking/SKILL.md +88 -0
- package/.agent/skills/anti-hallucination/SKILL.md +295 -0
- package/.agent/skills/anti-hallucination/scripts/check_hallucination.py +299 -0
- package/.agent/skills/api-fuzzing-bug-bounty/SKILL.md +66 -0
- package/.agent/skills/app-store-optimization/SKILL.md +66 -0
- package/.agent/skills/autonomous-agent-patterns/SKILL.md +414 -0
- package/.agent/skills/aws-penetration-testing/SKILL.md +50 -0
- package/.agent/skills/aws-serverless/SKILL.md +327 -0
- package/.agent/skills/azure-functions/SKILL.md +340 -0
- package/.agent/skills/bifurcation-analysis/SKILL.md +56 -0
- package/.agent/skills/brainstorming/SKILL.md +80 -6
- package/.agent/skills/broken-authentication/SKILL.md +53 -0
- package/.agent/skills/browser-automation/SKILL.md +408 -0
- package/.agent/skills/browser-extension-builder/SKILL.md +422 -0
- package/.agent/skills/bullmq-specialist/SKILL.md +424 -0
- package/.agent/skills/bun-development/SKILL.md +386 -0
- package/.agent/skills/burp-suite-testing/SKILL.md +60 -0
- package/.agent/skills/clerk-auth/SKILL.md +432 -0
- package/.agent/skills/cloud-penetration-testing/SKILL.md +51 -0
- package/.agent/skills/copywriting/SKILL.md +66 -0
- package/.agent/skills/crewai/SKILL.md +470 -0
- package/.agent/skills/decision-memory/SKILL.md +317 -0
- package/.agent/skills/discord-bot-architect/SKILL.md +447 -0
- package/.agent/skills/email-sequence/SKILL.md +73 -0
- package/.agent/skills/emergence-detector/SKILL.md +230 -0
- package/.agent/skills/emergence-detector/scripts/check_emergence.py +265 -0
- package/.agent/skills/ethical-hacking-methodology/SKILL.md +67 -0
- package/.agent/skills/explained-qa/SKILL.md +142 -0
- package/.agent/skills/explained-qa/game-terminology.md +214 -0
- package/.agent/skills/firebase/SKILL.md +377 -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/godot-expert/SKILL.md +462 -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/npc-ai-integration/SKILL.md +110 -0
- package/.agent/skills/game-development/procedural-generation/SKILL.md +168 -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-integration/SKILL.md +358 -0
- package/.agent/skills/game-development/unity-mobile-optimization/SKILL.md +271 -0
- package/.agent/skills/game-development/webgpu-shading/SKILL.md +209 -0
- package/.agent/skills/gcp-cloud-run/SKILL.md +358 -0
- package/.agent/skills/graphql/SKILL.md +492 -0
- package/.agent/skills/idor-testing/SKILL.md +64 -0
- package/.agent/skills/inngest/SKILL.md +128 -0
- package/.agent/skills/intent-capture/SKILL.md +65 -0
- package/.agent/skills/langfuse/SKILL.md +415 -0
- package/.agent/skills/langgraph/SKILL.md +360 -0
- package/.agent/skills/launch-strategy/SKILL.md +68 -0
- package/.agent/skills/linux-privilege-escalation/SKILL.md +62 -0
- package/.agent/skills/llm-app-patterns/SKILL.md +367 -0
- package/.agent/skills/marketing-ideas/SKILL.md +66 -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/metasploit-framework/SKILL.md +60 -0
- package/.agent/skills/micro-saas-launcher/SKILL.md +93 -0
- package/.agent/skills/neon-postgres/SKILL.md +339 -0
- package/.agent/skills/paid-ads/SKILL.md +64 -0
- package/.agent/skills/supabase-integration/SKILL.md +411 -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 +38 -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 +39 -0
- package/.agent/workflows/next.md +1 -0
- package/.agent/workflows/orchestrate.md +12 -0
- package/.agent/workflows/pentest.md +39 -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 +38 -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/README.md +4 -4
- package/bin/cli.js +411 -111
- package/package.json +1 -2
- package/docs/AI_DATA_INFRASTRUCTURE.md +0 -288
- package/docs/CHANGELOG_AI_INFRA.md +0 -111
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry Integration for MCP Gateway
|
|
3
|
+
*
|
|
4
|
+
* Provides distributed tracing and metrics for MCP operations.
|
|
5
|
+
*
|
|
6
|
+
* This is a lightweight implementation that works without external dependencies.
|
|
7
|
+
* For production, install @opentelemetry/api and configure exporters.
|
|
8
|
+
*
|
|
9
|
+
* @see https://opentelemetry.io
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Types
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
export interface OtelConfig {
|
|
17
|
+
/** Service name for tracing */
|
|
18
|
+
serviceName: string;
|
|
19
|
+
/** Service version */
|
|
20
|
+
serviceVersion?: string;
|
|
21
|
+
/** OTLP endpoint (e.g., http://localhost:4318) */
|
|
22
|
+
otlpEndpoint?: string;
|
|
23
|
+
/** Enable console exporter for debugging */
|
|
24
|
+
debugMode?: boolean;
|
|
25
|
+
/** Sampling ratio (0.0 - 1.0) */
|
|
26
|
+
samplingRatio?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SpanConfig {
|
|
30
|
+
/** Span name */
|
|
31
|
+
name: string;
|
|
32
|
+
/** Custom attributes */
|
|
33
|
+
attributes?: Record<string, string | number | boolean>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ToolCallMetrics {
|
|
37
|
+
toolName: string;
|
|
38
|
+
duration: number;
|
|
39
|
+
success: boolean;
|
|
40
|
+
inputTokens?: number;
|
|
41
|
+
outputTokens?: number;
|
|
42
|
+
errorType?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface SpanContext {
|
|
46
|
+
traceId: string;
|
|
47
|
+
spanId: string;
|
|
48
|
+
traceFlags: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface Span {
|
|
52
|
+
setAttribute(key: string, value: string | number | boolean): void;
|
|
53
|
+
setStatus(status: { code: number; message?: string }): void;
|
|
54
|
+
recordException(error: Error): void;
|
|
55
|
+
end(): void;
|
|
56
|
+
spanContext(): SpanContext;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Simple Span Implementation
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
class SimpleSpan implements Span {
|
|
64
|
+
private name: string;
|
|
65
|
+
private attributes: Record<string, string | number | boolean> = {};
|
|
66
|
+
private status: { code: number; message?: string } = { code: 0 };
|
|
67
|
+
private startTime: number;
|
|
68
|
+
private endTime?: number;
|
|
69
|
+
private error?: Error;
|
|
70
|
+
private _spanContext: SpanContext;
|
|
71
|
+
private debug: boolean;
|
|
72
|
+
|
|
73
|
+
constructor(name: string, debug: boolean = false) {
|
|
74
|
+
this.name = name;
|
|
75
|
+
this.debug = debug;
|
|
76
|
+
this.startTime = Date.now();
|
|
77
|
+
this._spanContext = {
|
|
78
|
+
traceId: this.generateId(32),
|
|
79
|
+
spanId: this.generateId(16),
|
|
80
|
+
traceFlags: 1,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private generateId(length: number): string {
|
|
85
|
+
const chars = "0123456789abcdef";
|
|
86
|
+
let result = "";
|
|
87
|
+
for (let i = 0; i < length; i++) {
|
|
88
|
+
result += chars[Math.floor(Math.random() * chars.length)];
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setAttribute(key: string, value: string | number | boolean): void {
|
|
94
|
+
this.attributes[key] = value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setStatus(status: { code: number; message?: string }): void {
|
|
98
|
+
this.status = status;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
recordException(error: Error): void {
|
|
102
|
+
this.error = error;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
end(): void {
|
|
106
|
+
this.endTime = Date.now();
|
|
107
|
+
if (this.debug) {
|
|
108
|
+
console.log(`[TRACE] ${this.name}`, {
|
|
109
|
+
duration: this.endTime - this.startTime,
|
|
110
|
+
attributes: this.attributes,
|
|
111
|
+
status: this.status,
|
|
112
|
+
error: this.error?.message,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
spanContext(): SpanContext {
|
|
118
|
+
return this._spanContext;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// Tracer Management
|
|
124
|
+
// ============================================================================
|
|
125
|
+
|
|
126
|
+
let tracerConfig: OtelConfig = {
|
|
127
|
+
serviceName: "mcp-gateway",
|
|
128
|
+
serviceVersion: "2.0.0",
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Initialize tracing configuration
|
|
133
|
+
*/
|
|
134
|
+
export function initializeTracing(config: OtelConfig): void {
|
|
135
|
+
tracerConfig = config;
|
|
136
|
+
console.error(`[OTel] Tracing initialized for ${config.serviceName}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Start a new span
|
|
141
|
+
*/
|
|
142
|
+
export function startSpan(config: SpanConfig): Span {
|
|
143
|
+
const span = new SimpleSpan(config.name, tracerConfig.debugMode);
|
|
144
|
+
|
|
145
|
+
if (config.attributes) {
|
|
146
|
+
for (const [key, value] of Object.entries(config.attributes)) {
|
|
147
|
+
span.setAttribute(key, value);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return span;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Execute function within a span context
|
|
156
|
+
*/
|
|
157
|
+
export async function withSpan<T>(
|
|
158
|
+
config: SpanConfig,
|
|
159
|
+
fn: (span: Span) => Promise<T>
|
|
160
|
+
): Promise<T> {
|
|
161
|
+
const span = startSpan(config);
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const result = await fn(span);
|
|
165
|
+
span.setStatus({ code: 0 }); // OK
|
|
166
|
+
return result;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
span.setStatus({
|
|
169
|
+
code: 2, // ERROR
|
|
170
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
171
|
+
});
|
|
172
|
+
span.recordException(error as Error);
|
|
173
|
+
throw error;
|
|
174
|
+
} finally {
|
|
175
|
+
span.end();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Trace an MCP tool call
|
|
181
|
+
*/
|
|
182
|
+
export async function traceToolCall<T>(
|
|
183
|
+
toolName: string,
|
|
184
|
+
input: unknown,
|
|
185
|
+
fn: () => Promise<T>
|
|
186
|
+
): Promise<T> {
|
|
187
|
+
return withSpan(
|
|
188
|
+
{
|
|
189
|
+
name: `mcp.tool.${toolName}`,
|
|
190
|
+
attributes: {
|
|
191
|
+
"mcp.tool.name": toolName,
|
|
192
|
+
"mcp.tool.input_size": JSON.stringify(input).length,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
async (span) => {
|
|
196
|
+
const startTime = Date.now();
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const result = await fn();
|
|
200
|
+
|
|
201
|
+
const duration = Date.now() - startTime;
|
|
202
|
+
span.setAttribute("mcp.tool.duration_ms", duration);
|
|
203
|
+
span.setAttribute("mcp.tool.success", true);
|
|
204
|
+
|
|
205
|
+
// Record metrics
|
|
206
|
+
recordToolMetrics({
|
|
207
|
+
toolName,
|
|
208
|
+
duration,
|
|
209
|
+
success: true,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return result;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
const duration = Date.now() - startTime;
|
|
215
|
+
span.setAttribute("mcp.tool.duration_ms", duration);
|
|
216
|
+
span.setAttribute("mcp.tool.success", false);
|
|
217
|
+
span.setAttribute("mcp.tool.error_type", (error as Error).name);
|
|
218
|
+
|
|
219
|
+
recordToolMetrics({
|
|
220
|
+
toolName,
|
|
221
|
+
duration,
|
|
222
|
+
success: false,
|
|
223
|
+
errorType: (error as Error).name,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Trace an MCP request (session-level)
|
|
234
|
+
*/
|
|
235
|
+
export async function traceRequest<T>(
|
|
236
|
+
requestId: string,
|
|
237
|
+
fn: () => Promise<T>
|
|
238
|
+
): Promise<T> {
|
|
239
|
+
return withSpan(
|
|
240
|
+
{
|
|
241
|
+
name: "mcp.request",
|
|
242
|
+
attributes: {
|
|
243
|
+
"mcp.request.id": requestId,
|
|
244
|
+
"mcp.request.timestamp": new Date().toISOString(),
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
async (span) => fn()
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================================
|
|
252
|
+
// Metrics Collection
|
|
253
|
+
// ============================================================================
|
|
254
|
+
|
|
255
|
+
// In-memory metrics store
|
|
256
|
+
const metricsStore = {
|
|
257
|
+
toolCalls: new Map<string, number>(),
|
|
258
|
+
toolDurations: new Map<string, number[]>(),
|
|
259
|
+
toolErrors: new Map<string, number>(),
|
|
260
|
+
requestCount: 0,
|
|
261
|
+
totalDuration: 0,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Record tool call metrics
|
|
266
|
+
*/
|
|
267
|
+
export function recordToolMetrics(metrics: ToolCallMetrics): void {
|
|
268
|
+
const { toolName, duration, success, errorType } = metrics;
|
|
269
|
+
|
|
270
|
+
// Increment call count
|
|
271
|
+
metricsStore.toolCalls.set(
|
|
272
|
+
toolName,
|
|
273
|
+
(metricsStore.toolCalls.get(toolName) || 0) + 1
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Record duration
|
|
277
|
+
const durations = metricsStore.toolDurations.get(toolName) || [];
|
|
278
|
+
durations.push(duration);
|
|
279
|
+
// Keep last 1000 samples
|
|
280
|
+
if (durations.length > 1000) durations.shift();
|
|
281
|
+
metricsStore.toolDurations.set(toolName, durations);
|
|
282
|
+
|
|
283
|
+
// Record errors
|
|
284
|
+
if (!success) {
|
|
285
|
+
const errorKey = `${toolName}:${errorType || "unknown"}`;
|
|
286
|
+
metricsStore.toolErrors.set(
|
|
287
|
+
errorKey,
|
|
288
|
+
(metricsStore.toolErrors.get(errorKey) || 0) + 1
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
metricsStore.requestCount++;
|
|
293
|
+
metricsStore.totalDuration += duration;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get aggregated metrics
|
|
298
|
+
*/
|
|
299
|
+
export function getMetrics(): {
|
|
300
|
+
tools: Record<string, {
|
|
301
|
+
calls: number;
|
|
302
|
+
avgDuration: number;
|
|
303
|
+
p95Duration: number;
|
|
304
|
+
errorRate: number;
|
|
305
|
+
}>;
|
|
306
|
+
summary: {
|
|
307
|
+
totalRequests: number;
|
|
308
|
+
avgDuration: number;
|
|
309
|
+
uptime: number;
|
|
310
|
+
};
|
|
311
|
+
} {
|
|
312
|
+
const tools: Record<string, {
|
|
313
|
+
calls: number;
|
|
314
|
+
avgDuration: number;
|
|
315
|
+
p95Duration: number;
|
|
316
|
+
errorRate: number;
|
|
317
|
+
}> = {};
|
|
318
|
+
|
|
319
|
+
for (const [toolName, calls] of metricsStore.toolCalls) {
|
|
320
|
+
const durations = metricsStore.toolDurations.get(toolName) || [];
|
|
321
|
+
const errors = Array.from(metricsStore.toolErrors.entries())
|
|
322
|
+
.filter(([key]) => key.startsWith(toolName))
|
|
323
|
+
.reduce((sum, [, count]) => sum + count, 0);
|
|
324
|
+
|
|
325
|
+
const sortedDurations = [...durations].sort((a, b) => a - b);
|
|
326
|
+
const p95Index = Math.floor(sortedDurations.length * 0.95);
|
|
327
|
+
|
|
328
|
+
tools[toolName] = {
|
|
329
|
+
calls,
|
|
330
|
+
avgDuration: durations.length > 0
|
|
331
|
+
? durations.reduce((a, b) => a + b, 0) / durations.length
|
|
332
|
+
: 0,
|
|
333
|
+
p95Duration: sortedDurations[p95Index] || 0,
|
|
334
|
+
errorRate: calls > 0 ? errors / calls : 0,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
tools,
|
|
340
|
+
summary: {
|
|
341
|
+
totalRequests: metricsStore.requestCount,
|
|
342
|
+
avgDuration: metricsStore.requestCount > 0
|
|
343
|
+
? metricsStore.totalDuration / metricsStore.requestCount
|
|
344
|
+
: 0,
|
|
345
|
+
uptime: process.uptime(),
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Reset metrics (for testing)
|
|
352
|
+
*/
|
|
353
|
+
export function resetMetrics(): void {
|
|
354
|
+
metricsStore.toolCalls.clear();
|
|
355
|
+
metricsStore.toolDurations.clear();
|
|
356
|
+
metricsStore.toolErrors.clear();
|
|
357
|
+
metricsStore.requestCount = 0;
|
|
358
|
+
metricsStore.totalDuration = 0;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ============================================================================
|
|
362
|
+
// Context Propagation
|
|
363
|
+
// ============================================================================
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Extract trace context from HTTP headers (W3C format)
|
|
367
|
+
*/
|
|
368
|
+
export function extractTraceContext(headers: Record<string, string | string[] | undefined>): SpanContext | null {
|
|
369
|
+
const traceparent = headers["traceparent"];
|
|
370
|
+
if (!traceparent || typeof traceparent !== "string") {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Format: version-traceid-parentid-flags
|
|
375
|
+
const parts = traceparent.split("-");
|
|
376
|
+
if (parts.length !== 4) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
traceId: parts[1],
|
|
382
|
+
spanId: parts[2],
|
|
383
|
+
traceFlags: parseInt(parts[3], 16),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Inject trace context into HTTP headers
|
|
389
|
+
*/
|
|
390
|
+
export function injectTraceContext(headers: Record<string, string>, context: SpanContext): void {
|
|
391
|
+
headers["traceparent"] = `00-${context.traceId}-${context.spanId}-0${context.traceFlags}`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export default {
|
|
395
|
+
initializeTracing,
|
|
396
|
+
startSpan,
|
|
397
|
+
withSpan,
|
|
398
|
+
traceToolCall,
|
|
399
|
+
traceRequest,
|
|
400
|
+
recordToolMetrics,
|
|
401
|
+
getMetrics,
|
|
402
|
+
resetMetrics,
|
|
403
|
+
extractTraceContext,
|
|
404
|
+
injectTraceContext,
|
|
405
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP Transport for MCP Gateway
|
|
3
|
+
*
|
|
4
|
+
* Modern transport layer supporting:
|
|
5
|
+
* - HTTP POST/GET for request/response
|
|
6
|
+
* - Optional SSE for server-initiated streaming
|
|
7
|
+
* - Stateless operation mode
|
|
8
|
+
* - Multiple concurrent clients
|
|
9
|
+
*
|
|
10
|
+
* @see https://modelcontextprotocol.io/docs/transports
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
14
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
15
|
+
import type { IncomingMessage, ServerResponse } from "http";
|
|
16
|
+
import { createServer, Server } from "http";
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface StreamableHttpOptions {
|
|
23
|
+
/** Port to listen on */
|
|
24
|
+
port: number;
|
|
25
|
+
/** Host to bind to (default: 0.0.0.0) */
|
|
26
|
+
host?: string;
|
|
27
|
+
/** Enable SSE for server-initiated streaming */
|
|
28
|
+
enableSSE?: boolean;
|
|
29
|
+
/** Enable stateless mode for horizontal scaling */
|
|
30
|
+
stateless?: boolean;
|
|
31
|
+
/** Session timeout in milliseconds */
|
|
32
|
+
sessionTimeout?: number;
|
|
33
|
+
/** Enable OAuth 2.1 authentication */
|
|
34
|
+
enableAuth?: boolean;
|
|
35
|
+
/** CORS origins (default: none) */
|
|
36
|
+
corsOrigins?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface TransportResult {
|
|
40
|
+
transport: StreamableHTTPServerTransport;
|
|
41
|
+
server: Server;
|
|
42
|
+
close: () => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// CORS Middleware
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
function handleCors(
|
|
50
|
+
req: IncomingMessage,
|
|
51
|
+
res: ServerResponse,
|
|
52
|
+
allowedOrigins: string[]
|
|
53
|
+
): boolean {
|
|
54
|
+
const origin = req.headers.origin;
|
|
55
|
+
|
|
56
|
+
if (origin && allowedOrigins.includes(origin)) {
|
|
57
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
58
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
59
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Session-ID");
|
|
60
|
+
res.setHeader("Access-Control-Expose-Headers", "X-Session-ID");
|
|
61
|
+
res.setHeader("Access-Control-Max-Age", "86400");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Handle preflight
|
|
65
|
+
if (req.method === "OPTIONS") {
|
|
66
|
+
res.writeHead(204);
|
|
67
|
+
res.end();
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Health Check Endpoint
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
function handleHealthCheck(req: IncomingMessage, res: ServerResponse): boolean {
|
|
79
|
+
if (req.url === "/health" && req.method === "GET") {
|
|
80
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
81
|
+
res.end(JSON.stringify({
|
|
82
|
+
status: "healthy",
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
version: "2.0.0",
|
|
85
|
+
transport: "streamable-http"
|
|
86
|
+
}));
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Create Streamable HTTP Transport
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
export async function createStreamableHttpTransport(
|
|
97
|
+
options: StreamableHttpOptions
|
|
98
|
+
): Promise<TransportResult> {
|
|
99
|
+
const {
|
|
100
|
+
port,
|
|
101
|
+
host = "0.0.0.0",
|
|
102
|
+
enableSSE = true,
|
|
103
|
+
stateless = true,
|
|
104
|
+
corsOrigins = [],
|
|
105
|
+
} = options;
|
|
106
|
+
|
|
107
|
+
// Session management for stateful mode
|
|
108
|
+
const sessions = new Map<string, StreamableHTTPServerTransport>();
|
|
109
|
+
|
|
110
|
+
// Create HTTP server
|
|
111
|
+
const httpServer = createServer(async (req, res) => {
|
|
112
|
+
// CORS handling
|
|
113
|
+
if (corsOrigins.length > 0 && handleCors(req, res, corsOrigins)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Health check
|
|
118
|
+
if (handleHealthCheck(req, res)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// MCP endpoint
|
|
123
|
+
if (req.url === "/mcp" || req.url === "/") {
|
|
124
|
+
try {
|
|
125
|
+
// Get or create session
|
|
126
|
+
let sessionId = req.headers["x-session-id"] as string;
|
|
127
|
+
let transport: StreamableHTTPServerTransport;
|
|
128
|
+
|
|
129
|
+
if (stateless || !sessionId) {
|
|
130
|
+
// Stateless mode: new transport per request
|
|
131
|
+
transport = new StreamableHTTPServerTransport({
|
|
132
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
// Stateful mode: reuse transport
|
|
136
|
+
transport = sessions.get(sessionId) || new StreamableHTTPServerTransport({
|
|
137
|
+
sessionIdGenerator: () => sessionId,
|
|
138
|
+
});
|
|
139
|
+
sessions.set(sessionId, transport);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Handle the request
|
|
143
|
+
await transport.handleRequest(req, res);
|
|
144
|
+
|
|
145
|
+
// Return session ID in response header
|
|
146
|
+
if (!stateless && transport.sessionId) {
|
|
147
|
+
res.setHeader("X-Session-ID", transport.sessionId);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("[MCP] Request handling error:", error);
|
|
152
|
+
if (!res.headersSent) {
|
|
153
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
154
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// SSE endpoint for server-initiated streaming
|
|
161
|
+
if (enableSSE && req.url === "/sse" && req.method === "GET") {
|
|
162
|
+
const sseTransport = new SSEServerTransport("/sse", res);
|
|
163
|
+
await sseTransport.start();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 404 for unknown paths
|
|
168
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
169
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Start listening
|
|
173
|
+
await new Promise<void>((resolve) => {
|
|
174
|
+
httpServer.listen(port, host, () => {
|
|
175
|
+
console.log(`[MCP] Streamable HTTP transport listening on http://${host}:${port}`);
|
|
176
|
+
console.log(`[MCP] Endpoints: /mcp (main), /health (status)${enableSSE ? ", /sse (streaming)" : ""}`);
|
|
177
|
+
resolve();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Create main transport for server connection
|
|
182
|
+
const mainTransport = new StreamableHTTPServerTransport({
|
|
183
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
transport: mainTransport,
|
|
188
|
+
server: httpServer,
|
|
189
|
+
close: async () => {
|
|
190
|
+
sessions.clear();
|
|
191
|
+
await new Promise<void>((resolve, reject) => {
|
|
192
|
+
httpServer.close((err) => {
|
|
193
|
+
if (err) reject(err);
|
|
194
|
+
else resolve();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// Dual Transport (stdio + HTTP)
|
|
203
|
+
// ============================================================================
|
|
204
|
+
|
|
205
|
+
export interface DualTransportOptions {
|
|
206
|
+
/** Enable stdio transport for local development */
|
|
207
|
+
enableStdio?: boolean;
|
|
208
|
+
/** HTTP transport options */
|
|
209
|
+
http?: StreamableHttpOptions;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function createDualTransport(options: DualTransportOptions) {
|
|
213
|
+
const transports: {
|
|
214
|
+
stdio?: any;
|
|
215
|
+
http?: TransportResult;
|
|
216
|
+
} = {};
|
|
217
|
+
|
|
218
|
+
// Create HTTP transport if configured
|
|
219
|
+
if (options.http) {
|
|
220
|
+
transports.http = await createStreamableHttpTransport(options.http);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// stdio is handled separately in main entry
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
transports,
|
|
227
|
+
closeAll: async () => {
|
|
228
|
+
if (transports.http) {
|
|
229
|
+
await transports.http.close();
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export default createStreamableHttpTransport;
|