@bryan-thompson/inspector-assessment-client 1.6.0 → 1.7.0
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/dist/assets/{OAuthCallback-ZcXdfhZQ.js → OAuthCallback-Xo9zS7pv.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-xt1SlIHS.js → OAuthDebugCallback-CaIey8K_.js} +1 -1
- package/dist/assets/{index-B3lTiDVe.js → index-nCPw6E-c.js} +4 -4
- package/dist/index.html +1 -1
- package/lib/lib/assessmentTypes.d.ts +670 -0
- package/lib/lib/assessmentTypes.d.ts.map +1 -0
- package/lib/lib/assessmentTypes.js +220 -0
- package/lib/lib/aupPatterns.d.ts +63 -0
- package/lib/lib/aupPatterns.d.ts.map +1 -0
- package/lib/lib/aupPatterns.js +344 -0
- package/lib/lib/prohibitedLibraries.d.ts +76 -0
- package/lib/lib/prohibitedLibraries.d.ts.map +1 -0
- package/lib/lib/prohibitedLibraries.js +364 -0
- package/lib/lib/securityPatterns.d.ts +64 -0
- package/lib/lib/securityPatterns.d.ts.map +1 -0
- package/lib/lib/securityPatterns.js +453 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts +88 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -0
- package/lib/services/assessment/AssessmentOrchestrator.js +418 -0
- package/lib/services/assessment/ResponseValidator.d.ts +69 -0
- package/lib/services/assessment/ResponseValidator.d.ts.map +1 -0
- package/lib/services/assessment/ResponseValidator.js +1038 -0
- package/lib/services/assessment/TestDataGenerator.d.ts +86 -0
- package/lib/services/assessment/TestDataGenerator.d.ts.map +1 -0
- package/lib/services/assessment/TestDataGenerator.js +669 -0
- package/lib/services/assessment/TestScenarioEngine.d.ts +91 -0
- package/lib/services/assessment/TestScenarioEngine.d.ts.map +1 -0
- package/lib/services/assessment/TestScenarioEngine.js +505 -0
- package/lib/services/assessment/ToolClassifier.d.ts +61 -0
- package/lib/services/assessment/ToolClassifier.d.ts.map +1 -0
- package/lib/services/assessment/ToolClassifier.js +349 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts +160 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts.map +1 -0
- package/lib/services/assessment/lib/claudeCodeBridge.js +357 -0
- package/lib/services/assessment/modules/AUPComplianceAssessor.d.ts +100 -0
- package/lib/services/assessment/modules/AUPComplianceAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/AUPComplianceAssessor.js +474 -0
- package/lib/services/assessment/modules/BaseAssessor.d.ts +71 -0
- package/lib/services/assessment/modules/BaseAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/BaseAssessor.js +171 -0
- package/lib/services/assessment/modules/DocumentationAssessor.d.ts +45 -0
- package/lib/services/assessment/modules/DocumentationAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/DocumentationAssessor.js +355 -0
- package/lib/services/assessment/modules/ErrorHandlingAssessor.d.ts +25 -0
- package/lib/services/assessment/modules/ErrorHandlingAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ErrorHandlingAssessor.js +564 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.d.ts +20 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.js +253 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts +70 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +508 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts +70 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +430 -0
- package/lib/services/assessment/modules/PortabilityAssessor.d.ts +43 -0
- package/lib/services/assessment/modules/PortabilityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/PortabilityAssessor.js +347 -0
- package/lib/services/assessment/modules/ProhibitedLibrariesAssessor.d.ts +41 -0
- package/lib/services/assessment/modules/ProhibitedLibrariesAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ProhibitedLibrariesAssessor.js +256 -0
- package/lib/services/assessment/modules/SecurityAssessor.d.ts +176 -0
- package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/SecurityAssessor.js +1333 -0
- package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts +96 -0
- package/lib/services/assessment/modules/ToolAnnotationAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ToolAnnotationAssessor.js +593 -0
- package/lib/services/assessment/modules/UsabilityAssessor.d.ts +21 -0
- package/lib/services/assessment/modules/UsabilityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/UsabilityAssessor.js +241 -0
- package/lib/services/assessment/modules/index.d.ts +33 -0
- package/lib/services/assessment/modules/index.d.ts.map +1 -0
- package/lib/services/assessment/modules/index.js +35 -0
- package/package.json +15 -3
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Spec Compliance Assessment Module
|
|
3
|
+
* Simple, focused MCP protocol validation for directory approval
|
|
4
|
+
*/
|
|
5
|
+
import Ajv from "ajv";
|
|
6
|
+
import { BaseAssessor } from "./BaseAssessor.js";
|
|
7
|
+
export class MCPSpecComplianceAssessor extends BaseAssessor {
|
|
8
|
+
ajv;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
super(config);
|
|
11
|
+
this.ajv = new Ajv({ allErrors: true });
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Assess MCP Specification Compliance - Hybrid Approach
|
|
15
|
+
* Separates protocol-verified checks from metadata-based hints
|
|
16
|
+
*/
|
|
17
|
+
async assess(context) {
|
|
18
|
+
const protocolVersion = this.extractProtocolVersion(context);
|
|
19
|
+
const tools = context.tools;
|
|
20
|
+
const callTool = context.callTool;
|
|
21
|
+
// SECTION 1: Protocol Checks (HIGH CONFIDENCE - actually tested)
|
|
22
|
+
const schemaCheck = this.checkSchemaCompliance(tools);
|
|
23
|
+
const jsonRpcCheck = await this.checkJsonRpcCompliance(callTool);
|
|
24
|
+
const errorCheck = await this.checkErrorResponses(tools, callTool);
|
|
25
|
+
const protocolChecks = {
|
|
26
|
+
jsonRpcCompliance: {
|
|
27
|
+
passed: jsonRpcCheck.passed,
|
|
28
|
+
confidence: "high",
|
|
29
|
+
evidence: "Verified via actual tool call",
|
|
30
|
+
rawResponse: jsonRpcCheck.rawResponse,
|
|
31
|
+
},
|
|
32
|
+
serverInfoValidity: {
|
|
33
|
+
passed: this.checkServerInfoValidity(context.serverInfo),
|
|
34
|
+
confidence: "high",
|
|
35
|
+
evidence: "Validated server info structure",
|
|
36
|
+
rawResponse: context.serverInfo,
|
|
37
|
+
},
|
|
38
|
+
schemaCompliance: {
|
|
39
|
+
passed: schemaCheck.passed,
|
|
40
|
+
confidence: schemaCheck.confidence,
|
|
41
|
+
warnings: schemaCheck.details ? [schemaCheck.details] : undefined,
|
|
42
|
+
rawResponse: tools.map((t) => ({
|
|
43
|
+
name: t.name,
|
|
44
|
+
inputSchema: t.inputSchema,
|
|
45
|
+
})),
|
|
46
|
+
},
|
|
47
|
+
errorResponseCompliance: {
|
|
48
|
+
passed: errorCheck.passed,
|
|
49
|
+
confidence: "high",
|
|
50
|
+
evidence: "Tested error handling with invalid parameters",
|
|
51
|
+
rawResponse: errorCheck.rawResponse,
|
|
52
|
+
},
|
|
53
|
+
structuredOutputSupport: {
|
|
54
|
+
passed: this.checkStructuredOutputSupport(tools),
|
|
55
|
+
confidence: "high",
|
|
56
|
+
evidence: `${tools.filter((t) => t.outputSchema).length}/${tools.length} tools have outputSchema`,
|
|
57
|
+
rawResponse: tools.map((t) => ({
|
|
58
|
+
name: t.name,
|
|
59
|
+
hasOutputSchema: !!t.outputSchema,
|
|
60
|
+
outputSchema: t.outputSchema,
|
|
61
|
+
})),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
// SECTION 2: Metadata Hints (LOW CONFIDENCE - not tested, just parsed)
|
|
65
|
+
const metadataHints = this.extractMetadataHints(context);
|
|
66
|
+
// Calculate score based ONLY on protocol checks (reliable)
|
|
67
|
+
const checksArray = Object.values(protocolChecks);
|
|
68
|
+
const passedCount = checksArray.filter((c) => c.passed).length;
|
|
69
|
+
const complianceScore = (passedCount / checksArray.length) * 100;
|
|
70
|
+
// Determine status based on protocol checks only
|
|
71
|
+
let status;
|
|
72
|
+
if (!protocolChecks.serverInfoValidity.passed) {
|
|
73
|
+
status = "FAIL";
|
|
74
|
+
}
|
|
75
|
+
else if (complianceScore >= 90) {
|
|
76
|
+
status = "PASS";
|
|
77
|
+
}
|
|
78
|
+
else if (complianceScore >= 70) {
|
|
79
|
+
status = "NEED_MORE_INFO";
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
status = "FAIL";
|
|
83
|
+
}
|
|
84
|
+
const explanation = this.generateExplanationHybrid(complianceScore, protocolChecks);
|
|
85
|
+
const recommendations = this.generateRecommendationsHybrid(protocolChecks, metadataHints);
|
|
86
|
+
// Legacy fields for backward compatibility
|
|
87
|
+
const transportCompliance = this.assessTransportCompliance(context);
|
|
88
|
+
const oauthImplementation = this.assessOAuthCompliance(context);
|
|
89
|
+
const annotationSupport = this.assessAnnotationSupport(context);
|
|
90
|
+
const streamingSupport = this.assessStreamingSupport(context);
|
|
91
|
+
return {
|
|
92
|
+
protocolVersion,
|
|
93
|
+
protocolChecks,
|
|
94
|
+
metadataHints,
|
|
95
|
+
status,
|
|
96
|
+
complianceScore,
|
|
97
|
+
explanation,
|
|
98
|
+
recommendations,
|
|
99
|
+
// Legacy fields (deprecated but maintained for backward compatibility)
|
|
100
|
+
transportCompliance,
|
|
101
|
+
oauthImplementation,
|
|
102
|
+
annotationSupport,
|
|
103
|
+
streamingSupport,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Extract protocol version from context
|
|
108
|
+
*/
|
|
109
|
+
extractProtocolVersion(context) {
|
|
110
|
+
// Try metadata.protocolVersion first
|
|
111
|
+
const metadata = context.serverInfo?.metadata;
|
|
112
|
+
const protocolVersion = metadata?.protocolVersion;
|
|
113
|
+
if (protocolVersion) {
|
|
114
|
+
this.log(`Using protocol version from metadata: ${protocolVersion}`);
|
|
115
|
+
return protocolVersion;
|
|
116
|
+
}
|
|
117
|
+
// Fall back to server version
|
|
118
|
+
if (context.serverInfo?.version) {
|
|
119
|
+
this.log(`Using server version as protocol version: ${context.serverInfo.version}`);
|
|
120
|
+
return context.serverInfo.version;
|
|
121
|
+
}
|
|
122
|
+
// Default if no version information available
|
|
123
|
+
this.log("No protocol version information available, using default");
|
|
124
|
+
return "2025-06-18"; // Current MCP spec version as fallback
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check JSON-RPC 2.0 compliance
|
|
128
|
+
*/
|
|
129
|
+
async checkJsonRpcCompliance(callTool) {
|
|
130
|
+
try {
|
|
131
|
+
// Test basic JSON-RPC structure by making a simple call
|
|
132
|
+
// If we can call any tool, JSON-RPC is working
|
|
133
|
+
const result = await callTool("list", {});
|
|
134
|
+
return { passed: result !== null, rawResponse: result };
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
// If call fails, that's actually expected for many tools
|
|
138
|
+
// The fact that we got a structured response means JSON-RPC works
|
|
139
|
+
return { passed: true, rawResponse: error };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if server info is valid and properly formatted
|
|
144
|
+
*/
|
|
145
|
+
checkServerInfoValidity(serverInfo) {
|
|
146
|
+
if (!serverInfo) {
|
|
147
|
+
// No server info is acceptable (optional)
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
// Check if name is properly set (should be a string, not null/undefined)
|
|
151
|
+
if (serverInfo.name !== undefined && serverInfo.name !== null) {
|
|
152
|
+
if (typeof serverInfo.name !== "string") {
|
|
153
|
+
this.log("Server info name is not a string");
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Check if metadata is properly formatted (should be an object if present)
|
|
158
|
+
if (serverInfo.metadata !== undefined && serverInfo.metadata !== null) {
|
|
159
|
+
if (typeof serverInfo.metadata !== "object") {
|
|
160
|
+
this.log("Server info metadata is not an object");
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check schema compliance for all tools
|
|
168
|
+
*/
|
|
169
|
+
checkSchemaCompliance(tools) {
|
|
170
|
+
try {
|
|
171
|
+
let hasErrors = false;
|
|
172
|
+
const errors = [];
|
|
173
|
+
for (const tool of tools) {
|
|
174
|
+
if (tool.inputSchema) {
|
|
175
|
+
// Validate that the schema is valid JSON Schema
|
|
176
|
+
const isValid = this.ajv.validateSchema(tool.inputSchema);
|
|
177
|
+
if (!isValid) {
|
|
178
|
+
hasErrors = true;
|
|
179
|
+
const errorMsg = `${tool.name}: ${JSON.stringify(this.ajv.errors)}`;
|
|
180
|
+
errors.push(errorMsg);
|
|
181
|
+
console.warn(`Invalid schema for tool ${tool.name}:`, this.ajv.errors);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// If errors found, mark as low confidence (likely Zod conversion issues)
|
|
186
|
+
return {
|
|
187
|
+
passed: !hasErrors,
|
|
188
|
+
confidence: hasErrors ? "low" : "high",
|
|
189
|
+
details: hasErrors ? errors.join("; ") : undefined,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
console.error("Schema compliance check failed:", error);
|
|
194
|
+
return {
|
|
195
|
+
passed: false,
|
|
196
|
+
confidence: "low",
|
|
197
|
+
details: String(error),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check error response compliance
|
|
203
|
+
*/
|
|
204
|
+
async checkErrorResponses(tools, callTool) {
|
|
205
|
+
try {
|
|
206
|
+
if (tools.length === 0)
|
|
207
|
+
return { passed: true, rawResponse: "No tools to test" };
|
|
208
|
+
// Test error handling with invalid parameters
|
|
209
|
+
const testTool = tools[0];
|
|
210
|
+
try {
|
|
211
|
+
const result = await callTool(testTool.name, { invalid_param: "test" });
|
|
212
|
+
return { passed: true, rawResponse: result }; // Server handled gracefully
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return { passed: true, rawResponse: error }; // Server provided error response
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
return { passed: false, rawResponse: error };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check if tools have structured output support (2025-06-18 feature)
|
|
224
|
+
*/
|
|
225
|
+
checkStructuredOutputSupport(tools) {
|
|
226
|
+
// Check if any tools define outputSchema
|
|
227
|
+
const toolsWithOutputSchema = tools.filter((tool) => tool.outputSchema).length;
|
|
228
|
+
const percentage = tools.length > 0 ? (toolsWithOutputSchema / tools.length) * 100 : 0;
|
|
229
|
+
// Log for debugging
|
|
230
|
+
this.log(`Structured output support: ${toolsWithOutputSchema}/${tools.length} tools (${percentage.toFixed(1)}%)`);
|
|
231
|
+
// Consider it supported if at least some tools use it
|
|
232
|
+
return toolsWithOutputSchema > 0;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Assess transport compliance (basic check)
|
|
236
|
+
*/
|
|
237
|
+
assessTransportCompliance(context) {
|
|
238
|
+
// If no server info at all, assume failure
|
|
239
|
+
if (!context.serverInfo) {
|
|
240
|
+
return {
|
|
241
|
+
supportsStreamableHTTP: false,
|
|
242
|
+
deprecatedSSE: false,
|
|
243
|
+
transportValidation: "failed",
|
|
244
|
+
supportsStdio: false,
|
|
245
|
+
supportsSSE: false,
|
|
246
|
+
confidence: "low",
|
|
247
|
+
detectionMethod: "manual-required",
|
|
248
|
+
requiresManualCheck: true,
|
|
249
|
+
manualVerificationSteps: [
|
|
250
|
+
"Test STDIO: Run `npm start`, send JSON-RPC initialize request",
|
|
251
|
+
"Test HTTP: Set HTTP_STREAMABLE_SERVER=true, run `npm start`, test /health endpoint",
|
|
252
|
+
"Check if framework handles transports internally",
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
// Check transport from metadata
|
|
257
|
+
const metadata = context.serverInfo?.metadata;
|
|
258
|
+
const transport = metadata?.transport;
|
|
259
|
+
const hasTransportMetadata = !!transport;
|
|
260
|
+
const supportsStreamableHTTP = transport === "streamable-http" ||
|
|
261
|
+
transport === "http" ||
|
|
262
|
+
(!transport && !!context.serverInfo);
|
|
263
|
+
const deprecatedSSE = transport === "sse";
|
|
264
|
+
// Determine validation status
|
|
265
|
+
let transportValidation = "passed";
|
|
266
|
+
// Check for MCP-Protocol-Version header requirement (2025-06-18)
|
|
267
|
+
// Note: We can't directly check headers through the SDK, but we can verify protocol version
|
|
268
|
+
const protocolVersion = this.extractProtocolVersion(context);
|
|
269
|
+
const isNewProtocol = protocolVersion >= "2025-06-18";
|
|
270
|
+
if (deprecatedSSE) {
|
|
271
|
+
transportValidation = "partial"; // SSE is deprecated
|
|
272
|
+
}
|
|
273
|
+
else if (transport &&
|
|
274
|
+
transport !== "streamable-http" &&
|
|
275
|
+
transport !== "http" &&
|
|
276
|
+
transport !== "stdio") {
|
|
277
|
+
transportValidation = "failed"; // Unknown transport
|
|
278
|
+
}
|
|
279
|
+
else if (isNewProtocol &&
|
|
280
|
+
(transport === "http" || transport === "streamable-http")) {
|
|
281
|
+
// For HTTP transport on 2025-06-18+, headers are required
|
|
282
|
+
// We assume compliance if using the new protocol version
|
|
283
|
+
this.log(`HTTP transport detected with protocol ${protocolVersion} - header compliance assumed`);
|
|
284
|
+
}
|
|
285
|
+
// Determine confidence based on detection method
|
|
286
|
+
const confidence = hasTransportMetadata ? "medium" : "low";
|
|
287
|
+
const requiresManualCheck = !hasTransportMetadata;
|
|
288
|
+
return {
|
|
289
|
+
supportsStreamableHTTP: supportsStreamableHTTP,
|
|
290
|
+
deprecatedSSE: deprecatedSSE,
|
|
291
|
+
transportValidation: transportValidation,
|
|
292
|
+
// Added missing properties that UI expects
|
|
293
|
+
supportsStdio: transport === "stdio" || !transport,
|
|
294
|
+
supportsSSE: deprecatedSSE,
|
|
295
|
+
// Detection metadata
|
|
296
|
+
confidence,
|
|
297
|
+
detectionMethod: hasTransportMetadata ? "automated" : "manual-required",
|
|
298
|
+
requiresManualCheck,
|
|
299
|
+
manualVerificationSteps: requiresManualCheck
|
|
300
|
+
? [
|
|
301
|
+
"Test STDIO: Run `npm start`, send JSON-RPC initialize request via stdin",
|
|
302
|
+
"Test HTTP: Set HTTP_STREAMABLE_SERVER=true, run `npm start`, curl http://localhost:3000/health",
|
|
303
|
+
"Check if framework (e.g., FastMCP, firecrawl-fastmcp) handles transports internally",
|
|
304
|
+
"Review server startup logs for transport initialization messages",
|
|
305
|
+
]
|
|
306
|
+
: undefined,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Assess annotation support
|
|
311
|
+
*/
|
|
312
|
+
assessAnnotationSupport(context) {
|
|
313
|
+
// Check if server metadata indicates annotation support
|
|
314
|
+
const metadata = context.serverInfo?.metadata;
|
|
315
|
+
const annotations = metadata?.annotations;
|
|
316
|
+
const supportsAnnotations = annotations?.supported || false;
|
|
317
|
+
const customAnnotations = annotations?.types || [];
|
|
318
|
+
return {
|
|
319
|
+
supportsReadOnlyHint: supportsAnnotations,
|
|
320
|
+
supportsDestructiveHint: supportsAnnotations,
|
|
321
|
+
supportsTitleAnnotation: supportsAnnotations,
|
|
322
|
+
customAnnotations: customAnnotations.length > 0 ? customAnnotations : undefined,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Assess streaming support
|
|
327
|
+
*/
|
|
328
|
+
assessStreamingSupport(context) {
|
|
329
|
+
// Check if server metadata indicates streaming support
|
|
330
|
+
const metadata = context.serverInfo?.metadata;
|
|
331
|
+
const streaming = metadata?.streaming;
|
|
332
|
+
const supportsStreaming = streaming?.supported || false;
|
|
333
|
+
const protocol = streaming?.protocol;
|
|
334
|
+
// Validate protocol is one of the allowed types
|
|
335
|
+
const validProtocols = ["http-streaming", "sse", "websocket"];
|
|
336
|
+
const streamingProtocol = protocol && validProtocols.includes(protocol)
|
|
337
|
+
? protocol
|
|
338
|
+
: supportsStreaming
|
|
339
|
+
? "http-streaming"
|
|
340
|
+
: undefined;
|
|
341
|
+
return {
|
|
342
|
+
supportsStreaming: supportsStreaming,
|
|
343
|
+
streamingProtocol,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Assess OAuth implementation (optional)
|
|
348
|
+
*/
|
|
349
|
+
assessOAuthCompliance(context) {
|
|
350
|
+
// Check if OAuth is configured
|
|
351
|
+
const metadata = context.serverInfo?.metadata;
|
|
352
|
+
const oauthConfig = metadata?.oauth;
|
|
353
|
+
if (!oauthConfig || !oauthConfig.enabled) {
|
|
354
|
+
// OAuth is optional for MCP servers
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
// Extract OAuth configuration with type assertions
|
|
358
|
+
const resourceIndicators = [];
|
|
359
|
+
if (oauthConfig.resourceIndicators) {
|
|
360
|
+
const indicators = oauthConfig.resourceIndicators;
|
|
361
|
+
resourceIndicators.push(...indicators);
|
|
362
|
+
}
|
|
363
|
+
if (oauthConfig.resourceServer) {
|
|
364
|
+
resourceIndicators.push(oauthConfig.resourceServer);
|
|
365
|
+
}
|
|
366
|
+
if (oauthConfig.authorizationEndpoint &&
|
|
367
|
+
!resourceIndicators.includes(oauthConfig.authorizationEndpoint)) {
|
|
368
|
+
resourceIndicators.push(oauthConfig.authorizationEndpoint);
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
implementsResourceServer: oauthConfig.enabled === true,
|
|
372
|
+
supportsRFC8707: oauthConfig.supportsRFC8707 || false,
|
|
373
|
+
resourceIndicators: resourceIndicators,
|
|
374
|
+
tokenValidation: oauthConfig.tokenValidation !== false, // Default to true if not specified
|
|
375
|
+
scopeEnforcement: oauthConfig.scopeEnforcement !== false, // Default to true if not specified
|
|
376
|
+
// Added missing properties that UI expects
|
|
377
|
+
supportsOAuth: oauthConfig.enabled === true,
|
|
378
|
+
supportsPKCE: oauthConfig.supportsPKCE || false,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Extract metadata hints from server context
|
|
383
|
+
* LOW CONFIDENCE - these are just parsed from metadata, not tested
|
|
384
|
+
*/
|
|
385
|
+
extractMetadataHints(context) {
|
|
386
|
+
const metadata = context.serverInfo?.metadata;
|
|
387
|
+
if (!metadata && !context.serverInfo) {
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
// Parse transport hints
|
|
391
|
+
const transport = metadata?.transport;
|
|
392
|
+
const transportHints = {
|
|
393
|
+
detectedTransport: transport,
|
|
394
|
+
supportsStdio: transport === "stdio" || !transport,
|
|
395
|
+
supportsHTTP: transport === "http" ||
|
|
396
|
+
transport === "streamable-http" ||
|
|
397
|
+
(!transport && !!context.serverInfo),
|
|
398
|
+
supportsSSE: transport === "sse",
|
|
399
|
+
detectionMethod: (transport ? "metadata" : "assumed"),
|
|
400
|
+
};
|
|
401
|
+
// Parse OAuth hints
|
|
402
|
+
const oauthConfig = metadata?.oauth;
|
|
403
|
+
const oauthHints = oauthConfig
|
|
404
|
+
? {
|
|
405
|
+
hasOAuthConfig: true,
|
|
406
|
+
supportsOAuth: oauthConfig.enabled === true,
|
|
407
|
+
supportsPKCE: oauthConfig.supportsPKCE || false,
|
|
408
|
+
resourceIndicators: oauthConfig.resourceIndicators
|
|
409
|
+
? oauthConfig.resourceIndicators
|
|
410
|
+
: undefined,
|
|
411
|
+
}
|
|
412
|
+
: undefined;
|
|
413
|
+
// Parse annotation hints
|
|
414
|
+
const annotations = metadata?.annotations;
|
|
415
|
+
const annotationHints = {
|
|
416
|
+
supportsReadOnlyHint: annotations?.supported || false,
|
|
417
|
+
supportsDestructiveHint: annotations?.supported || false,
|
|
418
|
+
supportsTitleAnnotation: annotations?.supported || false,
|
|
419
|
+
customAnnotations: annotations?.types
|
|
420
|
+
? annotations.types
|
|
421
|
+
: undefined,
|
|
422
|
+
};
|
|
423
|
+
// Parse streaming hints
|
|
424
|
+
const streaming = metadata?.streaming;
|
|
425
|
+
const streamingHints = {
|
|
426
|
+
supportsStreaming: streaming?.supported || false,
|
|
427
|
+
streamingProtocol: streaming?.protocol &&
|
|
428
|
+
["http-streaming", "sse", "websocket"].includes(streaming.protocol)
|
|
429
|
+
? streaming.protocol
|
|
430
|
+
: undefined,
|
|
431
|
+
};
|
|
432
|
+
return {
|
|
433
|
+
confidence: "low",
|
|
434
|
+
requiresManualVerification: true,
|
|
435
|
+
transportHints,
|
|
436
|
+
oauthHints,
|
|
437
|
+
annotationHints,
|
|
438
|
+
streamingHints,
|
|
439
|
+
manualVerificationSteps: [
|
|
440
|
+
"Test STDIO transport: Run `npm start`, send JSON-RPC initialize request via stdin",
|
|
441
|
+
"Test HTTP transport: Set HTTP_STREAMABLE_SERVER=true, run `npm start`, curl http://localhost:3000/health",
|
|
442
|
+
"Verify OAuth endpoints if configured",
|
|
443
|
+
"Check if framework (FastMCP, firecrawl-fastmcp) handles transports/features internally",
|
|
444
|
+
"Review server startup logs for transport initialization messages",
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Generate explanation based on protocol checks
|
|
450
|
+
*/
|
|
451
|
+
generateExplanationHybrid(complianceScore, checks) {
|
|
452
|
+
const failedChecks = [];
|
|
453
|
+
if (!checks.jsonRpcCompliance.passed)
|
|
454
|
+
failedChecks.push("JSON-RPC compliance");
|
|
455
|
+
if (!checks.serverInfoValidity.passed)
|
|
456
|
+
failedChecks.push("server info validity");
|
|
457
|
+
if (!checks.schemaCompliance.passed)
|
|
458
|
+
failedChecks.push("schema compliance");
|
|
459
|
+
if (!checks.errorResponseCompliance.passed)
|
|
460
|
+
failedChecks.push("error response compliance");
|
|
461
|
+
if (!checks.structuredOutputSupport.passed)
|
|
462
|
+
failedChecks.push("structured output support");
|
|
463
|
+
if (complianceScore >= 90) {
|
|
464
|
+
return "Excellent MCP protocol compliance. Server meets all critical requirements verified through protocol testing.";
|
|
465
|
+
}
|
|
466
|
+
else if (complianceScore >= 70) {
|
|
467
|
+
return `Good MCP compliance with minor issues in protocol testing: ${failedChecks.join(", ")}. Review recommended before directory submission.`;
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
return `Poor MCP compliance detected in protocol testing. Critical issues: ${failedChecks.join(", ")}. Must fix before directory approval.`;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Generate simplified recommendations based on protocol checks and metadata hints
|
|
475
|
+
*/
|
|
476
|
+
generateRecommendationsHybrid(checks, metadataHints) {
|
|
477
|
+
const recommendations = [];
|
|
478
|
+
// Protocol check failures (high confidence)
|
|
479
|
+
if (!checks.jsonRpcCompliance.passed) {
|
|
480
|
+
recommendations.push("⚠️ JSON-RPC 2.0 compliance failed: Ensure all requests/responses follow JSON-RPC 2.0 format with proper jsonrpc, id, method/result fields.");
|
|
481
|
+
}
|
|
482
|
+
if (!checks.serverInfoValidity.passed) {
|
|
483
|
+
recommendations.push("❌ Server info is malformed: Fix serverInfo structure to include valid name and metadata fields.");
|
|
484
|
+
}
|
|
485
|
+
if (!checks.schemaCompliance.passed) {
|
|
486
|
+
if (checks.schemaCompliance.confidence === "low") {
|
|
487
|
+
recommendations.push("⚠️ Schema validation warnings detected (LOW CONFIDENCE): May be false positives from Zod/TypeBox conversion. Test if tools execute successfully - if they do, this is likely a conversion artifact and not a real problem.");
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
recommendations.push("⚠️ JSON Schema validation errors: Review tool schemas and ensure they follow JSON Schema specification.");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (!checks.errorResponseCompliance.passed) {
|
|
494
|
+
recommendations.push("⚠️ Error response format issues: Return proper MCP error objects with error codes and messages following JSON-RPC 2.0 format.");
|
|
495
|
+
}
|
|
496
|
+
if (!checks.structuredOutputSupport.passed) {
|
|
497
|
+
recommendations.push("💡 Enhancement: Add outputSchema to tools for type-safe responses (optional MCP 2025-06-18 feature).");
|
|
498
|
+
}
|
|
499
|
+
// Metadata hints reminder
|
|
500
|
+
if (metadataHints?.requiresManualVerification) {
|
|
501
|
+
recommendations.push("ℹ️ Transport/OAuth/Streaming features require manual verification (metadata-based detection only).");
|
|
502
|
+
}
|
|
503
|
+
if (recommendations.length === 0) {
|
|
504
|
+
recommendations.push("✅ Excellent MCP compliance! All protocol checks passed. Server is ready for directory submission.");
|
|
505
|
+
}
|
|
506
|
+
return recommendations;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest Validation Assessor
|
|
3
|
+
* Validates MCPB manifest.json against spec requirements
|
|
4
|
+
*
|
|
5
|
+
* Checks:
|
|
6
|
+
* - manifest_version (must be 0.3)
|
|
7
|
+
* - Required fields: name, version, description, author
|
|
8
|
+
* - mcp_config structure
|
|
9
|
+
* - icon.png presence
|
|
10
|
+
*
|
|
11
|
+
* Reference: MCPB Specification
|
|
12
|
+
*/
|
|
13
|
+
import { BaseAssessor } from "./BaseAssessor.js";
|
|
14
|
+
import { AssessmentContext } from "../AssessmentOrchestrator.js";
|
|
15
|
+
import type { ManifestValidationAssessment } from "../../../lib/assessmentTypes.js";
|
|
16
|
+
export declare class ManifestValidationAssessor extends BaseAssessor {
|
|
17
|
+
/**
|
|
18
|
+
* Run manifest validation assessment
|
|
19
|
+
*/
|
|
20
|
+
assess(context: AssessmentContext): Promise<ManifestValidationAssessment>;
|
|
21
|
+
/**
|
|
22
|
+
* Create result when no manifest is available
|
|
23
|
+
*/
|
|
24
|
+
private createNoManifestResult;
|
|
25
|
+
/**
|
|
26
|
+
* Create result when manifest JSON is invalid
|
|
27
|
+
*/
|
|
28
|
+
private createInvalidJsonResult;
|
|
29
|
+
/**
|
|
30
|
+
* Validate manifest_version field
|
|
31
|
+
*/
|
|
32
|
+
private validateManifestVersion;
|
|
33
|
+
/**
|
|
34
|
+
* Validate required field presence
|
|
35
|
+
*/
|
|
36
|
+
private validateRequiredField;
|
|
37
|
+
/**
|
|
38
|
+
* Validate recommended field presence
|
|
39
|
+
*/
|
|
40
|
+
private validateRecommendedField;
|
|
41
|
+
/**
|
|
42
|
+
* Validate mcp_config structure
|
|
43
|
+
*/
|
|
44
|
+
private validateMcpConfig;
|
|
45
|
+
/**
|
|
46
|
+
* Validate icon presence
|
|
47
|
+
*/
|
|
48
|
+
private validateIcon;
|
|
49
|
+
/**
|
|
50
|
+
* Validate name format
|
|
51
|
+
*/
|
|
52
|
+
private validateNameFormat;
|
|
53
|
+
/**
|
|
54
|
+
* Validate version format (semver)
|
|
55
|
+
*/
|
|
56
|
+
private validateVersionFormat;
|
|
57
|
+
/**
|
|
58
|
+
* Determine overall status
|
|
59
|
+
*/
|
|
60
|
+
private determineManifestStatus;
|
|
61
|
+
/**
|
|
62
|
+
* Generate explanation
|
|
63
|
+
*/
|
|
64
|
+
private generateExplanation;
|
|
65
|
+
/**
|
|
66
|
+
* Generate recommendations
|
|
67
|
+
*/
|
|
68
|
+
private generateRecommendations;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=ManifestValidationAssessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ManifestValidationAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/ManifestValidationAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EACV,4BAA4B,EAI7B,MAAM,uBAAuB,CAAC;AAM/B,qBAAa,0BAA2B,SAAQ,YAAY;IAC1D;;OAEG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,4BAA4B,CAAC;IA0GxC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAyB9B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAmB/B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAgC/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAiC7B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAiChC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA+CzB;;OAEG;IACH,OAAO,CAAC,YAAY;IAqCpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA+B1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA8B7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAsB/B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;IACH,OAAO,CAAC,uBAAuB;CA+BhC"}
|