unreal-engine-mcp-server 0.2.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/.dockerignore +57 -0
- package/.env.production +25 -0
- package/.eslintrc.json +54 -0
- package/.github/workflows/publish-mcp.yml +75 -0
- package/Dockerfile +54 -0
- package/LICENSE +21 -0
- package/Public/icon.png +0 -0
- package/README.md +209 -0
- package/claude_desktop_config_example.json +13 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +7 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +484 -0
- package/dist/prompts/index.d.ts +14 -0
- package/dist/prompts/index.js +38 -0
- package/dist/python-utils.d.ts +29 -0
- package/dist/python-utils.js +54 -0
- package/dist/resources/actors.d.ts +13 -0
- package/dist/resources/actors.js +83 -0
- package/dist/resources/assets.d.ts +23 -0
- package/dist/resources/assets.js +245 -0
- package/dist/resources/levels.d.ts +17 -0
- package/dist/resources/levels.js +94 -0
- package/dist/tools/actors.d.ts +51 -0
- package/dist/tools/actors.js +459 -0
- package/dist/tools/animation.d.ts +196 -0
- package/dist/tools/animation.js +579 -0
- package/dist/tools/assets.d.ts +21 -0
- package/dist/tools/assets.js +304 -0
- package/dist/tools/audio.d.ts +170 -0
- package/dist/tools/audio.js +416 -0
- package/dist/tools/blueprint.d.ts +144 -0
- package/dist/tools/blueprint.js +652 -0
- package/dist/tools/build_environment_advanced.d.ts +66 -0
- package/dist/tools/build_environment_advanced.js +484 -0
- package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
- package/dist/tools/consolidated-tool-definitions.js +607 -0
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
- package/dist/tools/consolidated-tool-handlers.js +1050 -0
- package/dist/tools/debug.d.ts +185 -0
- package/dist/tools/debug.js +265 -0
- package/dist/tools/editor.d.ts +88 -0
- package/dist/tools/editor.js +365 -0
- package/dist/tools/engine.d.ts +30 -0
- package/dist/tools/engine.js +36 -0
- package/dist/tools/foliage.d.ts +155 -0
- package/dist/tools/foliage.js +525 -0
- package/dist/tools/introspection.d.ts +98 -0
- package/dist/tools/introspection.js +683 -0
- package/dist/tools/landscape.d.ts +158 -0
- package/dist/tools/landscape.js +375 -0
- package/dist/tools/level.d.ts +110 -0
- package/dist/tools/level.js +362 -0
- package/dist/tools/lighting.d.ts +159 -0
- package/dist/tools/lighting.js +1179 -0
- package/dist/tools/materials.d.ts +34 -0
- package/dist/tools/materials.js +146 -0
- package/dist/tools/niagara.d.ts +145 -0
- package/dist/tools/niagara.js +289 -0
- package/dist/tools/performance.d.ts +163 -0
- package/dist/tools/performance.js +412 -0
- package/dist/tools/physics.d.ts +189 -0
- package/dist/tools/physics.js +784 -0
- package/dist/tools/rc.d.ts +110 -0
- package/dist/tools/rc.js +363 -0
- package/dist/tools/sequence.d.ts +112 -0
- package/dist/tools/sequence.js +675 -0
- package/dist/tools/tool-definitions.d.ts +4919 -0
- package/dist/tools/tool-definitions.js +891 -0
- package/dist/tools/tool-handlers.d.ts +47 -0
- package/dist/tools/tool-handlers.js +830 -0
- package/dist/tools/ui.d.ts +171 -0
- package/dist/tools/ui.js +337 -0
- package/dist/tools/visual.d.ts +29 -0
- package/dist/tools/visual.js +67 -0
- package/dist/types/env.d.ts +10 -0
- package/dist/types/env.js +18 -0
- package/dist/types/index.d.ts +323 -0
- package/dist/types/index.js +28 -0
- package/dist/types/tool-types.d.ts +274 -0
- package/dist/types/tool-types.js +13 -0
- package/dist/unreal-bridge.d.ts +126 -0
- package/dist/unreal-bridge.js +992 -0
- package/dist/utils/cache-manager.d.ts +64 -0
- package/dist/utils/cache-manager.js +176 -0
- package/dist/utils/error-handler.d.ts +66 -0
- package/dist/utils/error-handler.js +243 -0
- package/dist/utils/errors.d.ts +133 -0
- package/dist/utils/errors.js +256 -0
- package/dist/utils/http.d.ts +26 -0
- package/dist/utils/http.js +135 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/normalize.d.ts +17 -0
- package/dist/utils/normalize.js +49 -0
- package/dist/utils/response-validator.d.ts +34 -0
- package/dist/utils/response-validator.js +121 -0
- package/dist/utils/safe-json.d.ts +4 -0
- package/dist/utils/safe-json.js +97 -0
- package/dist/utils/stdio-redirect.d.ts +2 -0
- package/dist/utils/stdio-redirect.js +20 -0
- package/dist/utils/validation.d.ts +50 -0
- package/dist/utils/validation.js +173 -0
- package/mcp-config-example.json +14 -0
- package/package.json +63 -0
- package/server.json +60 -0
- package/src/cli.ts +7 -0
- package/src/index.ts +543 -0
- package/src/prompts/index.ts +51 -0
- package/src/python/editor_compat.py +181 -0
- package/src/python-utils.ts +57 -0
- package/src/resources/actors.ts +92 -0
- package/src/resources/assets.ts +251 -0
- package/src/resources/levels.ts +83 -0
- package/src/tools/actors.ts +480 -0
- package/src/tools/animation.ts +713 -0
- package/src/tools/assets.ts +305 -0
- package/src/tools/audio.ts +548 -0
- package/src/tools/blueprint.ts +736 -0
- package/src/tools/build_environment_advanced.ts +526 -0
- package/src/tools/consolidated-tool-definitions.ts +619 -0
- package/src/tools/consolidated-tool-handlers.ts +1093 -0
- package/src/tools/debug.ts +368 -0
- package/src/tools/editor.ts +360 -0
- package/src/tools/engine.ts +32 -0
- package/src/tools/foliage.ts +652 -0
- package/src/tools/introspection.ts +778 -0
- package/src/tools/landscape.ts +523 -0
- package/src/tools/level.ts +410 -0
- package/src/tools/lighting.ts +1316 -0
- package/src/tools/materials.ts +148 -0
- package/src/tools/niagara.ts +312 -0
- package/src/tools/performance.ts +549 -0
- package/src/tools/physics.ts +924 -0
- package/src/tools/rc.ts +437 -0
- package/src/tools/sequence.ts +791 -0
- package/src/tools/tool-definitions.ts +907 -0
- package/src/tools/tool-handlers.ts +941 -0
- package/src/tools/ui.ts +499 -0
- package/src/tools/visual.ts +60 -0
- package/src/types/env.ts +27 -0
- package/src/types/index.ts +414 -0
- package/src/types/tool-types.ts +343 -0
- package/src/unreal-bridge.ts +1118 -0
- package/src/utils/cache-manager.ts +213 -0
- package/src/utils/error-handler.ts +320 -0
- package/src/utils/errors.ts +312 -0
- package/src/utils/http.ts +184 -0
- package/src/utils/logger.ts +30 -0
- package/src/utils/normalize.ts +54 -0
- package/src/utils/response-validator.ts +145 -0
- package/src/utils/safe-json.ts +112 -0
- package/src/utils/stdio-redirect.ts +18 -0
- package/src/utils/validation.ts +212 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced error types for better error handling and recovery
|
|
3
|
+
*/
|
|
4
|
+
export var ErrorCode;
|
|
5
|
+
(function (ErrorCode) {
|
|
6
|
+
// Connection errors
|
|
7
|
+
ErrorCode["CONNECTION_FAILED"] = "CONNECTION_FAILED";
|
|
8
|
+
ErrorCode["CONNECTION_TIMEOUT"] = "CONNECTION_TIMEOUT";
|
|
9
|
+
ErrorCode["CONNECTION_REFUSED"] = "CONNECTION_REFUSED";
|
|
10
|
+
// API errors
|
|
11
|
+
ErrorCode["API_ERROR"] = "API_ERROR";
|
|
12
|
+
ErrorCode["INVALID_RESPONSE"] = "INVALID_RESPONSE";
|
|
13
|
+
ErrorCode["RATE_LIMITED"] = "RATE_LIMITED";
|
|
14
|
+
// Validation errors
|
|
15
|
+
ErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
16
|
+
ErrorCode["INVALID_PARAMETERS"] = "INVALID_PARAMETERS";
|
|
17
|
+
ErrorCode["MISSING_REQUIRED_FIELD"] = "MISSING_REQUIRED_FIELD";
|
|
18
|
+
// Resource errors
|
|
19
|
+
ErrorCode["RESOURCE_NOT_FOUND"] = "RESOURCE_NOT_FOUND";
|
|
20
|
+
ErrorCode["RESOURCE_LOCKED"] = "RESOURCE_LOCKED";
|
|
21
|
+
ErrorCode["RESOURCE_UNAVAILABLE"] = "RESOURCE_UNAVAILABLE";
|
|
22
|
+
// Permission errors
|
|
23
|
+
ErrorCode["UNAUTHORIZED"] = "UNAUTHORIZED";
|
|
24
|
+
ErrorCode["FORBIDDEN"] = "FORBIDDEN";
|
|
25
|
+
// System errors
|
|
26
|
+
ErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
27
|
+
ErrorCode["CIRCUIT_BREAKER_OPEN"] = "CIRCUIT_BREAKER_OPEN";
|
|
28
|
+
ErrorCode["SERVICE_UNAVAILABLE"] = "SERVICE_UNAVAILABLE";
|
|
29
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
30
|
+
/**
|
|
31
|
+
* Base application error with metadata
|
|
32
|
+
*/
|
|
33
|
+
export class AppError extends Error {
|
|
34
|
+
metadata;
|
|
35
|
+
constructor(message, metadata = {}) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = this.constructor.name;
|
|
38
|
+
this.metadata = {
|
|
39
|
+
code: metadata.code || ErrorCode.INTERNAL_ERROR,
|
|
40
|
+
retriable: metadata.retriable || false,
|
|
41
|
+
timestamp: new Date(),
|
|
42
|
+
...metadata
|
|
43
|
+
};
|
|
44
|
+
Error.captureStackTrace(this, this.constructor);
|
|
45
|
+
}
|
|
46
|
+
toJSON() {
|
|
47
|
+
return {
|
|
48
|
+
name: this.name,
|
|
49
|
+
message: this.message,
|
|
50
|
+
...this.metadata,
|
|
51
|
+
stack: this.stack
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Connection-related errors
|
|
57
|
+
*/
|
|
58
|
+
export class ConnectionError extends AppError {
|
|
59
|
+
constructor(message, metadata = {}) {
|
|
60
|
+
super(message, {
|
|
61
|
+
code: ErrorCode.CONNECTION_FAILED,
|
|
62
|
+
retriable: true,
|
|
63
|
+
statusCode: 503,
|
|
64
|
+
...metadata
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* API-related errors
|
|
70
|
+
*/
|
|
71
|
+
export class ApiError extends AppError {
|
|
72
|
+
constructor(message, statusCode, metadata = {}) {
|
|
73
|
+
super(message, {
|
|
74
|
+
code: ErrorCode.API_ERROR,
|
|
75
|
+
statusCode,
|
|
76
|
+
retriable: statusCode >= 500 || statusCode === 429,
|
|
77
|
+
...metadata
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Validation errors
|
|
83
|
+
*/
|
|
84
|
+
export class ValidationError extends AppError {
|
|
85
|
+
constructor(message, metadata = {}) {
|
|
86
|
+
super(message, {
|
|
87
|
+
code: ErrorCode.VALIDATION_ERROR,
|
|
88
|
+
statusCode: 400,
|
|
89
|
+
retriable: false,
|
|
90
|
+
...metadata
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resource errors
|
|
96
|
+
*/
|
|
97
|
+
export class ResourceError extends AppError {
|
|
98
|
+
constructor(message, code, metadata = {}) {
|
|
99
|
+
super(message, {
|
|
100
|
+
code,
|
|
101
|
+
statusCode: code === ErrorCode.RESOURCE_NOT_FOUND ? 404 : 409,
|
|
102
|
+
retriable: code === ErrorCode.RESOURCE_LOCKED,
|
|
103
|
+
...metadata
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Circuit Breaker implementation for fault tolerance
|
|
109
|
+
*/
|
|
110
|
+
export var CircuitState;
|
|
111
|
+
(function (CircuitState) {
|
|
112
|
+
CircuitState["CLOSED"] = "CLOSED";
|
|
113
|
+
CircuitState["OPEN"] = "OPEN";
|
|
114
|
+
CircuitState["HALF_OPEN"] = "HALF_OPEN";
|
|
115
|
+
})(CircuitState || (CircuitState = {}));
|
|
116
|
+
export class CircuitBreaker {
|
|
117
|
+
state = CircuitState.CLOSED;
|
|
118
|
+
failures = 0;
|
|
119
|
+
successCount = 0;
|
|
120
|
+
lastFailureTime;
|
|
121
|
+
options;
|
|
122
|
+
constructor(options = {}) {
|
|
123
|
+
this.options = {
|
|
124
|
+
threshold: options.threshold || 5,
|
|
125
|
+
timeout: options.timeout || 60000, // 1 minute
|
|
126
|
+
resetTimeout: options.resetTimeout || 30000, // 30 seconds
|
|
127
|
+
onStateChange: options.onStateChange
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Execute function with circuit breaker protection
|
|
132
|
+
*/
|
|
133
|
+
async execute(fn) {
|
|
134
|
+
// Check circuit state
|
|
135
|
+
if (this.state === CircuitState.OPEN) {
|
|
136
|
+
if (this.shouldAttemptReset()) {
|
|
137
|
+
this.transitionTo(CircuitState.HALF_OPEN);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
throw new AppError('Circuit breaker is open', {
|
|
141
|
+
code: ErrorCode.CIRCUIT_BREAKER_OPEN,
|
|
142
|
+
retriable: true,
|
|
143
|
+
context: {
|
|
144
|
+
failures: this.failures,
|
|
145
|
+
lastFailure: this.lastFailureTime
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const result = await fn();
|
|
152
|
+
this.onSuccess();
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
this.onFailure();
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
onSuccess() {
|
|
161
|
+
this.failures = 0;
|
|
162
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
163
|
+
this.successCount++;
|
|
164
|
+
if (this.successCount >= 3) {
|
|
165
|
+
this.transitionTo(CircuitState.CLOSED);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
onFailure() {
|
|
170
|
+
this.failures++;
|
|
171
|
+
this.lastFailureTime = new Date();
|
|
172
|
+
this.successCount = 0;
|
|
173
|
+
if (this.failures >= this.options.threshold) {
|
|
174
|
+
this.transitionTo(CircuitState.OPEN);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
shouldAttemptReset() {
|
|
178
|
+
return (this.lastFailureTime !== undefined &&
|
|
179
|
+
Date.now() - this.lastFailureTime.getTime() >= this.options.resetTimeout);
|
|
180
|
+
}
|
|
181
|
+
transitionTo(newState) {
|
|
182
|
+
const oldState = this.state;
|
|
183
|
+
this.state = newState;
|
|
184
|
+
if (newState === CircuitState.CLOSED) {
|
|
185
|
+
this.failures = 0;
|
|
186
|
+
this.successCount = 0;
|
|
187
|
+
}
|
|
188
|
+
if (this.options.onStateChange && oldState !== newState) {
|
|
189
|
+
this.options.onStateChange(oldState, newState);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
getState() {
|
|
193
|
+
return this.state;
|
|
194
|
+
}
|
|
195
|
+
getMetrics() {
|
|
196
|
+
return {
|
|
197
|
+
state: this.state,
|
|
198
|
+
failures: this.failures,
|
|
199
|
+
successCount: this.successCount,
|
|
200
|
+
lastFailureTime: this.lastFailureTime
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Error recovery strategies
|
|
206
|
+
*/
|
|
207
|
+
export class ErrorRecovery {
|
|
208
|
+
static circuitBreakers = new Map();
|
|
209
|
+
/**
|
|
210
|
+
* Get or create circuit breaker for a service
|
|
211
|
+
*/
|
|
212
|
+
static getCircuitBreaker(service, options) {
|
|
213
|
+
if (!this.circuitBreakers.has(service)) {
|
|
214
|
+
this.circuitBreakers.set(service, new CircuitBreaker(options));
|
|
215
|
+
}
|
|
216
|
+
const breaker = this.circuitBreakers.get(service);
|
|
217
|
+
if (!breaker) {
|
|
218
|
+
throw new Error(`Circuit breaker for service ${service} could not be created`);
|
|
219
|
+
}
|
|
220
|
+
return breaker;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Wrap function with error recovery
|
|
224
|
+
*/
|
|
225
|
+
static async withRecovery(fn, options) {
|
|
226
|
+
const breaker = this.getCircuitBreaker(options.service);
|
|
227
|
+
try {
|
|
228
|
+
return await breaker.execute(fn);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (options.onError) {
|
|
232
|
+
options.onError(error);
|
|
233
|
+
}
|
|
234
|
+
// Try fallback if available
|
|
235
|
+
if (options.fallback) {
|
|
236
|
+
return await options.fallback();
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Check if error is retriable
|
|
243
|
+
*/
|
|
244
|
+
static isRetriable(error) {
|
|
245
|
+
if (error instanceof AppError) {
|
|
246
|
+
return error.metadata.retriable;
|
|
247
|
+
}
|
|
248
|
+
// Check for network errors
|
|
249
|
+
const message = error.message.toLowerCase();
|
|
250
|
+
return (message.includes('timeout') ||
|
|
251
|
+
message.includes('network') ||
|
|
252
|
+
message.includes('econnrefused') ||
|
|
253
|
+
message.includes('econnreset'));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
2
|
+
interface RetryConfig {
|
|
3
|
+
maxRetries: number;
|
|
4
|
+
initialDelay: number;
|
|
5
|
+
maxDelay: number;
|
|
6
|
+
backoffMultiplier: number;
|
|
7
|
+
retryableStatuses: number[];
|
|
8
|
+
retryableErrors: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Enhanced HTTP client factory with connection pooling and retry logic
|
|
12
|
+
*/
|
|
13
|
+
export declare function createHttpClient(baseURL: string): AxiosInstance;
|
|
14
|
+
/**
|
|
15
|
+
* Execute request with retry logic for resilience
|
|
16
|
+
*/
|
|
17
|
+
export declare function requestWithRetry<T = any>(client: AxiosInstance, config: AxiosRequestConfig, retryConfig?: Partial<RetryConfig>): Promise<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Batch multiple requests for efficiency
|
|
20
|
+
*/
|
|
21
|
+
export declare function batchRequests<T = any>(client: AxiosInstance, requests: AxiosRequestConfig[], options?: {
|
|
22
|
+
concurrency?: number;
|
|
23
|
+
throwOnError?: boolean;
|
|
24
|
+
}): Promise<(T | Error)[]>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import https from 'https';
|
|
4
|
+
// Connection pooling configuration for better performance
|
|
5
|
+
const httpAgent = new http.Agent({
|
|
6
|
+
keepAlive: true,
|
|
7
|
+
keepAliveMsecs: 30000,
|
|
8
|
+
maxSockets: 10,
|
|
9
|
+
maxFreeSockets: 5,
|
|
10
|
+
timeout: 30000
|
|
11
|
+
});
|
|
12
|
+
const httpsAgent = new https.Agent({
|
|
13
|
+
keepAlive: true,
|
|
14
|
+
keepAliveMsecs: 30000,
|
|
15
|
+
maxSockets: 10,
|
|
16
|
+
maxFreeSockets: 5,
|
|
17
|
+
timeout: 30000
|
|
18
|
+
});
|
|
19
|
+
const defaultRetryConfig = {
|
|
20
|
+
maxRetries: 3,
|
|
21
|
+
initialDelay: 1000,
|
|
22
|
+
maxDelay: 10000,
|
|
23
|
+
backoffMultiplier: 2,
|
|
24
|
+
retryableStatuses: [408, 429, 500, 502, 503, 504],
|
|
25
|
+
retryableErrors: ['ECONNABORTED', 'ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND']
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Calculate exponential backoff delay with jitter
|
|
29
|
+
*/
|
|
30
|
+
function calculateBackoff(attempt, config) {
|
|
31
|
+
const delay = Math.min(config.initialDelay * Math.pow(config.backoffMultiplier, attempt - 1), config.maxDelay);
|
|
32
|
+
// Add jitter to prevent thundering herd
|
|
33
|
+
return delay + Math.random() * 1000;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Enhanced HTTP client factory with connection pooling and retry logic
|
|
37
|
+
*/
|
|
38
|
+
export function createHttpClient(baseURL) {
|
|
39
|
+
const client = axios.create({
|
|
40
|
+
baseURL,
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'Accept': 'application/json'
|
|
44
|
+
},
|
|
45
|
+
timeout: 15000,
|
|
46
|
+
httpAgent,
|
|
47
|
+
httpsAgent,
|
|
48
|
+
// Ensure proper handling of request body transformation
|
|
49
|
+
transformRequest: [(data, headers) => {
|
|
50
|
+
// Remove Content-Length if it's set incorrectly
|
|
51
|
+
delete headers['Content-Length'];
|
|
52
|
+
delete headers['content-length'];
|
|
53
|
+
// Properly stringify JSON data
|
|
54
|
+
if (data && typeof data === 'object') {
|
|
55
|
+
const jsonStr = JSON.stringify(data);
|
|
56
|
+
// Let axios set Content-Length automatically
|
|
57
|
+
return jsonStr;
|
|
58
|
+
}
|
|
59
|
+
return data;
|
|
60
|
+
}],
|
|
61
|
+
// Optimize response handling
|
|
62
|
+
maxContentLength: 50 * 1024 * 1024, // 50MB
|
|
63
|
+
maxBodyLength: 50 * 1024 * 1024,
|
|
64
|
+
decompress: true
|
|
65
|
+
});
|
|
66
|
+
// Add request interceptor for timing
|
|
67
|
+
client.interceptors.request.use((config) => {
|
|
68
|
+
// Add metadata for performance tracking
|
|
69
|
+
config.metadata = { startTime: Date.now() };
|
|
70
|
+
return config;
|
|
71
|
+
}, (error) => {
|
|
72
|
+
return Promise.reject(error);
|
|
73
|
+
});
|
|
74
|
+
// Add response interceptor for timing and logging
|
|
75
|
+
client.interceptors.response.use((response) => {
|
|
76
|
+
const duration = Date.now() - (response.config.metadata?.startTime || 0);
|
|
77
|
+
if (duration > 5000) {
|
|
78
|
+
console.warn(`[HTTP] Slow request: ${response.config.url} took ${duration}ms`);
|
|
79
|
+
}
|
|
80
|
+
return response;
|
|
81
|
+
}, (error) => {
|
|
82
|
+
return Promise.reject(error);
|
|
83
|
+
});
|
|
84
|
+
return client;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Execute request with retry logic for resilience
|
|
88
|
+
*/
|
|
89
|
+
export async function requestWithRetry(client, config, retryConfig = {}) {
|
|
90
|
+
const retry = { ...defaultRetryConfig, ...retryConfig };
|
|
91
|
+
let lastError = null;
|
|
92
|
+
for (let attempt = 1; attempt <= retry.maxRetries; attempt++) {
|
|
93
|
+
try {
|
|
94
|
+
const response = await client.request(config);
|
|
95
|
+
// Check if we should retry based on status
|
|
96
|
+
if (retry.retryableStatuses.includes(response.status)) {
|
|
97
|
+
throw new Error(`Retryable status: ${response.status}`);
|
|
98
|
+
}
|
|
99
|
+
return response.data;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
lastError = error;
|
|
103
|
+
const axiosError = error;
|
|
104
|
+
// Check if error is retryable
|
|
105
|
+
const isRetryable = retry.retryableErrors.includes(axiosError.code || '') ||
|
|
106
|
+
(axiosError.response && retry.retryableStatuses.includes(axiosError.response.status));
|
|
107
|
+
if (!isRetryable || attempt === retry.maxRetries) {
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
// Calculate delay and wait
|
|
111
|
+
const delay = calculateBackoff(attempt, retry);
|
|
112
|
+
console.error(`[HTTP] Retry attempt ${attempt}/${retry.maxRetries} after ${Math.round(delay)}ms`);
|
|
113
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
throw lastError || new Error('Request failed after retries');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Batch multiple requests for efficiency
|
|
120
|
+
*/
|
|
121
|
+
export async function batchRequests(client, requests, options = {}) {
|
|
122
|
+
const { concurrency = 5, throwOnError = false } = options;
|
|
123
|
+
const results = [];
|
|
124
|
+
// Process requests in batches
|
|
125
|
+
for (let i = 0; i < requests.length; i += concurrency) {
|
|
126
|
+
const batch = requests.slice(i, i + concurrency);
|
|
127
|
+
const batchPromises = batch.map(req => client.request(req)
|
|
128
|
+
.then(res => res.data)
|
|
129
|
+
.catch(err => throwOnError ? Promise.reject(err) : err));
|
|
130
|
+
const batchResults = await Promise.all(batchPromises);
|
|
131
|
+
results.push(...batchResults);
|
|
132
|
+
}
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
export declare class Logger {
|
|
3
|
+
private scope;
|
|
4
|
+
private level;
|
|
5
|
+
constructor(scope: string, level?: LogLevel);
|
|
6
|
+
private shouldLog;
|
|
7
|
+
debug(...args: any[]): void;
|
|
8
|
+
info(...args: any[]): void;
|
|
9
|
+
warn(...args: any[]): void;
|
|
10
|
+
error(...args: any[]): void;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class Logger {
|
|
2
|
+
scope;
|
|
3
|
+
level;
|
|
4
|
+
constructor(scope, level = 'info') {
|
|
5
|
+
this.scope = scope;
|
|
6
|
+
const envLevel = (process.env.LOG_LEVEL || process.env.LOGLEVEL || level).toString().toLowerCase();
|
|
7
|
+
this.level = ['debug', 'info', 'warn', 'error'].includes(envLevel)
|
|
8
|
+
? envLevel
|
|
9
|
+
: 'info';
|
|
10
|
+
}
|
|
11
|
+
shouldLog(level) {
|
|
12
|
+
const order = ['debug', 'info', 'warn', 'error'];
|
|
13
|
+
return order.indexOf(level) >= order.indexOf(this.level);
|
|
14
|
+
}
|
|
15
|
+
debug(...args) {
|
|
16
|
+
if (this.shouldLog('debug'))
|
|
17
|
+
console.error(`[${this.scope}]`, ...args);
|
|
18
|
+
}
|
|
19
|
+
info(...args) {
|
|
20
|
+
if (this.shouldLog('info'))
|
|
21
|
+
console.error(`[${this.scope}]`, ...args);
|
|
22
|
+
}
|
|
23
|
+
warn(...args) {
|
|
24
|
+
if (this.shouldLog('warn'))
|
|
25
|
+
console.error(`[${this.scope}]`, ...args);
|
|
26
|
+
}
|
|
27
|
+
error(...args) {
|
|
28
|
+
if (this.shouldLog('error'))
|
|
29
|
+
console.error(`[${this.scope}]`, ...args);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type Vec3Array = [number, number, number];
|
|
2
|
+
export type Rot3Array = [number, number, number];
|
|
3
|
+
export interface Vec3Obj {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
z: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Rot3Obj {
|
|
9
|
+
pitch: number;
|
|
10
|
+
yaw: number;
|
|
11
|
+
roll: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function toVec3Object(input: any): Vec3Obj | null;
|
|
14
|
+
export declare function toVec3Array(input: any): Vec3Array | null;
|
|
15
|
+
export declare function toRotObject(input: any): Rot3Obj | null;
|
|
16
|
+
export declare function toRotArray(input: any): Rot3Array | null;
|
|
17
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function toVec3Object(input) {
|
|
2
|
+
try {
|
|
3
|
+
if (Array.isArray(input) && input.length === 3) {
|
|
4
|
+
const [x, y, z] = input;
|
|
5
|
+
if ([x, y, z].every(v => typeof v === 'number' && isFinite(v))) {
|
|
6
|
+
return { x, y, z };
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
if (input && typeof input === 'object') {
|
|
10
|
+
const x = Number(input.x ?? input.X);
|
|
11
|
+
const y = Number(input.y ?? input.Y);
|
|
12
|
+
const z = Number(input.z ?? input.Z);
|
|
13
|
+
if ([x, y, z].every(v => typeof v === 'number' && !isNaN(v) && isFinite(v))) {
|
|
14
|
+
return { x, y, z };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
export function toVec3Array(input) {
|
|
22
|
+
const obj = toVec3Object(input);
|
|
23
|
+
return obj ? [obj.x, obj.y, obj.z] : null;
|
|
24
|
+
}
|
|
25
|
+
export function toRotObject(input) {
|
|
26
|
+
try {
|
|
27
|
+
if (Array.isArray(input) && input.length === 3) {
|
|
28
|
+
const [pitch, yaw, roll] = input;
|
|
29
|
+
if ([pitch, yaw, roll].every(v => typeof v === 'number' && isFinite(v))) {
|
|
30
|
+
return { pitch, yaw, roll };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (input && typeof input === 'object') {
|
|
34
|
+
const pitch = Number(input.pitch ?? input.Pitch);
|
|
35
|
+
const yaw = Number(input.yaw ?? input.Yaw);
|
|
36
|
+
const roll = Number(input.roll ?? input.Roll);
|
|
37
|
+
if ([pitch, yaw, roll].every(v => typeof v === 'number' && !isNaN(v) && isFinite(v))) {
|
|
38
|
+
return { pitch, yaw, roll };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
export function toRotArray(input) {
|
|
46
|
+
const obj = toRotObject(input);
|
|
47
|
+
return obj ? [obj.pitch, obj.yaw, obj.roll] : null;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Validator for MCP Tool Outputs
|
|
3
|
+
* Validates tool responses against their defined output schemas
|
|
4
|
+
*/
|
|
5
|
+
export declare class ResponseValidator {
|
|
6
|
+
private ajv;
|
|
7
|
+
private validators;
|
|
8
|
+
constructor();
|
|
9
|
+
/**
|
|
10
|
+
* Register a tool's output schema for validation
|
|
11
|
+
*/
|
|
12
|
+
registerSchema(toolName: string, outputSchema: any): void;
|
|
13
|
+
/**
|
|
14
|
+
* Validate a tool's response against its schema
|
|
15
|
+
*/
|
|
16
|
+
validateResponse(toolName: string, response: any): {
|
|
17
|
+
valid: boolean;
|
|
18
|
+
errors?: string[];
|
|
19
|
+
structuredContent?: any;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Wrap a tool response with validation
|
|
23
|
+
*/
|
|
24
|
+
wrapResponse(toolName: string, response: any): any;
|
|
25
|
+
/**
|
|
26
|
+
* Get validation statistics
|
|
27
|
+
*/
|
|
28
|
+
getStats(): {
|
|
29
|
+
totalSchemas: number;
|
|
30
|
+
tools: string[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export declare const responseValidator: ResponseValidator;
|
|
34
|
+
//# sourceMappingURL=response-validator.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import { Logger } from './logger.js';
|
|
3
|
+
import { cleanObject } from './safe-json.js';
|
|
4
|
+
const log = new Logger('ResponseValidator');
|
|
5
|
+
/**
|
|
6
|
+
* Response Validator for MCP Tool Outputs
|
|
7
|
+
* Validates tool responses against their defined output schemas
|
|
8
|
+
*/
|
|
9
|
+
export class ResponseValidator {
|
|
10
|
+
ajv;
|
|
11
|
+
validators = new Map();
|
|
12
|
+
constructor() {
|
|
13
|
+
this.ajv = new Ajv({
|
|
14
|
+
allErrors: true,
|
|
15
|
+
verbose: true,
|
|
16
|
+
strict: false // Allow additional properties for flexibility
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Register a tool's output schema for validation
|
|
21
|
+
*/
|
|
22
|
+
registerSchema(toolName, outputSchema) {
|
|
23
|
+
if (!outputSchema) {
|
|
24
|
+
log.warn(`No output schema defined for tool: ${toolName}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const validator = this.ajv.compile(outputSchema);
|
|
29
|
+
this.validators.set(toolName, validator);
|
|
30
|
+
log.info(`Registered output schema for tool: ${toolName}`);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
log.error(`Failed to compile output schema for ${toolName}:`, error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validate a tool's response against its schema
|
|
38
|
+
*/
|
|
39
|
+
validateResponse(toolName, response) {
|
|
40
|
+
const validator = this.validators.get(toolName);
|
|
41
|
+
if (!validator) {
|
|
42
|
+
log.debug(`No validator found for tool: ${toolName}`);
|
|
43
|
+
return { valid: true }; // Pass through if no schema defined
|
|
44
|
+
}
|
|
45
|
+
// Extract structured content from response
|
|
46
|
+
let structuredContent = response;
|
|
47
|
+
// If response has MCP format with content array
|
|
48
|
+
if (response.content && Array.isArray(response.content)) {
|
|
49
|
+
// Try to extract structured data from text content
|
|
50
|
+
const textContent = response.content.find((c) => c.type === 'text');
|
|
51
|
+
if (textContent?.text) {
|
|
52
|
+
try {
|
|
53
|
+
// Check if text is JSON
|
|
54
|
+
structuredContent = JSON.parse(textContent.text);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Not JSON, use the full response
|
|
58
|
+
structuredContent = response;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const valid = validator(structuredContent);
|
|
63
|
+
if (!valid) {
|
|
64
|
+
const errors = validator.errors?.map((err) => `${err.instancePath || 'root'}: ${err.message}`);
|
|
65
|
+
log.warn(`Response validation failed for ${toolName}:`, errors);
|
|
66
|
+
return {
|
|
67
|
+
valid: false,
|
|
68
|
+
errors,
|
|
69
|
+
structuredContent
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
valid: true,
|
|
74
|
+
structuredContent
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Wrap a tool response with validation
|
|
79
|
+
*/
|
|
80
|
+
wrapResponse(toolName, response) {
|
|
81
|
+
// Ensure response is safe to serialize first
|
|
82
|
+
try {
|
|
83
|
+
// The response should already be cleaned, but double-check
|
|
84
|
+
if (response && typeof response === 'object') {
|
|
85
|
+
// Make sure we can serialize it
|
|
86
|
+
JSON.stringify(response);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
log.error(`Response for ${toolName} contains circular references, cleaning...`);
|
|
91
|
+
response = cleanObject(response);
|
|
92
|
+
}
|
|
93
|
+
const validation = this.validateResponse(toolName, response);
|
|
94
|
+
// Add validation metadata
|
|
95
|
+
if (!validation.valid) {
|
|
96
|
+
log.warn(`Tool ${toolName} response validation failed:`, validation.errors);
|
|
97
|
+
// Add warning to response but don't fail
|
|
98
|
+
if (response && typeof response === 'object') {
|
|
99
|
+
response._validation = {
|
|
100
|
+
valid: false,
|
|
101
|
+
errors: validation.errors
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Don't add structuredContent to the response - it's for internal validation only
|
|
106
|
+
// Adding it can cause circular references
|
|
107
|
+
return response;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get validation statistics
|
|
111
|
+
*/
|
|
112
|
+
getStats() {
|
|
113
|
+
return {
|
|
114
|
+
totalSchemas: this.validators.size,
|
|
115
|
+
tools: Array.from(this.validators.keys())
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Singleton instance
|
|
120
|
+
export const responseValidator = new ResponseValidator();
|
|
121
|
+
//# sourceMappingURL=response-validator.js.map
|