@slbdn/mcp-tester 1.0.2 → 1.2.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.
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Secret masking utility for preventing sensitive data from appearing in logs.
3
+ *
4
+ * Automatically detects and masks common secret patterns like API keys,
5
+ * tokens, passwords, and connection strings. Also masks values of known
6
+ * sensitive environment variable keys.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { maskSecrets, addSecretPattern } from '@slbdn/mcp-tester';
11
+ *
12
+ * // Built-in patterns are applied automatically
13
+ * maskSecrets('API key: sk-abc123def456ghi789');
14
+ * // → 'API key: sk-ab...789'
15
+ *
16
+ * // Add custom patterns
17
+ * addSecretPattern(/my-secret-key-[a-zA-Z0-9]+/g, 'MyOrg API key');
18
+ * ```
19
+ */
20
+ /**
21
+ * Environment variable keys that commonly contain secrets.
22
+ * Values for these keys will be masked in log output.
23
+ */
24
+ const SENSITIVE_ENV_KEYS = new Set([
25
+ 'API_KEY',
26
+ 'API_SECRET',
27
+ 'SECRET_KEY',
28
+ 'SECRET',
29
+ 'PRIVATE_KEY',
30
+ 'ACCESS_KEY',
31
+ 'ACCESS_TOKEN',
32
+ 'ACCESS_SECRET',
33
+ 'AUTH_TOKEN',
34
+ 'AUTHORIZATION',
35
+ 'BEARER_TOKEN',
36
+ 'TOKEN',
37
+ 'REFRESH_TOKEN',
38
+ 'SESSION_TOKEN',
39
+ 'SESSION_ID',
40
+ 'ID_TOKEN',
41
+ 'PASSWORD',
42
+ 'PASSWD',
43
+ 'DB_PASSWORD',
44
+ 'DATABASE_PASSWORD',
45
+ 'REDIS_PASSWORD',
46
+ 'MONGO_PASSWORD',
47
+ 'DATABASE_URL',
48
+ 'DB_URL',
49
+ 'MONGODB_URI',
50
+ 'POSTGRES_URL',
51
+ 'MYSQL_URL',
52
+ 'REDIS_URL',
53
+ 'AWS_ACCESS_KEY_ID',
54
+ 'AWS_SECRET_ACCESS_KEY',
55
+ 'AWS_SESSION_TOKEN',
56
+ 'AZURE_CLIENT_SECRET',
57
+ 'GCP_API_KEY',
58
+ 'GOOGLE_API_KEY',
59
+ 'OPENAI_API_KEY',
60
+ 'ANTHROPIC_API_KEY',
61
+ 'GITHUB_TOKEN',
62
+ 'GITLAB_TOKEN',
63
+ 'NPM_TOKEN',
64
+ 'NPM_AUTH_TOKEN',
65
+ 'SLACK_TOKEN',
66
+ 'SLACK_WEBHOOK',
67
+ 'SENTRY_DSN',
68
+ 'ENCRYPTION_KEY',
69
+ 'SIGNING_KEY',
70
+ 'JWT_SECRET',
71
+ 'CSRF_SECRET',
72
+ 'APP_SECRET',
73
+ ]);
74
+ const OPENAI_KEY_REGEX = /\bsk-(?:proj|ant)-[a-zA-Z0-9_-]{10,}/g;
75
+ const SK_KEY_REGEX = /\bsk-[a-zA-Z0-9]{12,}/g;
76
+ const AWS_KEY_REGEX = /\bAKIA[A-Z0-9]{16}\b/g;
77
+ const BEARER_REGEX = /\bBearer\s+[a-zA-Z0-9._-]{8,}/gi;
78
+ const JWT_REGEX = /\beyJ[a-zA-Z0-9_-]{10,}/g;
79
+ const HEX_REGEX = /\b[a-f0-9]{40,}\b/g;
80
+ const URL_CREDS_REGEX = /:[a-zA-Z0-9._-]{4,}@[a-zA-Z0-9.-]+/g;
81
+ const KV_REGEX = /\b(?:password|passwd|secret|token|key|auth|credential)s?\s*[:=]\s*["']?[a-zA-Z0-9._/@+-]{8,}["']?/gi;
82
+ /**
83
+ * Built-in secret patterns that are automatically masked.
84
+ */
85
+ const DEFAULT_SECRET_PATTERNS = [
86
+ { pattern: OPENAI_KEY_REGEX, name: 'OpenAI/Anthropic API key' },
87
+ { pattern: SK_KEY_REGEX, name: 'API key (sk-)' },
88
+ { pattern: AWS_KEY_REGEX, name: 'AWS access key' },
89
+ { pattern: BEARER_REGEX, name: 'Bearer token' },
90
+ { pattern: JWT_REGEX, name: 'JWT token' },
91
+ { pattern: HEX_REGEX, name: 'Hex token' },
92
+ { pattern: URL_CREDS_REGEX, name: 'URL credentials' },
93
+ { pattern: KV_REGEX, name: 'Key-value secret' },
94
+ ];
95
+ /** Active secret patterns (built-in + user-added). */
96
+ let activePatterns = [...DEFAULT_SECRET_PATTERNS];
97
+ /** Number of characters to keep visible at start/end when masking. */
98
+ const VISIBLE_CHARS = 3;
99
+ /**
100
+ * Mask a string value, keeping the first few and last few characters visible
101
+ * and replacing the rest with `...`.
102
+ *
103
+ * @param value - The secret value to mask
104
+ * @param visibleChars - How many characters to keep visible at start and end
105
+ * @returns Masked string like "sk-ab...789"
106
+ */
107
+ export function maskValue(value, visibleChars = VISIBLE_CHARS) {
108
+ if (visibleChars <= 0 || value.length <= visibleChars * 2 + 3) {
109
+ return '***';
110
+ }
111
+ const start = value.slice(0, visibleChars);
112
+ const end = value.slice(-visibleChars);
113
+ return `${start}...${end}`;
114
+ }
115
+ /**
116
+ * Mask secrets in a string by applying all active secret patterns
117
+ * and masking values of known sensitive environment variable keys.
118
+ *
119
+ * @param input - The value to mask (any type — converted to string)
120
+ * @returns String with secrets replaced by masked versions
121
+ */
122
+ export function maskSecrets(input) {
123
+ if (input === null || input === undefined) {
124
+ return String(input);
125
+ }
126
+ let result = String(input);
127
+ // 1. First mask values of known sensitive environment variable keys
128
+ // Matches: KEY=value, KEY="value", KEY: value, KEY: "value"
129
+ result = result.replace(/\b([A-Z_][A-Z0-9_]{2,})(\s*[:=]\s*)(["']?)([^\s"']{4,})\3/g, (_match, key, separator, quote, _value) => {
130
+ if (SENSITIVE_ENV_KEYS.has(key.toUpperCase())) {
131
+ return `${key}${separator}${quote}***${quote}`;
132
+ }
133
+ return _match;
134
+ });
135
+ // 2. Then apply built-in and user-added secret patterns
136
+ for (const { pattern } of activePatterns) {
137
+ // Create fresh regex to avoid lastIndex issues from global flag
138
+ const regex = new RegExp(pattern.source, pattern.flags);
139
+ result = result.replace(regex, (match) => maskValue(match));
140
+ }
141
+ return result;
142
+ }
143
+ /**
144
+ * Add a custom secret pattern to the masking rules.
145
+ *
146
+ * @param pattern - Regex pattern to match secrets
147
+ * @param name - Human-readable name for the pattern
148
+ */
149
+ export function addSecretPattern(pattern, name = 'custom') {
150
+ activePatterns.push({ pattern, name });
151
+ }
152
+ /**
153
+ * Reset all secret patterns to the built-in defaults.
154
+ * Removes any patterns added via `addSecretPattern()`.
155
+ */
156
+ export function resetSecretPatterns() {
157
+ activePatterns = [...DEFAULT_SECRET_PATTERNS];
158
+ }
159
+ /**
160
+ * Get the list of currently active secret patterns.
161
+ */
162
+ export function getSecretPatterns() {
163
+ return [...activePatterns];
164
+ }
165
+ /**
166
+ * Get the set of sensitive environment variable key names.
167
+ */
168
+ export function getSensitiveEnvKeys() {
169
+ return SENSITIVE_ENV_KEYS;
170
+ }
171
+ //# sourceMappingURL=masking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masking.js","sourceRoot":"","sources":["../../src/utils/masking.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slbdn/mcp-tester",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "Minimal MCP client implementation for CI/CD testing of MCP servers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,38 +9,63 @@
9
9
  "files": [
10
10
  "dist/client",
11
11
  "dist/utils",
12
+ "dist/cli",
12
13
  "dist/index.js",
13
14
  "dist/index.d.ts",
14
15
  "dist/index.d.ts.map",
15
16
  "dist/index.js.map",
17
+ "dist/assert.js",
18
+ "dist/assert.d.ts",
19
+ "dist/assert.d.ts.map",
20
+ "dist/assert.js.map",
21
+ "dist/matchers.js",
22
+ "dist/matchers.d.ts",
23
+ "dist/matchers.d.ts.map",
24
+ "dist/matchers.js.map",
25
+ "vitest.d.ts",
16
26
  "LICENSE",
17
27
  "README.md",
18
28
  "CHANGELOG.md"
19
29
  ],
20
30
  "scripts": {
21
31
  "build": "tsc",
32
+ "dev": "tsc --watch",
33
+ "docs": "typedoc",
22
34
  "test": "jest",
23
35
  "test:watch": "jest --watch",
24
36
  "test:coverage": "jest --coverage",
37
+ "benchmark": "npx tsx src/__tests__/benchmarks.ts 2>/dev/null",
25
38
  "lint": "eslint src --ext .ts",
26
39
  "format": "prettier --write \"src/**/*.ts\"",
27
40
  "format:check": "prettier --check \"src/**/*.ts\"",
28
41
  "audit": "npm audit",
29
42
  "audit:fix": "npm audit fix",
30
- "prepublishOnly": "npm run build",
43
+ "prepublishOnly": "npm run build && npm run docs",
31
44
  "release:patch": "npm version patch && npm run build",
32
45
  "release:minor": "npm version minor && npm run build",
33
46
  "release:major": "npm version major && npm run build",
34
47
  "release:prerelease": "npm version prerelease && npm run build",
35
48
  "release:commit": "git add package.json CHANGELOG.md && git commit -m \"chore: release $(node -p \"require('./package.json').version\")\"",
36
- "prepare": "husky"
49
+ "prepare": "husky || true"
50
+ },
51
+ "bin": {
52
+ "mcp-tester": "dist/cli/index.js"
37
53
  },
38
54
  "keywords": [
39
55
  "mcp",
40
56
  "model-context-protocol",
57
+ "mcp-tester",
58
+ "mcp-client",
41
59
  "testing",
60
+ "test",
42
61
  "jest",
43
- "ci-cd"
62
+ "vitest",
63
+ "assert",
64
+ "matchers",
65
+ "ci-cd",
66
+ "cli",
67
+ "typescript",
68
+ "esm"
44
69
  ],
45
70
  "author": "Slobodan Ivkovic",
46
71
  "license": "MIT",
@@ -53,9 +78,11 @@
53
78
  },
54
79
  "homepage": "https://github.com/islobodan/mcp-tester#readme",
55
80
  "dependencies": {
56
- "@modelcontextprotocol/sdk": "^1.29.0"
81
+ "@modelcontextprotocol/sdk": "^1.29.0",
82
+ "commander": "^14.0.3"
57
83
  },
58
84
  "devDependencies": {
85
+ "@modelcontextprotocol/server-everything": "^2026.1.26",
59
86
  "@types/jest": "^29.5.0",
60
87
  "@types/node": "^20.0.0",
61
88
  "@typescript-eslint/eslint-plugin": "^8.58.0",
@@ -66,6 +93,8 @@
66
93
  "lint-staged": "^16.4.0",
67
94
  "prettier": "^3.8.1",
68
95
  "ts-jest": "^29.4.9",
96
+ "typedoc": "^0.28.19",
97
+ "typedoc-plugin-markdown": "^4.11.0",
69
98
  "typescript": "^5.3.0"
70
99
  },
71
100
  "engines": {
package/vitest.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Vitest type declarations for MCP Tester custom matchers.
3
+ *
4
+ * Add this file to your tsconfig.json includes or reference it
5
+ * in your Vitest setup file to get type-safe matcher autocompletion.
6
+ *
7
+ * Usage (in vitest.d.ts or your setup file):
8
+ * /// <reference types="@slbdn/mcp-tester/vitest" />
9
+ *
10
+ * Or add to tsconfig.json:
11
+ * "include": ["node_modules/@slbdn/mcp-tester/vitest.d.ts"]
12
+ *
13
+ * Then in your Vitest setup:
14
+ * import { setupVitestMatchers } from '@slbdn/mcp-tester';
15
+ * beforeAll(() => setupVitestMatchers());
16
+ */
17
+
18
+ import 'vitest';
19
+
20
+ declare module 'vitest' {
21
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
22
+ interface Matchers<R = void, T = unknown> {
23
+ // Collection matchers
24
+ toHaveTool(toolName: string): R;
25
+ toHaveResource(uri: string): R;
26
+ toHavePrompt(promptName: string): R;
27
+ toHaveToolWithSchema(toolName: string): R;
28
+ toHaveToolCount(count: number): R;
29
+ toHaveResourceCount(count: number): R;
30
+ toHavePromptCount(count: number): R;
31
+ toHaveResourceByName(name: string): R;
32
+ toHavePromptWithArgs(promptName: string): R;
33
+
34
+ // Tool result matchers
35
+ toReturnText(expected?: string): R;
36
+ toReturnTextContaining(substring: string): R;
37
+ toReturnError(): R;
38
+ toReturnOk(): R;
39
+ toReturnJson(expected: unknown): R;
40
+ toReturnContentCount(count: number): R;
41
+ toReturnImage(): R;
42
+
43
+ // Resource result matchers
44
+ toReturnResourceText(expected?: string): R;
45
+ toReturnResourceTextContaining(substring: string): R;
46
+
47
+ // Prompt result matchers
48
+ toReturnPromptTextContaining(substring: string): R;
49
+ toReturnPromptMessageCount(count: number): R;
50
+ }
51
+ }