mcp-compress-router 1.0.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/LICENSE +7 -0
- package/README.md +51 -0
- package/build/catalog.js +54 -0
- package/build/cli/add-command.js +107 -0
- package/build/cli/config-io.js +213 -0
- package/build/cli/config-path-note.js +21 -0
- package/build/cli/get-command.js +49 -0
- package/build/cli/index.js +6 -0
- package/build/cli/list-command.js +79 -0
- package/build/cli/login-command.js +219 -0
- package/build/cli/logout-command.js +34 -0
- package/build/cli/remove-command.js +26 -0
- package/build/config.js +80 -0
- package/build/discovery.js +39 -0
- package/build/index.js +241 -0
- package/build/services/auth-status.js +113 -0
- package/build/services/catalog.js +54 -0
- package/build/services/config.js +273 -0
- package/build/services/discovery.js +104 -0
- package/build/services/index.js +6 -0
- package/build/services/invoker.js +72 -0
- package/build/services/oauth.js +145 -0
- package/build/text-format.js +24 -0
- package/build/tools/get-tool-schema.js +61 -0
- package/build/tools/index.js +2 -0
- package/build/tools/invoke-tool.js +92 -0
- package/build/types.js +1 -0
- package/build/utils/expand-env.js +47 -0
- package/build/utils/index.js +7 -0
- package/build/utils/logger.js +78 -0
- package/build/utils/open-browser.js +49 -0
- package/build/utils/parse-jsonc.js +24 -0
- package/build/utils/text-format.js +24 -0
- package/build/utils/types.js +1 -0
- package/build/utils/validate-arguments.js +156 -0
- package/package.json +71 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const TYPE_CHECKERS = {
|
|
2
|
+
string: (v) => typeof v === 'string',
|
|
3
|
+
number: (v) => typeof v === 'number' && !Number.isNaN(v),
|
|
4
|
+
integer: (v) => Number.isInteger(v),
|
|
5
|
+
boolean: (v) => typeof v === 'boolean',
|
|
6
|
+
object: (v) => typeof v === 'object' && v !== null && !Array.isArray(v),
|
|
7
|
+
array: (v) => Array.isArray(v),
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Coerces a string value to the target JSON Schema type.
|
|
11
|
+
*
|
|
12
|
+
* Handles common LLM mistakes where `"true"` / `"false"` is passed for
|
|
13
|
+
* a boolean field or `"42"` / `"3.14"` is passed for a number field.
|
|
14
|
+
* Returns the coerced value, or the original value if coercion is
|
|
15
|
+
* not applicable.
|
|
16
|
+
*
|
|
17
|
+
* @param value - The raw argument value (may be a string).
|
|
18
|
+
* @param targetType - The expected JSON Schema type.
|
|
19
|
+
* @returns The coerced value or the original.
|
|
20
|
+
*/
|
|
21
|
+
function coerceValue(value, targetType) {
|
|
22
|
+
if (typeof value !== 'string')
|
|
23
|
+
return value;
|
|
24
|
+
switch (targetType) {
|
|
25
|
+
case 'boolean': {
|
|
26
|
+
if (value === 'true')
|
|
27
|
+
return true;
|
|
28
|
+
if (value === 'false')
|
|
29
|
+
return false;
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
case 'number':
|
|
33
|
+
case 'integer': {
|
|
34
|
+
// Reject empty strings and whitespace-only strings
|
|
35
|
+
if (value.trim() === '')
|
|
36
|
+
return value;
|
|
37
|
+
const num = Number(value);
|
|
38
|
+
if (!Number.isFinite(num))
|
|
39
|
+
return value;
|
|
40
|
+
// Only coerce if the string is a complete numeric representation
|
|
41
|
+
// (e.g., "42" → 42, "3.14" → 3.14, but "42abc" stays a string)
|
|
42
|
+
if (String(num) === value.trim())
|
|
43
|
+
return num;
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
default:
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validates a single property value against its schema definition.
|
|
52
|
+
*
|
|
53
|
+
* Checks type correctness and coerces string values to the expected type
|
|
54
|
+
* when possible. Mutates `args` in-place on successful coercion and
|
|
55
|
+
* pushes error messages into `errors` on validation failures.
|
|
56
|
+
*
|
|
57
|
+
* @param key - The property name.
|
|
58
|
+
* @param value - The raw value for the property.
|
|
59
|
+
* @param propSchema - The JSON Schema definition for this property.
|
|
60
|
+
* @param required - List of required property names.
|
|
61
|
+
* @param args - The arguments object (mutated on coercion).
|
|
62
|
+
* @param errors - Accumulator for validation error messages.
|
|
63
|
+
*/
|
|
64
|
+
function checkProperty(key, value, propSchema, required, args, errors) {
|
|
65
|
+
// If the value is missing (undefined) but not required, skip type check
|
|
66
|
+
if (value === undefined && !required.includes(key)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const expectedType = propSchema.type;
|
|
70
|
+
if (!expectedType)
|
|
71
|
+
return;
|
|
72
|
+
// Coerce string values to the expected type (e.g., "true" → boolean,
|
|
73
|
+
// "42" → number) to accommodate common LLM encoding mistakes.
|
|
74
|
+
const coerced = coerceValue(value, expectedType);
|
|
75
|
+
if (coerced !== value) {
|
|
76
|
+
args[key] = coerced;
|
|
77
|
+
}
|
|
78
|
+
if (!TYPE_CHECKERS[expectedType]?.(coerced)) {
|
|
79
|
+
const actualType = Array.isArray(coerced) ? 'array' : typeof coerced;
|
|
80
|
+
errors.push(`Wrong type for "${key}": expected ${expectedType}, got ${actualType}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Builds a human-readable summary of the expected schema shape
|
|
85
|
+
* for inclusion in validation error messages.
|
|
86
|
+
*
|
|
87
|
+
* @param properties - The `properties` map from the input schema.
|
|
88
|
+
* @param required - List of required property names.
|
|
89
|
+
* @returns A formatted string describing each property and its type.
|
|
90
|
+
*/
|
|
91
|
+
function summarizeSchema(properties, required) {
|
|
92
|
+
const propDescriptions = Object.keys(properties)
|
|
93
|
+
.map((k) => {
|
|
94
|
+
const prop = properties[k];
|
|
95
|
+
const req = required.includes(k) ? ' (required)' : ' (optional)';
|
|
96
|
+
return ` - ${k}: ${prop.type ?? 'any'}${req}`;
|
|
97
|
+
})
|
|
98
|
+
.join('\n');
|
|
99
|
+
return `Expected shape:\n${propDescriptions}`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Validates a plain arguments object against a JSON Schema
|
|
103
|
+
* `inputSchema` from a downstream MCP tool.
|
|
104
|
+
*
|
|
105
|
+
* Handles `type: "object"` schemas with `properties` (type checks),
|
|
106
|
+
* `required` (presence checks), and extra/unknown property detection.
|
|
107
|
+
* Returns descriptive error messages suitable for LLM self-correction.
|
|
108
|
+
*
|
|
109
|
+
* Coerces string values to the expected type when possible
|
|
110
|
+
* (e.g., `"true"` → `true`, `"42"` → `42`). This accommodates common
|
|
111
|
+
* LLM encoding mistakes where string representations of booleans or
|
|
112
|
+
* numbers are passed instead of their JSON-native equivalents.
|
|
113
|
+
* Mutates the `args` object in-place when coercion is applied, so the
|
|
114
|
+
* caller receives properly-typed values for downstream forwarding.
|
|
115
|
+
*
|
|
116
|
+
* @param args - The arguments object to validate. May be mutated.
|
|
117
|
+
* @param inputSchema - The JSON Schema object from the tool descriptor.
|
|
118
|
+
* @returns A validation result.
|
|
119
|
+
*/
|
|
120
|
+
export function validateArguments(args, inputSchema) {
|
|
121
|
+
const errors = [];
|
|
122
|
+
// Top-level must be type "object" (MCP tool convention)
|
|
123
|
+
if (inputSchema.type !== 'object') {
|
|
124
|
+
// Schema is not an object schema — skip validation, assume valid
|
|
125
|
+
return { valid: true };
|
|
126
|
+
}
|
|
127
|
+
const required = (Array.isArray(inputSchema.required) ? inputSchema.required : []);
|
|
128
|
+
const properties = inputSchema.properties ?? {};
|
|
129
|
+
if (Object.keys(properties).length === 0 && required.length === 0) {
|
|
130
|
+
// No constraints — any args accepted
|
|
131
|
+
return { valid: true };
|
|
132
|
+
}
|
|
133
|
+
// Check required
|
|
134
|
+
for (const key of required) {
|
|
135
|
+
if (!(key in args)) {
|
|
136
|
+
errors.push(`Missing required argument: "${key}"`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Check types of provided values
|
|
140
|
+
for (const [key, value] of Object.entries(args)) {
|
|
141
|
+
const propSchema = properties[key];
|
|
142
|
+
if (!propSchema) {
|
|
143
|
+
if (Object.keys(properties).length > 0) {
|
|
144
|
+
// Provided a key not listed in properties
|
|
145
|
+
const known = Object.keys(properties).join(', ');
|
|
146
|
+
errors.push(`Unknown argument: "${key}". Expected arguments: ${known}`);
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
checkProperty(key, value, propSchema, required, args, errors);
|
|
151
|
+
}
|
|
152
|
+
if (errors.length > 0) {
|
|
153
|
+
return { valid: false, errors: [...errors, summarizeSchema(properties, required)] };
|
|
154
|
+
}
|
|
155
|
+
return { valid: true };
|
|
156
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-compress-router",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Compress all connected MCP servers into a single router MCP to save tokens",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "build/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mcp-compress-router": "./build/index.js"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=22"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/ameshkov/mcp-compress-router.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/ameshkov/mcp-compress-router#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/ameshkov/mcp-compress-router/issues"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
|
27
|
+
"typecheck": "tsc && tsc --project tsconfig.test.json",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"clean": "rm -rf node_modules build",
|
|
31
|
+
"format:check": "prettier --check --no-error-on-unmatched-pattern --cache . && markdownlint-cli2 .",
|
|
32
|
+
"format:fix": "prettier --write --no-error-on-unmatched-pattern --cache . && markdownlint-cli2 --fix .",
|
|
33
|
+
"lint": "eslint src && pnpm knip",
|
|
34
|
+
"lint:fix": "eslint src --fix",
|
|
35
|
+
"knip": "knip --production",
|
|
36
|
+
"check": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run test",
|
|
37
|
+
"prepare": "husky || true"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"build"
|
|
41
|
+
],
|
|
42
|
+
"keywords": [
|
|
43
|
+
"mcp",
|
|
44
|
+
"copilot",
|
|
45
|
+
"codex",
|
|
46
|
+
"claude",
|
|
47
|
+
"model-context-protocol",
|
|
48
|
+
"router"
|
|
49
|
+
],
|
|
50
|
+
"author": "Andrey Meshkov <am@adguard.com>",
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@eslint/js": "9.16.0",
|
|
53
|
+
"@types/node": "22.19.2",
|
|
54
|
+
"eslint": "9.16.0",
|
|
55
|
+
"husky": "9.1.7",
|
|
56
|
+
"knip": "6.6.2",
|
|
57
|
+
"markdownlint-cli2": "0.22.0",
|
|
58
|
+
"prettier": "3.7.4",
|
|
59
|
+
"tsx": "4.20.3",
|
|
60
|
+
"typescript": "5.9.3",
|
|
61
|
+
"typescript-eslint": "8.57.0",
|
|
62
|
+
"vitest": "3.2.4"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@modelcontextprotocol/sdk": "1.24.3",
|
|
66
|
+
"commander": "14.0.2",
|
|
67
|
+
"dotenv": "17.4.2",
|
|
68
|
+
"jsonc-parser": "3.3.1",
|
|
69
|
+
"zod": "3.25.76"
|
|
70
|
+
}
|
|
71
|
+
}
|