@mcpspec/core 1.0.1 → 1.0.3
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/index.d.ts +23 -2
- package/dist/index.js +82 -14
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -352,6 +352,7 @@ declare class ResultDiffer {
|
|
|
352
352
|
diff(baseline: TestRunResult, current: TestRunResult, baselineName?: string): RunDiff;
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
declare const DANGEROUS_TOOL_PATTERNS: RegExp;
|
|
355
356
|
declare class ScanConfig {
|
|
356
357
|
readonly mode: SecurityScanMode;
|
|
357
358
|
readonly rules: string[];
|
|
@@ -359,9 +360,12 @@ declare class ScanConfig {
|
|
|
359
360
|
readonly acknowledgeRisk: boolean;
|
|
360
361
|
readonly timeout: number;
|
|
361
362
|
readonly maxProbesPerTool: number;
|
|
363
|
+
readonly excludeTools: string[];
|
|
364
|
+
readonly dryRun: boolean;
|
|
362
365
|
constructor(config?: Partial<SecurityScanConfig>);
|
|
363
366
|
requiresConfirmation(): boolean;
|
|
364
367
|
meetsThreshold(severity: SeverityLevel): boolean;
|
|
368
|
+
isToolExcluded(toolName: string): boolean;
|
|
365
369
|
private getRulesForMode;
|
|
366
370
|
}
|
|
367
371
|
|
|
@@ -377,10 +381,27 @@ interface ScanProgress {
|
|
|
377
381
|
onRuleComplete?: (ruleId: string, findingCount: number) => void;
|
|
378
382
|
onFinding?: (finding: SecurityFinding) => void;
|
|
379
383
|
}
|
|
384
|
+
interface DryRunResult {
|
|
385
|
+
tools: Array<{
|
|
386
|
+
name: string;
|
|
387
|
+
included: boolean;
|
|
388
|
+
reason?: string;
|
|
389
|
+
}>;
|
|
390
|
+
rules: string[];
|
|
391
|
+
mode: string;
|
|
392
|
+
}
|
|
380
393
|
declare class SecurityScanner {
|
|
381
394
|
private readonly rules;
|
|
382
395
|
constructor();
|
|
383
396
|
registerRule(rule: SecurityRule): void;
|
|
397
|
+
/**
|
|
398
|
+
* Preview which tools will be scanned without actually running payloads.
|
|
399
|
+
*/
|
|
400
|
+
dryRun(client: MCPClientInterface, config: ScanConfig): Promise<DryRunResult>;
|
|
401
|
+
/**
|
|
402
|
+
* Filter tools based on config exclusions.
|
|
403
|
+
*/
|
|
404
|
+
filterTools(tools: ToolInfo[], config: ScanConfig): ToolInfo[];
|
|
384
405
|
scan(client: MCPClientInterface, config: ScanConfig, progress?: ScanProgress): Promise<SecurityScanResult>;
|
|
385
406
|
private buildSummary;
|
|
386
407
|
private registerBuiltinRules;
|
|
@@ -514,7 +535,7 @@ declare class MCPScoreCalculator {
|
|
|
514
535
|
private scoreDocumentation;
|
|
515
536
|
private scoreSchemaQuality;
|
|
516
537
|
private scoreErrorHandling;
|
|
517
|
-
private
|
|
538
|
+
private scoreResponsiveness;
|
|
518
539
|
private scoreSecurity;
|
|
519
540
|
}
|
|
520
541
|
|
|
@@ -523,4 +544,4 @@ declare class BadgeGenerator {
|
|
|
523
544
|
getColor(score: number): string;
|
|
524
545
|
}
|
|
525
546
|
|
|
526
|
-
export { AuthBypassRule, BadgeGenerator, BaselineStore, type BenchmarkProgress, BenchmarkRunner, ConnectionManager, ConsoleReporter, DocGenerator, type DocGeneratorOptions, ERROR_CODE_MAP, ERROR_TEMPLATES, type ErrorCode, HtmlDocGenerator, HtmlReporter, InformationDisclosureRule, InjectionRule, InputValidationRule, JsonReporter, JunitReporter, LoggingTransport, MCPClient, type MCPClientInterface, MCPScoreCalculator, MCPSpecError, MarkdownGenerator, NotImplementedError, type OnProtocolMessage, PathTraversalRule, type PayloadSet, type PlatformPayload, ProcessManagerImpl, ProcessRegistry, Profiler, RateLimiter, ResourceExhaustionRule, ResultDiffer, type RunDiff, ScanConfig, type ScanProgress, type ScoreProgress, SecretMasker, type SecurityRule, SecurityScanner, type ServerDocData, TapReporter, type TestDiff, TestExecutor, type TestRunReporter, TestRunner, TestScheduler, WaterfallGenerator, YAML_LIMITS, computeStats, formatError, getPayloadsForMode, getPlatformInfo, getPlatformPayloads, getSafePayloads, loadYamlSafely, queryJsonPath, registerCleanupHandlers, resolveVariables };
|
|
547
|
+
export { AuthBypassRule, BadgeGenerator, BaselineStore, type BenchmarkProgress, BenchmarkRunner, ConnectionManager, ConsoleReporter, DANGEROUS_TOOL_PATTERNS, DocGenerator, type DocGeneratorOptions, type DryRunResult, ERROR_CODE_MAP, ERROR_TEMPLATES, type ErrorCode, HtmlDocGenerator, HtmlReporter, InformationDisclosureRule, InjectionRule, InputValidationRule, JsonReporter, JunitReporter, LoggingTransport, MCPClient, type MCPClientInterface, MCPScoreCalculator, MCPSpecError, MarkdownGenerator, NotImplementedError, type OnProtocolMessage, PathTraversalRule, type PayloadSet, type PlatformPayload, ProcessManagerImpl, ProcessRegistry, Profiler, RateLimiter, ResourceExhaustionRule, ResultDiffer, type RunDiff, ScanConfig, type ScanProgress, type ScoreProgress, SecretMasker, type SecurityRule, SecurityScanner, type ServerDocData, TapReporter, type TestDiff, TestExecutor, type TestRunReporter, TestRunner, TestScheduler, WaterfallGenerator, YAML_LIMITS, computeStats, formatError, getPayloadsForMode, getPlatformInfo, getPlatformPayloads, getSafePayloads, loadYamlSafely, queryJsonPath, registerCleanupHandlers, resolveVariables };
|
package/dist/index.js
CHANGED
|
@@ -1230,7 +1230,7 @@ var TestExecutor = class {
|
|
|
1230
1230
|
assertions: assertionResults
|
|
1231
1231
|
};
|
|
1232
1232
|
}
|
|
1233
|
-
const response = this.buildResponse(result);
|
|
1233
|
+
const response = this.buildResponse(result, test.rawResponse);
|
|
1234
1234
|
if (test.assertions) {
|
|
1235
1235
|
for (const assertion of test.assertions) {
|
|
1236
1236
|
assertionResults.push(this.runAssertion(assertion, response, Date.now() - startTime));
|
|
@@ -1273,7 +1273,7 @@ var TestExecutor = class {
|
|
|
1273
1273
|
};
|
|
1274
1274
|
}
|
|
1275
1275
|
}
|
|
1276
|
-
buildResponse(result) {
|
|
1276
|
+
buildResponse(result, rawResponse) {
|
|
1277
1277
|
const contents = result.content;
|
|
1278
1278
|
if (!Array.isArray(contents) || contents.length === 0) {
|
|
1279
1279
|
return {};
|
|
@@ -1281,6 +1281,9 @@ var TestExecutor = class {
|
|
|
1281
1281
|
if (contents.length === 1) {
|
|
1282
1282
|
const item = contents[0];
|
|
1283
1283
|
if (item["type"] === "text" && typeof item["text"] === "string") {
|
|
1284
|
+
if (rawResponse) {
|
|
1285
|
+
return { content: item["text"], text: item["text"] };
|
|
1286
|
+
}
|
|
1284
1287
|
try {
|
|
1285
1288
|
return JSON.parse(item["text"]);
|
|
1286
1289
|
} catch {
|
|
@@ -1420,17 +1423,16 @@ var TestScheduler = class {
|
|
|
1420
1423
|
return skippedResults;
|
|
1421
1424
|
}
|
|
1422
1425
|
if (parallelism <= 1) {
|
|
1423
|
-
const
|
|
1426
|
+
const executor = new TestExecutor(initialVariables, rateLimiter);
|
|
1424
1427
|
const results2 = [];
|
|
1425
1428
|
for (const test of filteredTests) {
|
|
1426
1429
|
reporter?.onTestStart(test.name);
|
|
1427
|
-
const result = await
|
|
1430
|
+
const result = await executor.execute(test, client);
|
|
1428
1431
|
results2.push(result);
|
|
1429
1432
|
reporter?.onTestComplete(result);
|
|
1430
1433
|
}
|
|
1431
1434
|
return [...results2, ...skippedResults];
|
|
1432
1435
|
}
|
|
1433
|
-
const executor = new TestExecutor(initialVariables, rateLimiter);
|
|
1434
1436
|
let running = 0;
|
|
1435
1437
|
const results = new Array(filteredTests.length);
|
|
1436
1438
|
const waitQueue = [];
|
|
@@ -1455,6 +1457,7 @@ var TestScheduler = class {
|
|
|
1455
1457
|
return (async () => {
|
|
1456
1458
|
await acquire();
|
|
1457
1459
|
try {
|
|
1460
|
+
const executor = new TestExecutor(initialVariables, rateLimiter);
|
|
1458
1461
|
reporter?.onTestStart(test.name);
|
|
1459
1462
|
const result = await executor.execute(test, client);
|
|
1460
1463
|
results[i] = result;
|
|
@@ -2086,6 +2089,7 @@ var ACTIVE_RULES = [
|
|
|
2086
2089
|
var AGGRESSIVE_RULES = [...ACTIVE_RULES];
|
|
2087
2090
|
var DEFAULT_TIMEOUT = 1e4;
|
|
2088
2091
|
var DEFAULT_MAX_PROBES = 50;
|
|
2092
|
+
var DANGEROUS_TOOL_PATTERNS = /^(delete|drop|remove|destroy|kill|purge|truncate|wipe|reset|erase)[_-]|[_-](delete|drop|remove|destroy|kill|purge|truncate|wipe|reset|erase)$/i;
|
|
2089
2093
|
var ScanConfig = class {
|
|
2090
2094
|
mode;
|
|
2091
2095
|
rules;
|
|
@@ -2093,12 +2097,16 @@ var ScanConfig = class {
|
|
|
2093
2097
|
acknowledgeRisk;
|
|
2094
2098
|
timeout;
|
|
2095
2099
|
maxProbesPerTool;
|
|
2100
|
+
excludeTools;
|
|
2101
|
+
dryRun;
|
|
2096
2102
|
constructor(config = {}) {
|
|
2097
2103
|
this.mode = config.mode ?? "passive";
|
|
2098
2104
|
this.severityThreshold = config.severityThreshold ?? "info";
|
|
2099
2105
|
this.acknowledgeRisk = config.acknowledgeRisk ?? false;
|
|
2100
2106
|
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
2101
2107
|
this.maxProbesPerTool = config.maxProbesPerTool ?? DEFAULT_MAX_PROBES;
|
|
2108
|
+
this.excludeTools = config.excludeTools ?? [];
|
|
2109
|
+
this.dryRun = config.dryRun ?? false;
|
|
2102
2110
|
const allRulesForMode = this.getRulesForMode(this.mode);
|
|
2103
2111
|
if (config.rules && config.rules.length > 0) {
|
|
2104
2112
|
this.rules = config.rules.filter((r) => allRulesForMode.includes(r));
|
|
@@ -2114,6 +2122,11 @@ var ScanConfig = class {
|
|
|
2114
2122
|
const severityIdx = SEVERITY_ORDER.indexOf(severity);
|
|
2115
2123
|
return severityIdx >= thresholdIdx;
|
|
2116
2124
|
}
|
|
2125
|
+
isToolExcluded(toolName) {
|
|
2126
|
+
if (this.excludeTools.includes(toolName)) return true;
|
|
2127
|
+
if (this.mode !== "passive" && DANGEROUS_TOOL_PATTERNS.test(toolName)) return true;
|
|
2128
|
+
return false;
|
|
2129
|
+
}
|
|
2117
2130
|
getRulesForMode(mode) {
|
|
2118
2131
|
switch (mode) {
|
|
2119
2132
|
case "passive":
|
|
@@ -2690,10 +2703,47 @@ var SecurityScanner = class {
|
|
|
2690
2703
|
registerRule(rule) {
|
|
2691
2704
|
this.rules.set(rule.id, rule);
|
|
2692
2705
|
}
|
|
2706
|
+
/**
|
|
2707
|
+
* Preview which tools will be scanned without actually running payloads.
|
|
2708
|
+
*/
|
|
2709
|
+
async dryRun(client, config) {
|
|
2710
|
+
const allTools = await client.listTools();
|
|
2711
|
+
const toolResults = allTools.map((tool) => {
|
|
2712
|
+
if (config.excludeTools.includes(tool.name)) {
|
|
2713
|
+
return { name: tool.name, included: false, reason: "excluded by --exclude-tools" };
|
|
2714
|
+
}
|
|
2715
|
+
if (config.isToolExcluded(tool.name)) {
|
|
2716
|
+
return { name: tool.name, included: false, reason: "auto-skipped (destructive name)" };
|
|
2717
|
+
}
|
|
2718
|
+
return { name: tool.name, included: true };
|
|
2719
|
+
});
|
|
2720
|
+
return {
|
|
2721
|
+
tools: toolResults,
|
|
2722
|
+
rules: [...config.rules],
|
|
2723
|
+
mode: config.mode
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Filter tools based on config exclusions.
|
|
2728
|
+
*/
|
|
2729
|
+
filterTools(tools, config) {
|
|
2730
|
+
return tools.filter((tool) => !config.isToolExcluded(tool.name));
|
|
2731
|
+
}
|
|
2693
2732
|
async scan(client, config, progress) {
|
|
2694
2733
|
const startedAt = /* @__PURE__ */ new Date();
|
|
2695
2734
|
const findings = [];
|
|
2696
|
-
const
|
|
2735
|
+
const allTools = await client.listTools();
|
|
2736
|
+
const tools = this.filterTools(allTools, config);
|
|
2737
|
+
const skippedCount = allTools.length - tools.length;
|
|
2738
|
+
if (skippedCount > 0) {
|
|
2739
|
+
findings.push({
|
|
2740
|
+
id: randomUUID9(),
|
|
2741
|
+
rule: "safety-filter",
|
|
2742
|
+
severity: "info",
|
|
2743
|
+
title: `${skippedCount} tool(s) excluded from scan`,
|
|
2744
|
+
description: `${skippedCount} tool(s) were excluded from scanning due to safety filters or --exclude-tools.`
|
|
2745
|
+
});
|
|
2746
|
+
}
|
|
2697
2747
|
for (const ruleId of config.rules) {
|
|
2698
2748
|
const rule = this.rules.get(ruleId);
|
|
2699
2749
|
if (!rule) continue;
|
|
@@ -3121,14 +3171,14 @@ var MCPScoreCalculator = class {
|
|
|
3121
3171
|
progress?.onCategoryStart?.("errorHandling");
|
|
3122
3172
|
const errorHandling = await this.scoreErrorHandling(client, tools);
|
|
3123
3173
|
progress?.onCategoryComplete?.("errorHandling", errorHandling);
|
|
3124
|
-
progress?.onCategoryStart?.("
|
|
3125
|
-
const
|
|
3126
|
-
progress?.onCategoryComplete?.("
|
|
3174
|
+
progress?.onCategoryStart?.("responsiveness");
|
|
3175
|
+
const responsiveness = await this.scoreResponsiveness(client, tools);
|
|
3176
|
+
progress?.onCategoryComplete?.("responsiveness", responsiveness);
|
|
3127
3177
|
progress?.onCategoryStart?.("security");
|
|
3128
3178
|
const security = await this.scoreSecurity(client);
|
|
3129
3179
|
progress?.onCategoryComplete?.("security", security);
|
|
3130
3180
|
const overall = Math.round(
|
|
3131
|
-
documentation * 0.25 + schemaQuality * 0.25 + errorHandling * 0.2 +
|
|
3181
|
+
documentation * 0.25 + schemaQuality * 0.25 + errorHandling * 0.2 + responsiveness * 0.15 + security * 0.15
|
|
3132
3182
|
);
|
|
3133
3183
|
return {
|
|
3134
3184
|
overall,
|
|
@@ -3136,7 +3186,7 @@ var MCPScoreCalculator = class {
|
|
|
3136
3186
|
documentation,
|
|
3137
3187
|
schemaQuality,
|
|
3138
3188
|
errorHandling,
|
|
3139
|
-
|
|
3189
|
+
responsiveness,
|
|
3140
3190
|
security
|
|
3141
3191
|
}
|
|
3142
3192
|
};
|
|
@@ -3172,9 +3222,26 @@ var MCPScoreCalculator = class {
|
|
|
3172
3222
|
try {
|
|
3173
3223
|
const result = await client.callTool(tool.name, {});
|
|
3174
3224
|
if (result.isError) {
|
|
3175
|
-
|
|
3225
|
+
const content = result.content;
|
|
3226
|
+
let isStructured = false;
|
|
3227
|
+
if (Array.isArray(content) && content.length > 0) {
|
|
3228
|
+
isStructured = content.some((c) => {
|
|
3229
|
+
const item = c;
|
|
3230
|
+
const text = item["text"];
|
|
3231
|
+
if (typeof text !== "string") return false;
|
|
3232
|
+
try {
|
|
3233
|
+
const parsed = JSON.parse(text);
|
|
3234
|
+
return typeof parsed === "object" && parsed !== null && ("code" in parsed || "message" in parsed || "error" in parsed);
|
|
3235
|
+
} catch {
|
|
3236
|
+
return false;
|
|
3237
|
+
}
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
totalScore += isStructured ? 100 : 80;
|
|
3176
3241
|
} else {
|
|
3177
|
-
|
|
3242
|
+
const schema = tool.inputSchema;
|
|
3243
|
+
const hasRequired = schema && Array.isArray(schema.required) && schema.required.length > 0;
|
|
3244
|
+
totalScore += hasRequired ? 30 : 50;
|
|
3178
3245
|
}
|
|
3179
3246
|
} catch {
|
|
3180
3247
|
totalScore += 0;
|
|
@@ -3182,7 +3249,7 @@ var MCPScoreCalculator = class {
|
|
|
3182
3249
|
}
|
|
3183
3250
|
return Math.round(totalScore / testTools.length);
|
|
3184
3251
|
}
|
|
3185
|
-
async
|
|
3252
|
+
async scoreResponsiveness(client, tools) {
|
|
3186
3253
|
if (tools.length === 0) return 20;
|
|
3187
3254
|
const tool = tools[0];
|
|
3188
3255
|
const latencies = [];
|
|
@@ -3260,6 +3327,7 @@ export {
|
|
|
3260
3327
|
BenchmarkRunner,
|
|
3261
3328
|
ConnectionManager,
|
|
3262
3329
|
ConsoleReporter,
|
|
3330
|
+
DANGEROUS_TOOL_PATTERNS,
|
|
3263
3331
|
DocGenerator,
|
|
3264
3332
|
ERROR_CODE_MAP,
|
|
3265
3333
|
ERROR_TEMPLATES,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcpspec/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "https://github.com/
|
|
19
|
+
"url": "https://github.com/light-handle/mcpspec.git",
|
|
20
20
|
"directory": "packages/core"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"expr-eval": "^2.0.2",
|
|
32
32
|
"handlebars": "^4.7.8",
|
|
33
33
|
"zod": "^3.22.0",
|
|
34
|
-
"@mcpspec/shared": "1.0.
|
|
34
|
+
"@mcpspec/shared": "1.0.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/js-yaml": "^4.0.9",
|