Version License Docker MCP SDK npm TypeScript Bun
Install in Claude Desktop Install in Cursor Install in VS Code
Public Hosted Server: https://toolkit.caseyjhand.com/mcp
Overview
Everyday developer utilities: generate IDs, QR codes, and hashes, encode and decode values, and geolocate a public IP or hostname. Only the geolocation lookup and the gated network diagnostics (off by default) make network calls; everything else runs locally.
Tools
| Tool | Description |
|---|---|
toolkit_hash_value | Generate a digest or HMAC (sha256/sha384/sha512/sha1/md5) as hex, base64, base64url, or SRI, or constant-time-compare a value against an expected digest or signature |
toolkit_generate_id | Mint cryptographically random UUIDv4, UUIDv7, or ULID identifiers, up to 1000 per call |
toolkit_generate_qr | Encode text or a URL as a QR code in SVG, base64 PNG, or terminal half-blocks |
toolkit_encode_value | Encode or decode base64, base64url, hex, or URL percent-encoding |
toolkit_geolocate_ip | Resolve a public IP or hostname to country, city, coordinates, ASN, and timezone |
toolkit_check_network | Gated, off by default. Ping, traceroute, TCP connectivity, or IPv4 egress-IP check from the server host |
toolkit_check_system | Gated, off by default. OS, CPU, memory, load average, or network interfaces of the server host |
Skill
| Skill | Description |
|---|---|
toolkit-guide | Gates the tool descriptions don't carry: reading a digest or signature mismatch, webhook and JWT signing inputs, moving digest bytes between hex, base64, and base64url, who runs an IP's network, and host diagnostics |
MCP clients that support the Skills extension read it as skill://toolkit-guide/SKILL.md. Plugin installs load it from skills/, which also ships in the npm package, the Docker image, and the .mcpb bundle.
Capability reference
toolkit_hash_value tool
valueread perinputEncoding(utf8default,hex,base64);algorithmissha256(default),sha384, orsha512, withsha1andmd5for checksum compatibility and verifying existing HMAC-SHA1 signatures only;operationisgenerateorcompare, and when omitted it compares ifexpectedis sent- A non-empty
secretmakes the digest an HMAC keyed by it, for webhook signatures (GitHub'sX-Hub-Signature-256pastes intoexpectedas-is) and HS256 JWT signatures;secretEncodingisutf8(default),hex,base64, orbase64url(a JWKk). The result then carrieshmac: true, and the secret is never echoed in the result or an error; a secret invalid for its encoding fails asinvalid_secret_encoding generatereturnsdigestindigestEncoding(hexdefault,base64, unpaddedbase64url, orsrifor plain SHA-2 digests, where an HMAC fails assri_with_secret) pluslengthInBytes;comparereturnsmatchesagainstexpected, pasted as published: hex, base64, or base64url, bare or prefixed (sha256:<digest>,sha256=<digest>); SRI, including a multi-entry npmintegrityvalue; or one checksum-file line assha256sum,shasum,md5, oropenssl dgstprints it- A bad
expectedfails asexpected_malformed(including a whole multi-line checksum file, with a hint to pass the file's own line),expected_length_mismatch(the hint names the algorithm that length fits), orexpected_algorithm_mismatch(a tag, prefix, or SRI entry naming another algorithm)
toolkit_generate_id tool
typeisuuid_v4(default),uuid_v7, orulid;countis 1–1000 (default 1)idsalways holds exactlycountvalues;uuid_v7andulidbatches are strictly increasing even within one millisecond, with random gaps so no id is derivable from another
toolkit_generate_qr tool
dataup to 2953 UTF-8 bytes aterrorCorrectionL, less atM(default),Q, andH;formatissvg(default),png_base64, orterminal;margin0–20 modules (default 4),scale1–32 px per module (default 4)- Returns
content, the symbolversion(1–40),mimeTypefor image formats, andbyteLengthfor PNG;png_base64also arrives as an MCP image block, andterminalis plain Unicode half-blocks drawn for a dark background - Over-capacity input fails as
data_too_largewith its byte count; a PNG over 2048 px per side fails asraster_too_largewith a scale that fits, whilesvgandterminalhave no pixel cap
toolkit_encode_value tool
operation(encodeordecode) andencoding(base64,base64url,hex,url) are required; whitespace inhex,base64, andbase64urlinput is ignored, so wrapped MIME and PEM bodies decode as-is- Decode returns
resultasutf8text unlessoutputEncodingasks forhex,base64, or unpaddedbase64url, which returns the bytes losslessly and transcodes between encodings; malformed input fails asdecode_failed, and binary bytes asdecode_not_utf8rather than with replacement characters
toolkit_geolocate_ip tool
targetis an IPv4/IPv6 address or a dotted hostname, DNS-resolved first; private or reserved addresses, and public-format ones the provider reports as unlocatable, fail asprivate_target, unresolvable hostnames asunresolvable_host- Or send
targets(up to 1000) instead, never both: the first 100 return orderedresultswith per-target errors,failedCountcounts failures, andunprocessedTargetslists the rest to resubmit - Returns
country,countryCode,region,city,latitude/longitude,asn,asHolder,asName,org,isp, andtimezonewithresolvedIpandsource; atrueinproxy,hosting, ormobilemeans the location describes infrastructure, not a person orgcan name a customer or sub-allocation (APNIC and Cloudflare DNS Resolver projectfor1.1.1.1);isp,asHolder, andasName(CLOUDFLARENET) name the network operator. With noorgfrom the provider,orgcarriesasHolder, thenisp- Uses the keyless ip-api free tier over plaintext HTTP by default (
TOOLKIT_GEO_BASE_URL,TOOLKIT_GEO_API_KEY); results are cached per resolved IP - Single and batch lookups have separate rate limits (
TOOLKIT_GEO_RATE_LIMIT_PER_MIN=45,TOOLKIT_GEO_BATCH_RATE_LIMIT_PER_MIN=15); refusals reportrate_limitedwith seconds to wait, and cache hits stay free
toolkit_check_network tool
modeisping,traceroute,connectivity, orpublic_ip(the host's IPv4 egress address; it can't show whether IPv6 egress works);targetis required except forpublic_ip, andport(1–65535) forconnectivity;count1–10 pings (default 3),timeoutMs100–30000 (default 3000, one budget across every address forconnectivity);family4or6probes one address family- A hostname resolves once to all its addresses, tried in turn and alternating IPv4 and IPv6 until one gives a result;
resolvedIpnames the address whose result is reported - A silent host is
reachable: false, not an error;pingaddsrttMs,sent,received, andpacketLossPercent,connectivityaddsoutcome(open,refused,timeout,unreachable), andtraceroutereturnshops; a hostname that won't resolve fails asunreachablein every mode, as does ping or traceroute when its binary gives no result for any address tried (missing, stopped at its time limit, or exited with an error), and afamilythe target has no address in fails asfamily_mismatch - Registered only when
TOOLKIT_ENABLE_NET_DIAGNOSTICS=true; unlessTOOLKIT_ALLOW_PRIVATE_NETWORK=true, a hostname's private and reserved addresses are skipped, a target with nothing else fails asprivate_target_blocked, and error messages count those addresses instead of naming them
toolkit_check_system tool
whatisos,cpu,memory,load, orinterfaces; exactly one matching facet object is populatedosnames the runtime (runtimeisbunornode,runtimeVersionits version) besidenodeVersion, the Node.js version the process reportsmemory.availableBytesis the allocation headroom, the same figure under Bun and Node, andlimitBytesappears under a container memory limit;totalBytes,freeBytes, andusedBytesare raw OS figures, and on macOSfreeBytesis free pages alone, sousedBytescounts reclaimable cache as used there- Under Bun on macOS the headroom is read from the host's page statistics; when they can't be read the call fails as
memory_stats_unavailableinstead of reporting free pages - Registered only when
TOOLKIT_ENABLE_SYSTEM_INFO=true, sinceosandinterfacesdisclose host topology and version details
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Toolkit-specific:
- Hashing, ID minting, QR encoding, and encode/decode run in-process on
node:crypto,Buffer, and theqrcodelibrary, with no upstream calls - Geolocation calls the provider, never the target, and checks the DNS-resolved IP against private ranges, so a hostname can't reach an internal address
- Fail-closed gating:
toolkit_check_networkandtoolkit_check_systemare absent fromtools/listunless enabled. Both report on the server's host, not the caller's, so they belong on local or self-hosted deployments - Two-tier network gate: with diagnostics on, private, loopback, link-local, and reserved targets (the cloud-metadata endpoint included) stay blocked until
TOOLKIT_ALLOW_PRIVATE_NETWORK=true
Agent-friendly output:
- Provenance: geolocation and network diagnostics echo
resolvedIp, the address located or probed; geolocation also names itssourceand omits fields the provider didn't report instead of inventing them - Response shaping: provider strings are capped at 256 characters, stripped of control characters and bidirectional override controls, and Markdown-escaped in
content[], so registry-controlled text likeorgcan't flood a model's context and reads back as written instead of rendering as links or formatting - Discriminated outputs:
operation,format,mode, andwhatecho what ran, and only that branch's fields are populated - Typed failures: every declared failure carries a
reasonand a recovery hint, and conflicting inputs (expectedwithgenerate,outputEncodingwithencode) are rejected by name, not ignored
Getting started
Public Hosted Instance
A public instance is available at https://toolkit.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"toolkit-mcp-server": {
"type": "streamable-http",
"url": "https://toolkit.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file. No API key is required.
{
"mcpServers": {
"toolkit-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/toolkit-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"toolkit-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/toolkit-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"toolkit-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/toolkit-mcp-server:latest"]
}
}
}
To enable the gated host-probing tools, add their flags to env (or -e for Docker):
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"TOOLKIT_ENABLE_NET_DIAGNOSTICS": "true",
"TOOLKIT_ENABLE_SYSTEM_INFO": "true"
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- No API key needed — geolocation uses the keyless ip-api free tier by default.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/toolkit-mcp-server.git
- Navigate into the directory:
cd toolkit-mcp-server
- Install dependencies:
bun install
Configuration
All variables are optional.
| Variable | Description | Default |
|---|---|---|
TOOLKIT_ENABLE_NET_DIAGNOSTICS | Register the gated toolkit_check_network tool. Leave off for hosted or shared deployments. | false |
TOOLKIT_ENABLE_SYSTEM_INFO | Register the gated toolkit_check_system tool. | false |
TOOLKIT_ALLOW_PRIVATE_NETWORK | With network diagnostics on, permit private, reserved, loopback, and link-local targets. | false |
TOOLKIT_GEO_API_KEY | API key for the geolocation endpoint, if it requires one. | none |
TOOLKIT_GEO_BASE_URL | Base URL for an ip-api-compatible endpoint. The keyless default is plaintext HTTP; ip-api's HTTPS endpoint needs a paid key. | http://ip-api.com |
TOOLKIT_GEO_CACHE_TTL_SECONDS | In-memory geolocation cache TTL in seconds. | 3600 |
TOOLKIT_GEO_RATE_LIMIT_PER_MIN | Max geolocation requests per minute; cache hits don't count. | 45 |
TOOLKIT_GEO_BATCH_RATE_LIMIT_PER_MIN | Max batch geolocation requests per minute, counted separately. | 15 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:http -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security, changelog sync bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t toolkit-mcp-server .
docker run --rm -p 3010:3010 toolkit-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/toolkit-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers the five always-on tools, and the two gated tools only behind their flags. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Seven tools, five always-on and two gated. |
src/services/geo | Geolocation service — DNS resolution, provider call with retry, normalization, in-memory cache. |
src/services/network | Network-diagnostic service plus the shared target schema and private-range classifier. |
src/utils | Strict hex / base64 / base64url decoder shared by toolkit_hash_value and toolkit_encode_value. |
skills/ | The published toolkit-guide agent skill. |
tests/ | Vitest suites for tools (tests/tools) and services (tests/services). |
Development guide
See CLAUDE.md / AGENTS.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage - Register new tools in the
createApp()arrays insrc/index.ts; a host-probing tool registers only behind an enable flag - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.