@coo-quack/calc-mcp 1.4.0 → 1.5.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/CHANGELOG.md +19 -0
- package/README.md +40 -55
- package/coo-quack-avatar.jpg +0 -0
- package/dist/index.js +63 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.5.0 (2026-02-11)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- **Password generation** — fine-grained options: `uppercase`, `numbers`, `symbols` (on/off), `readable` mode (excludes ambiguous chars like l/1/I/O/0/o), `excludeChars` for custom exclusions
|
|
8
|
+
- **Shuffle** — Fisher-Yates algorithm with `crypto.getRandomValues` for unbiased list shuffling
|
|
9
|
+
|
|
10
|
+
### Documentation
|
|
11
|
+
|
|
12
|
+
- README title: `@coo-quack/calc-mcp` → `Calc MCP`
|
|
13
|
+
- Added "Why?" section with AI-alone vs calc-mcp comparison
|
|
14
|
+
- Quick Start moved to top
|
|
15
|
+
- Install guides consolidated (Claude Desktop/Cursor/Windsurf share same JSON format)
|
|
16
|
+
|
|
17
|
+
### Tests
|
|
18
|
+
|
|
19
|
+
- Random tool tests: 12 → 27 (+15)
|
|
20
|
+
- Total: 194 tests, 280 assertions
|
|
21
|
+
|
|
3
22
|
## v1.4.0 (2026-02-11)
|
|
4
23
|
|
|
5
24
|
### Features
|
package/README.md
CHANGED
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Calc MCP
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@coo-quack/calc-mcp)
|
|
4
4
|
[](https://github.com/coo-quack/calc-mcp/actions/workflows/ci.yml)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**21 tools for things AI is bad at** — deterministic math, cryptographic randomness, accurate date arithmetic, encoding, hashing, and more.
|
|
8
|
+
|
|
9
|
+
LLMs hallucinate calculations, can't generate true random numbers, and struggle with timezones. This MCP server fixes that.
|
|
10
|
+
|
|
11
|
+
### Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Claude Code
|
|
15
|
+
claude mcp add -s user calc-mcp -- npx -y @coo-quack/calc-mcp
|
|
16
|
+
|
|
17
|
+
# Or just run it
|
|
18
|
+
npx -y @coo-quack/calc-mcp
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
> Works with Claude Desktop, VS Code Copilot, Cursor, Windsurf — [setup guides below](#install).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Why?
|
|
26
|
+
|
|
27
|
+
| AI alone | With calc-mcp |
|
|
28
|
+
|----------|---------------|
|
|
29
|
+
| "10 + 34 × 341 ÷ 23 = 507.8" ❌ | `514.087` ✅ (math) |
|
|
30
|
+
| "Here's a UUID: 550e8400-..." 🤷 fake | Cryptographically random UUID v4/v7 ✅ (random) |
|
|
31
|
+
| "100 days from now is..." 🤔 guess | `2026-05-22` ✅ (date) |
|
|
32
|
+
| "SHA-256 of password123 is..." 💀 hallucinated | `ef92b778bafe...` ✅ (hash) |
|
|
8
33
|
|
|
9
34
|
## Examples
|
|
10
35
|
|
|
@@ -43,7 +68,8 @@ Ask in natural language — the AI picks the right tool automatically.
|
|
|
43
68
|
| You ask | You get | Tool |
|
|
44
69
|
|---------|---------|------|
|
|
45
70
|
| Generate a UUID v7 | `019c4b54-aad2-7e52-...` | random |
|
|
46
|
-
| Generate a 20-char password | `
|
|
71
|
+
| Generate a readable 20-char password | `hT9jZDojX6sHRJt8vaKS` | random |
|
|
72
|
+
| Shuffle ["Alice", "Bob", "Charlie"] | `["Charlie", "Alice", "Bob"]` | random |
|
|
47
73
|
|
|
48
74
|
### Conversion
|
|
49
75
|
|
|
@@ -71,14 +97,14 @@ Ask in natural language — the AI picks the right tool automatically.
|
|
|
71
97
|
| Decode this JWT: eyJhbGci... | `{ alg: "HS256", name: "John Doe" }` | jwt_decode |
|
|
72
98
|
| Parse https://example.com/search?q=hello | `host: example.com, q: "hello"` | url_parse |
|
|
73
99
|
|
|
74
|
-
## All Tools
|
|
100
|
+
## All 21 Tools
|
|
75
101
|
|
|
76
102
|
| Tool | Description |
|
|
77
103
|
|------|-------------|
|
|
78
104
|
| `math` | Evaluate expressions, statistics |
|
|
79
105
|
| `count` | Characters (grapheme-aware), words, lines, bytes |
|
|
80
106
|
| `datetime` | Current time, timezone conversion, UNIX timestamps |
|
|
81
|
-
| `random` | UUID v4/v7, ULID, passwords,
|
|
107
|
+
| `random` | UUID v4/v7, ULID, passwords (readable, custom charset), shuffle |
|
|
82
108
|
| `hash` | MD5, SHA-1, SHA-256, SHA-512, CRC32 |
|
|
83
109
|
| `base64` | Encode / decode |
|
|
84
110
|
| `encode` | URL, HTML entity, Unicode escape |
|
|
@@ -91,7 +117,7 @@ Ask in natural language — the AI picks the right tool automatically.
|
|
|
91
117
|
| `luhn` | Validate / generate check digits |
|
|
92
118
|
| `ip` | IPv4/IPv6 info, CIDR range |
|
|
93
119
|
| `color` | HEX ↔ RGB ↔ HSL |
|
|
94
|
-
| `convert` | 8 categories, 72 units: length
|
|
120
|
+
| `convert` | 8 categories, 72 units: length, weight, temperature, area (tsubo, tatami), volume, speed, data, time |
|
|
95
121
|
| `char_info` | Unicode code point, block, category |
|
|
96
122
|
| `jwt_decode` | Decode header + payload (no verification) |
|
|
97
123
|
| `url_parse` | Protocol, host, path, params, hash |
|
|
@@ -105,12 +131,16 @@ Ask in natural language — the AI picks the right tool automatically.
|
|
|
105
131
|
claude mcp add -s user calc-mcp -- npx -y @coo-quack/calc-mcp
|
|
106
132
|
```
|
|
107
133
|
|
|
108
|
-
### Claude Desktop
|
|
134
|
+
### Claude Desktop / Cursor / Windsurf
|
|
109
135
|
|
|
110
136
|
Add to your config file:
|
|
111
137
|
|
|
112
|
-
|
|
113
|
-
|
|
138
|
+
| App | Config path |
|
|
139
|
+
|-----|-------------|
|
|
140
|
+
| Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
141
|
+
| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
142
|
+
| Cursor | `~/.cursor/mcp.json` |
|
|
143
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
114
144
|
|
|
115
145
|
```json
|
|
116
146
|
{
|
|
@@ -138,57 +168,12 @@ Add to `.vscode/mcp.json` in your workspace:
|
|
|
138
168
|
}
|
|
139
169
|
```
|
|
140
170
|
|
|
141
|
-
Or add globally via settings.json:
|
|
142
|
-
|
|
143
|
-
```json
|
|
144
|
-
{
|
|
145
|
-
"mcp": {
|
|
146
|
-
"servers": {
|
|
147
|
-
"calc-mcp": {
|
|
148
|
-
"command": "npx",
|
|
149
|
-
"args": ["-y", "@coo-quack/calc-mcp"]
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Cursor
|
|
157
|
-
|
|
158
|
-
Add to `~/.cursor/mcp.json`:
|
|
159
|
-
|
|
160
|
-
```json
|
|
161
|
-
{
|
|
162
|
-
"mcpServers": {
|
|
163
|
-
"calc-mcp": {
|
|
164
|
-
"command": "npx",
|
|
165
|
-
"args": ["-y", "@coo-quack/calc-mcp"]
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Windsurf
|
|
172
|
-
|
|
173
|
-
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
174
|
-
|
|
175
|
-
```json
|
|
176
|
-
{
|
|
177
|
-
"mcpServers": {
|
|
178
|
-
"calc-mcp": {
|
|
179
|
-
"command": "npx",
|
|
180
|
-
"args": ["-y", "@coo-quack/calc-mcp"]
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
171
|
## Development
|
|
187
172
|
|
|
188
173
|
```bash
|
|
189
174
|
bun install
|
|
190
175
|
bun run dev # Start dev server
|
|
191
|
-
bun test #
|
|
176
|
+
bun test # Run tests
|
|
192
177
|
bun run lint # Biome
|
|
193
178
|
bun run format # Biome
|
|
194
179
|
```
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -75870,16 +75870,56 @@ var tool17 = {
|
|
|
75870
75870
|
|
|
75871
75871
|
// src/tools/random.ts
|
|
75872
75872
|
var schema18 = {
|
|
75873
|
-
type: exports_external.enum(["uuid", "ulid", "password", "number"]).describe("Type of random value to generate"),
|
|
75873
|
+
type: exports_external.enum(["uuid", "ulid", "password", "number", "shuffle"]).describe("Type of random value to generate: uuid, ulid, password, number, or shuffle"),
|
|
75874
75874
|
uuidVersion: exports_external.enum(["v4", "v7"]).optional().describe("UUID version: v4 (random, default) or v7 (time-ordered)"),
|
|
75875
75875
|
length: exports_external.number().int().min(1).max(256).optional().describe("Length for password generation (default: 16)"),
|
|
75876
75876
|
min: exports_external.number().optional().describe("Minimum value for number generation (default: 0)"),
|
|
75877
75877
|
max: exports_external.number().optional().describe("Maximum value for number generation (default: 100)"),
|
|
75878
|
-
charset: exports_external.string().optional().describe("
|
|
75878
|
+
charset: exports_external.string().optional().describe("Custom character set for password (overrides uppercase/numbers/symbols options)"),
|
|
75879
|
+
uppercase: exports_external.boolean().optional().describe("Include uppercase letters in password (default: true)"),
|
|
75880
|
+
numbers: exports_external.boolean().optional().describe("Include numbers in password (default: true)"),
|
|
75881
|
+
symbols: exports_external.boolean().optional().describe("Include symbols in password (default: true)"),
|
|
75882
|
+
excludeChars: exports_external.string().optional().describe('Characters to exclude from password (e.g. "\\\\|{}")'),
|
|
75883
|
+
readable: exports_external.boolean().optional().describe("Readable mode: excludes ambiguous characters (l/1/I/O/0/o) for easy reading"),
|
|
75884
|
+
items: exports_external.array(exports_external.string()).optional().describe("Items to shuffle (for type=shuffle)")
|
|
75879
75885
|
};
|
|
75880
75886
|
var inputSchema18 = exports_external.object(schema18);
|
|
75881
|
-
var
|
|
75887
|
+
var LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
|
75888
|
+
var UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
75889
|
+
var NUMBERS = "0123456789";
|
|
75890
|
+
var SYMBOLS = "!@#$%^&*()-_=+[]{}|;:,.<>?/~`";
|
|
75891
|
+
var AMBIGUOUS = "lI1O0o";
|
|
75882
75892
|
var ULID_ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
75893
|
+
function buildCharset(input) {
|
|
75894
|
+
if (input.charset) {
|
|
75895
|
+
let cs2 = input.charset;
|
|
75896
|
+
if (input.excludeChars) {
|
|
75897
|
+
const exclude = new Set(input.excludeChars);
|
|
75898
|
+
cs2 = cs2.split("").filter((c) => !exclude.has(c)).join("");
|
|
75899
|
+
}
|
|
75900
|
+
if (cs2.length === 0)
|
|
75901
|
+
throw new Error("Charset is empty after exclusions");
|
|
75902
|
+
return cs2;
|
|
75903
|
+
}
|
|
75904
|
+
let cs = LOWERCASE;
|
|
75905
|
+
if (input.uppercase !== false)
|
|
75906
|
+
cs += UPPERCASE;
|
|
75907
|
+
if (input.numbers !== false)
|
|
75908
|
+
cs += NUMBERS;
|
|
75909
|
+
if (input.symbols !== false)
|
|
75910
|
+
cs += SYMBOLS;
|
|
75911
|
+
if (input.readable) {
|
|
75912
|
+
const ambiguous = new Set(AMBIGUOUS);
|
|
75913
|
+
cs = cs.split("").filter((c) => !ambiguous.has(c)).join("");
|
|
75914
|
+
}
|
|
75915
|
+
if (input.excludeChars) {
|
|
75916
|
+
const exclude = new Set(input.excludeChars);
|
|
75917
|
+
cs = cs.split("").filter((c) => !exclude.has(c)).join("");
|
|
75918
|
+
}
|
|
75919
|
+
if (cs.length === 0)
|
|
75920
|
+
throw new Error("Charset is empty after exclusions");
|
|
75921
|
+
return cs;
|
|
75922
|
+
}
|
|
75883
75923
|
function generatePassword(length, charset) {
|
|
75884
75924
|
const array3 = new Uint32Array(length);
|
|
75885
75925
|
crypto.getRandomValues(array3);
|
|
@@ -75933,6 +75973,18 @@ function generateULID() {
|
|
|
75933
75973
|
}
|
|
75934
75974
|
return timeStr + randomStr;
|
|
75935
75975
|
}
|
|
75976
|
+
function shuffle(items) {
|
|
75977
|
+
if (items.length === 0)
|
|
75978
|
+
throw new Error("Items array must not be empty");
|
|
75979
|
+
const result = [...items];
|
|
75980
|
+
for (let i2 = result.length - 1;i2 > 0; i2--) {
|
|
75981
|
+
const array3 = new Uint32Array(1);
|
|
75982
|
+
crypto.getRandomValues(array3);
|
|
75983
|
+
const j = array3[0] % (i2 + 1);
|
|
75984
|
+
[result[i2], result[j]] = [result[j], result[i2]];
|
|
75985
|
+
}
|
|
75986
|
+
return result;
|
|
75987
|
+
}
|
|
75936
75988
|
function execute18(input) {
|
|
75937
75989
|
switch (input.type) {
|
|
75938
75990
|
case "uuid":
|
|
@@ -75941,7 +75993,7 @@ function execute18(input) {
|
|
|
75941
75993
|
return generateULID();
|
|
75942
75994
|
case "password": {
|
|
75943
75995
|
const length = input.length ?? 16;
|
|
75944
|
-
const charset = input
|
|
75996
|
+
const charset = buildCharset(input);
|
|
75945
75997
|
return generatePassword(length, charset);
|
|
75946
75998
|
}
|
|
75947
75999
|
case "number": {
|
|
@@ -75949,11 +76001,17 @@ function execute18(input) {
|
|
|
75949
76001
|
const max3 = input.max ?? 100;
|
|
75950
76002
|
return String(generateRandomNumber(min3, max3));
|
|
75951
76003
|
}
|
|
76004
|
+
case "shuffle": {
|
|
76005
|
+
if (!input.items || input.items.length === 0) {
|
|
76006
|
+
throw new Error("Items array is required for shuffle");
|
|
76007
|
+
}
|
|
76008
|
+
return JSON.stringify(shuffle(input.items));
|
|
76009
|
+
}
|
|
75952
76010
|
}
|
|
75953
76011
|
}
|
|
75954
76012
|
var tool18 = {
|
|
75955
76013
|
name: "random",
|
|
75956
|
-
description: "Generate random values: UUID, ULID, secure password, or
|
|
76014
|
+
description: "Generate random values: UUID, ULID, secure password (with fine-grained options), random number, or shuffle a list",
|
|
75957
76015
|
schema: schema18,
|
|
75958
76016
|
handler: async (args) => {
|
|
75959
76017
|
const input = inputSchema18.parse(args);
|