@jkumonpm/hash-crypto-mcp 2.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/README.md +226 -0
- package/dist/index.js +293 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# hash-crypto-mcp
|
|
2
|
+
|
|
3
|
+
Hash & Crypto MCP server. Hash, UUID, random strings, AES encrypt/decrypt.
|
|
4
|
+
|
|
5
|
+
No external dependencies. Pure Node.js crypto.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g hash-crypto-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Claude Desktop / Cursor / OpenCode
|
|
16
|
+
|
|
17
|
+
Add to your MCP config:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"hash-crypto": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "hash-crypto-mcp"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Tools
|
|
31
|
+
|
|
32
|
+
### `generate_hash` — Generate Hash
|
|
33
|
+
|
|
34
|
+
Generate a cryptographic hash of the input string.
|
|
35
|
+
|
|
36
|
+
**Input:**
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"algorithm": "sha256",
|
|
40
|
+
"input": "hello world"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Output:**
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"algorithm": "sha256",
|
|
48
|
+
"input": "hello world",
|
|
49
|
+
"hash": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Supported algorithms:** `md5`, `sha1`, `sha256`, `sha512`
|
|
54
|
+
|
|
55
|
+
### `generate_uuid` — Generate UUID
|
|
56
|
+
|
|
57
|
+
Generate UUIDs (v4 or v7).
|
|
58
|
+
|
|
59
|
+
**Input:**
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"version": "4",
|
|
63
|
+
"count": 3
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Output:**
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"version": 4,
|
|
71
|
+
"count": 3,
|
|
72
|
+
"uuids": [
|
|
73
|
+
"550e8400-e29b-41d4-a716-446655440000",
|
|
74
|
+
"6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
75
|
+
"6ba7b811-9dad-11d1-80b4-00c04fd430c8"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `generate_random_string` — Generate Random String
|
|
81
|
+
|
|
82
|
+
Generate random strings with specified length and character set.
|
|
83
|
+
|
|
84
|
+
**Input:**
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"length": 16,
|
|
88
|
+
"charset": "alphanumeric",
|
|
89
|
+
"count": 3
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Output:**
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"length": 16,
|
|
97
|
+
"charset": "alphanumeric",
|
|
98
|
+
"count": 3,
|
|
99
|
+
"strings": ["aB3xK9mP2qR7sT1v", "wY5zN8jL4hF6gD0c", "eU2iO7pA3bM9nQ1x"]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Built-in charsets:** `alphanumeric`, `lowercase`, `uppercase`, `digits`, `hex`, `symbols`, `printable`
|
|
104
|
+
|
|
105
|
+
You can also pass a custom charset string directly.
|
|
106
|
+
|
|
107
|
+
### `seed_random_string` — Seed-Based Random String
|
|
108
|
+
|
|
109
|
+
Generate a **deterministic** random string from a seed. Same seed + same params always produce the same output.
|
|
110
|
+
|
|
111
|
+
**Input:**
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"seed": "user-123-api-key",
|
|
115
|
+
"length": 32,
|
|
116
|
+
"charset": "hex"
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Output:**
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"seed": "user-123-api-key",
|
|
124
|
+
"length": 32,
|
|
125
|
+
"charset": "hex",
|
|
126
|
+
"string": "a3f8c2d1e9b7..."
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Use cases:**
|
|
131
|
+
- Generate reproducible API keys or tokens from a seed
|
|
132
|
+
- Create deterministic test data
|
|
133
|
+
- Password template generation
|
|
134
|
+
|
|
135
|
+
### `encrypt_string` — Encrypt/Decrypt String
|
|
136
|
+
|
|
137
|
+
AES-256-CBC encrypt or decrypt a string.
|
|
138
|
+
|
|
139
|
+
**Encrypt:**
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"action": "encrypt",
|
|
143
|
+
"text": "secret message",
|
|
144
|
+
"password": "mykey123"
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Output:**
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"action": "encrypt",
|
|
152
|
+
"encrypted": "a1b2c3d4e5f6...",
|
|
153
|
+
"salt": "abcdef1234567890...",
|
|
154
|
+
"iv": "1234567890abcdef..."
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Decrypt:**
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"action": "decrypt",
|
|
162
|
+
"encrypted": "a1b2c3d4e5f6...",
|
|
163
|
+
"password": "mykey123",
|
|
164
|
+
"salt": "abcdef1234567890...",
|
|
165
|
+
"iv": "1234567890abcdef..."
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Output:**
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"action": "decrypt",
|
|
173
|
+
"decrypted": "secret message"
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### `verify_hash` — Verify Hash
|
|
178
|
+
|
|
179
|
+
Verify that a string produces the expected hash. Returns match status and the actual hash.
|
|
180
|
+
|
|
181
|
+
**Input:**
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"algorithm": "sha256",
|
|
185
|
+
"input": "hello world",
|
|
186
|
+
"expected": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Output (match):**
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"algorithm": "sha256",
|
|
194
|
+
"input": "hello world",
|
|
195
|
+
"expected": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
|
|
196
|
+
"match": true,
|
|
197
|
+
"actual": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Output (mismatch):**
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"algorithm": "sha256",
|
|
205
|
+
"input": "hello world",
|
|
206
|
+
"expected": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
207
|
+
"match": false,
|
|
208
|
+
"actual": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Design
|
|
213
|
+
|
|
214
|
+
| Feature | Why |
|
|
215
|
+
|---------|-----|
|
|
216
|
+
| Zero runtime deps | Only `@modelcontextprotocol/sdk` and `zod` |
|
|
217
|
+
| Native crypto | Uses `node:crypto` — no external libraries |
|
|
218
|
+
| AES-256-CBC | Industry-standard encryption with random salt + IV |
|
|
219
|
+
| UUID v7 support | Timestamp-based UUIDs for sortable IDs |
|
|
220
|
+
| Dynamic charset | Pass any custom character string |
|
|
221
|
+
| Seed-based random | Deterministic output for reproducible results |
|
|
222
|
+
| Hash verification | One-step verify with match status |
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { createHash, randomUUID, randomInt, randomBytes, scryptSync, createCipheriv, createDecipheriv, createHmac } from 'node:crypto';
|
|
6
|
+
// ============================================================
|
|
7
|
+
// Constants
|
|
8
|
+
// ============================================================
|
|
9
|
+
const HASH_ALGORITHMS = ['md5', 'sha1', 'sha256', 'sha512'];
|
|
10
|
+
const UUID_VERSIONS = [4, 7];
|
|
11
|
+
const CHARSET_MAP = {
|
|
12
|
+
alphanumeric: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
|
|
13
|
+
lowercase: 'abcdefghijklmnopqrstuvwxyz',
|
|
14
|
+
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
15
|
+
digits: '0123456789',
|
|
16
|
+
hex: '0123456789abcdef',
|
|
17
|
+
symbols: '!@#$%^&*()_+-=[]{}|;:,.<>?',
|
|
18
|
+
printable: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 !@#$%^&*()_+-=[]{}|;:,.<>?',
|
|
19
|
+
};
|
|
20
|
+
const DEFAULT_CHARSET = 'alphanumeric';
|
|
21
|
+
// ============================================================
|
|
22
|
+
// Helpers
|
|
23
|
+
// ============================================================
|
|
24
|
+
function generateHash(algorithm, input) {
|
|
25
|
+
if (!HASH_ALGORITHMS.includes(algorithm)) {
|
|
26
|
+
throw new Error(`Unsupported algorithm: ${algorithm}. Supported: ${HASH_ALGORITHMS.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
return createHash(algorithm).update(input).digest('hex');
|
|
29
|
+
}
|
|
30
|
+
function generateUuid(version) {
|
|
31
|
+
if (!UUID_VERSIONS.includes(version)) {
|
|
32
|
+
throw new Error(`Unsupported UUID version: ${version}. Supported: ${UUID_VERSIONS.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
// Node.js randomUUID() generates v4 by default
|
|
35
|
+
// For v7, we'll use v4 as fallback since Node 18 doesn't support v7 natively
|
|
36
|
+
if (version === 7) {
|
|
37
|
+
// Generate UUID v7: timestamp-based (48-bit ms timestamp + random)
|
|
38
|
+
const timestamp = Date.now();
|
|
39
|
+
const tsHex = timestamp.toString(16).padStart(12, '0');
|
|
40
|
+
const random = randomBytes(10);
|
|
41
|
+
random[0] = (random[0] & 0x0f) | 0x70; // version 7
|
|
42
|
+
random[1] = (random[1] & 0x3f) | 0x80; // variant 10
|
|
43
|
+
const randHex = random.toString('hex');
|
|
44
|
+
return `${tsHex.slice(0, 8)}-${tsHex.slice(8, 12)}-${randHex.slice(0, 4)}-${randHex.slice(4, 8)}-${randHex.slice(8, 20)}`;
|
|
45
|
+
}
|
|
46
|
+
return randomUUID();
|
|
47
|
+
}
|
|
48
|
+
function generateRandomString(length, charset) {
|
|
49
|
+
const chars = CHARSET_MAP[charset] ?? charset;
|
|
50
|
+
if (chars.length === 0) {
|
|
51
|
+
throw new Error('Charset is empty');
|
|
52
|
+
}
|
|
53
|
+
return Array.from({ length }, () => chars[randomInt(0, chars.length)]).join('');
|
|
54
|
+
}
|
|
55
|
+
function deriveKey(password, salt) {
|
|
56
|
+
return scryptSync(password, salt, 32);
|
|
57
|
+
}
|
|
58
|
+
function encryptString(text, password) {
|
|
59
|
+
const salt = randomBytes(16);
|
|
60
|
+
const iv = randomBytes(16);
|
|
61
|
+
const key = deriveKey(password, salt);
|
|
62
|
+
const cipher = createCipheriv('aes-256-cbc', key, iv);
|
|
63
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
64
|
+
encrypted += cipher.final('hex');
|
|
65
|
+
return {
|
|
66
|
+
encrypted,
|
|
67
|
+
salt: salt.toString('hex'),
|
|
68
|
+
iv: iv.toString('hex'),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function decryptString(encrypted, password, salt, iv) {
|
|
72
|
+
const saltBuf = Buffer.from(salt, 'hex');
|
|
73
|
+
const ivBuf = Buffer.from(iv, 'hex');
|
|
74
|
+
const key = deriveKey(password, saltBuf);
|
|
75
|
+
const decipher = createDecipheriv('aes-256-cbc', key, ivBuf);
|
|
76
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
77
|
+
decrypted += decipher.final('utf8');
|
|
78
|
+
return decrypted;
|
|
79
|
+
}
|
|
80
|
+
function generateSeedRandomString(seed, length, charset) {
|
|
81
|
+
const chars = CHARSET_MAP[charset] ?? charset;
|
|
82
|
+
if (chars.length === 0) {
|
|
83
|
+
throw new Error('Charset is empty');
|
|
84
|
+
}
|
|
85
|
+
// Use HMAC-SHA256 to create a deterministic PRNG from the seed
|
|
86
|
+
const hash = createHmac('sha256', seed).update('seed').digest('hex');
|
|
87
|
+
let result = '';
|
|
88
|
+
let hashIndex = 0;
|
|
89
|
+
let currentHash = hash;
|
|
90
|
+
for (let i = 0; i < length; i++) {
|
|
91
|
+
if (hashIndex >= currentHash.length) {
|
|
92
|
+
currentHash = createHmac('sha256', seed).update(`seed:${Math.floor(i / 64)}`).digest('hex');
|
|
93
|
+
hashIndex = 0;
|
|
94
|
+
}
|
|
95
|
+
const byteVal = parseInt(currentHash.slice(hashIndex, hashIndex + 2), 16);
|
|
96
|
+
result += chars[byteVal % chars.length];
|
|
97
|
+
hashIndex += 2;
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
function verifyHash(algorithm, input, expected) {
|
|
102
|
+
const actual = generateHash(algorithm, input);
|
|
103
|
+
return {
|
|
104
|
+
match: actual === expected,
|
|
105
|
+
actual,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// ============================================================
|
|
109
|
+
// MCP Server
|
|
110
|
+
// ============================================================
|
|
111
|
+
const server = new McpServer({
|
|
112
|
+
name: 'hash-crypto',
|
|
113
|
+
version: '1.0.0',
|
|
114
|
+
});
|
|
115
|
+
// ===== Tool 1: generate_hash =====
|
|
116
|
+
server.registerTool('generate_hash', {
|
|
117
|
+
title: 'Generate Hash',
|
|
118
|
+
description: 'Generate a cryptographic hash of the input string.',
|
|
119
|
+
inputSchema: z.object({
|
|
120
|
+
algorithm: z.enum(HASH_ALGORITHMS).describe('Hash algorithm (md5, sha1, sha256, sha512)'),
|
|
121
|
+
input: z.string().describe('Input string to hash'),
|
|
122
|
+
}),
|
|
123
|
+
}, async ({ algorithm, input }) => {
|
|
124
|
+
try {
|
|
125
|
+
const hash = generateHash(algorithm, input);
|
|
126
|
+
return {
|
|
127
|
+
content: [{
|
|
128
|
+
type: 'text',
|
|
129
|
+
text: JSON.stringify({ algorithm, input, hash }, null, 2),
|
|
130
|
+
}],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
135
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: true, message }, null, 2) }], isError: true };
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
// ===== Tool 2: generate_uuid =====
|
|
139
|
+
server.registerTool('generate_uuid', {
|
|
140
|
+
title: 'Generate UUID',
|
|
141
|
+
description: 'Generate a UUID (v4 or v7).',
|
|
142
|
+
inputSchema: z.object({
|
|
143
|
+
version: z.enum(['4', '7']).default('4').describe('UUID version (4 = random, 7 = timestamp-based)'),
|
|
144
|
+
count: z.number().int().min(1).max(100).default(1).describe('Number of UUIDs to generate'),
|
|
145
|
+
}),
|
|
146
|
+
}, async ({ version, count }) => {
|
|
147
|
+
try {
|
|
148
|
+
const v = parseInt(version, 10);
|
|
149
|
+
const uuids = Array.from({ length: count }, () => generateUuid(v));
|
|
150
|
+
return {
|
|
151
|
+
content: [{
|
|
152
|
+
type: 'text',
|
|
153
|
+
text: JSON.stringify({ version: v, count: uuids.length, uuids }, null, 2),
|
|
154
|
+
}],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
159
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: true, message }, null, 2) }], isError: true };
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// ===== Tool 3: generate_random_string =====
|
|
163
|
+
server.registerTool('generate_random_string', {
|
|
164
|
+
title: 'Generate Random String',
|
|
165
|
+
description: 'Generate a random string with specified length and character set.',
|
|
166
|
+
inputSchema: z.object({
|
|
167
|
+
length: z.number().int().min(1).max(10000).describe('Length of the random string'),
|
|
168
|
+
charset: z.string().default(DEFAULT_CHARSET).describe('Character set name (alphanumeric, lowercase, uppercase, digits, hex, symbols, printable) or custom string'),
|
|
169
|
+
count: z.number().int().min(1).max(100).default(1).describe('Number of strings to generate'),
|
|
170
|
+
}),
|
|
171
|
+
}, async ({ length, charset, count }) => {
|
|
172
|
+
try {
|
|
173
|
+
const strings = Array.from({ length: count }, () => generateRandomString(length, charset));
|
|
174
|
+
return {
|
|
175
|
+
content: [{
|
|
176
|
+
type: 'text',
|
|
177
|
+
text: JSON.stringify({ length, charset, count: strings.length, strings }, null, 2),
|
|
178
|
+
}],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
183
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: true, message }, null, 2) }], isError: true };
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// ===== Tool 4: encrypt_string =====
|
|
187
|
+
server.registerTool('encrypt_string', {
|
|
188
|
+
title: 'Encrypt/Decrypt String',
|
|
189
|
+
description: 'AES-256-CBC encrypt or decrypt a string. For encryption, provide text + password. For decryption, provide encrypted + password + salt + iv.',
|
|
190
|
+
inputSchema: z.object({
|
|
191
|
+
action: z.enum(['encrypt', 'decrypt']).describe('Action to perform'),
|
|
192
|
+
text: z.string().optional().describe('Text to encrypt (required for encrypt action)'),
|
|
193
|
+
password: z.string().describe('Password for encryption/decryption'),
|
|
194
|
+
encrypted: z.string().optional().describe('Encrypted hex string (required for decrypt action)'),
|
|
195
|
+
salt: z.string().optional().describe('Salt hex string (required for decrypt action)'),
|
|
196
|
+
iv: z.string().optional().describe('IV hex string (required for decrypt action)'),
|
|
197
|
+
}),
|
|
198
|
+
}, async ({ action, text, password, encrypted, salt, iv }) => {
|
|
199
|
+
try {
|
|
200
|
+
if (action === 'encrypt') {
|
|
201
|
+
if (!text) {
|
|
202
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: true, message: 'text is required for encrypt action' }, null, 2) }], isError: true };
|
|
203
|
+
}
|
|
204
|
+
const result = encryptString(text, password);
|
|
205
|
+
return {
|
|
206
|
+
content: [{
|
|
207
|
+
type: 'text',
|
|
208
|
+
text: JSON.stringify({ action: 'encrypt', ...result }, null, 2),
|
|
209
|
+
}],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
if (!encrypted || !salt || !iv) {
|
|
214
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: true, message: 'encrypted, salt, and iv are required for decrypt action' }, null, 2) }], isError: true };
|
|
215
|
+
}
|
|
216
|
+
const decrypted = decryptString(encrypted, password, salt, iv);
|
|
217
|
+
return {
|
|
218
|
+
content: [{
|
|
219
|
+
type: 'text',
|
|
220
|
+
text: JSON.stringify({ action: 'decrypt', decrypted }, null, 2),
|
|
221
|
+
}],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
227
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: true, message }, null, 2) }], isError: true };
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
// ===== Tool 5: seed_random_string =====
|
|
231
|
+
server.registerTool('seed_random_string', {
|
|
232
|
+
title: 'Seed-Based Random String',
|
|
233
|
+
description: 'Generate a deterministic random string from a seed. Same seed + same params always produce the same output.',
|
|
234
|
+
inputSchema: z.object({
|
|
235
|
+
seed: z.string().describe('Seed string for deterministic output'),
|
|
236
|
+
length: z.number().int().min(1).max(10000).describe('Length of the random string'),
|
|
237
|
+
charset: z.string().default(DEFAULT_CHARSET).describe('Character set name (alphanumeric, lowercase, uppercase, digits, hex, symbols, printable) or custom string'),
|
|
238
|
+
}),
|
|
239
|
+
}, async ({ seed, length, charset }) => {
|
|
240
|
+
try {
|
|
241
|
+
const str = generateSeedRandomString(seed, length, charset);
|
|
242
|
+
return {
|
|
243
|
+
content: [{
|
|
244
|
+
type: 'text',
|
|
245
|
+
text: JSON.stringify({ seed, length, charset, string: str }, null, 2),
|
|
246
|
+
}],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
251
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: true, message }, null, 2) }], isError: true };
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
// ===== Tool 6: verify_hash =====
|
|
255
|
+
server.registerTool('verify_hash', {
|
|
256
|
+
title: 'Verify Hash',
|
|
257
|
+
description: 'Verify that a string produces the expected hash. Returns match status and the actual hash.',
|
|
258
|
+
inputSchema: z.object({
|
|
259
|
+
algorithm: z.enum(HASH_ALGORITHMS).describe('Hash algorithm (md5, sha1, sha256, sha512)'),
|
|
260
|
+
input: z.string().describe('Input string to hash'),
|
|
261
|
+
expected: z.string().describe('Expected hash value to compare against'),
|
|
262
|
+
}),
|
|
263
|
+
}, async ({ algorithm, input, expected }) => {
|
|
264
|
+
try {
|
|
265
|
+
const result = verifyHash(algorithm, input, expected);
|
|
266
|
+
return {
|
|
267
|
+
content: [{
|
|
268
|
+
type: 'text',
|
|
269
|
+
text: JSON.stringify({ algorithm, input, expected, match: result.match, actual: result.actual }, null, 2),
|
|
270
|
+
}],
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
275
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: true, message }, null, 2) }], isError: true };
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
// ============================================================
|
|
279
|
+
// Start
|
|
280
|
+
// ============================================================
|
|
281
|
+
async function main() {
|
|
282
|
+
const transport = new StdioServerTransport();
|
|
283
|
+
await server.connect(transport);
|
|
284
|
+
console.error('hash-crypto-mcp server running on stdio');
|
|
285
|
+
}
|
|
286
|
+
main().catch((error) => {
|
|
287
|
+
console.error('Fatal error:', error);
|
|
288
|
+
process.exit(1);
|
|
289
|
+
});
|
|
290
|
+
process.on('SIGINT', async () => {
|
|
291
|
+
await server.close();
|
|
292
|
+
process.exit(0);
|
|
293
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jkumonpm/hash-crypto-mcp",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Hash & Crypto MCP server — hash, UUID, random strings, AES encrypt/decrypt",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hash-crypto-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/index.js",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"dev": "node --loader ts-node/esm src/index.ts",
|
|
18
|
+
"client": "npm run build && node dist/client.js",
|
|
19
|
+
"test": "npm run build && node dist/test-runner.js",
|
|
20
|
+
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
21
|
+
"security:audit": "node scripts/security-audit.mjs",
|
|
22
|
+
"release": "node scripts/release.mjs",
|
|
23
|
+
"release:dry": "node scripts/release.mjs --dry",
|
|
24
|
+
"release:patch": "node scripts/release.mjs patch",
|
|
25
|
+
"release:minor": "node scripts/release.mjs minor",
|
|
26
|
+
"prepublishOnly": "npm run typecheck && npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"model-context-protocol",
|
|
31
|
+
"hash",
|
|
32
|
+
"crypto",
|
|
33
|
+
"uuid",
|
|
34
|
+
"random",
|
|
35
|
+
"encrypt",
|
|
36
|
+
"decrypt"
|
|
37
|
+
],
|
|
38
|
+
"author": "",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
45
|
+
"zod": "^3.25.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^22.0.0",
|
|
49
|
+
"typescript": "^5.7.2"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
53
|
+
"zod": "^3.24.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"@modelcontextprotocol/sdk": {
|
|
57
|
+
"optional": false
|
|
58
|
+
},
|
|
59
|
+
"zod": {
|
|
60
|
+
"optional": false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|