
Tools
Seven tools. Five are always on and need no configuration β pure-compute utilities plus an SSRF-free IP lookup. Two probe the server host and stay absent from tools/list until you opt in, fail-closed.
| Tool | Description |
|---|---|
toolkit_hash_value | Generate a cryptographic digest (sha256/sha512/sha1/md5), or constant-time-compare a value against an expected digest. |
toolkit_generate_id | Mint cryptographically-random identifiers β UUIDv4, UUIDv7, or ULID β singly or in batches up to 1000. |
toolkit_generate_qr | Encode text or a URL into a QR code as SVG markup, base64 PNG, or a terminal-renderable string. |
toolkit_encode_value | Encode or decode a value across base64, base64url, hex, or URL percent-encoding, in either direction. |
toolkit_geolocate_ip | Resolve a public IP or hostname to geographic and network metadata β country, city, coordinates, ASN, timezone. |
toolkit_check_network | Gated, off by default. Read-only network diagnostics from the server host β ping, traceroute, TCP connectivity, or egress-IP detection. |
toolkit_check_system | Gated, off by default. Report a facet of the server host's system state β OS, CPU, memory, load average, or network interfaces. |
toolkit_hash_value
Generate a digest, or constant-time-verify a value against an expected one.
operation:generate(lowercase-hex digest) orcompare(timing-safe check viatimingSafeEqual)- Algorithms:
sha256(default) andsha512for security;sha1andmd5are exposed for checksum and file-integrity compatibility only β never for passwords or signatures inputEncodingreadsvalueasutf8(default),hex, orbase64, so binary blobs skip a decode round-trip- Canonical use: match a download against a vendor-published checksum
toolkit_generate_id
Mint cryptographically-random identifiers from the platform CSPRNG β the correct source for IDs that must be unpredictable, unlike model-invented values.
type:uuid_v4(random, default),uuid_v7(time-ordered, sortable by creation), orulid(26-char Crockford base32, lexicographically sortable)countmints a batch up to 1000 in one call; the returnedidsarray always holds exactlycountvaluesuuid_v7andulidbatches are monotonic β strictly increasing even within the same millisecond β soidsstays in sorted creation order- Not read-only β each call is fresh entropy, so a client won't cache it or auto-approve it as side-effect-free
toolkit_generate_qr
Encode text or a URL into a QR code.
format:svg(inline markup),png_base64(raster bytes withmimeTypeandbyteLength), orterminal(Unicode block string)errorCorrection(L/M/Q/H) trades data capacity for damage tolerance;marginsets the quiet-zone width;scalesets pixels per module for raster output- The returned
version(1β40) reflects how dense the encoded data is datais capped at 2953 bytes β the absolute ceiling (version 40, level L, byte mode); usable capacity is lower at highererrorCorrectionlevels, so over-capacity input is rejected with a typeddata_too_largeerror rather than a generic failure
toolkit_encode_value
Encode or decode a value, in either direction.
encoding:base64,base64url(URL-safe alphabet),hex, orurl(percent-encoding)operation:encode(raw UTF-8 β encoding) ordecode(encoded value β text)- Malformed decode input returns a typed
decode_failederror with a recovery hint, not a silent best-effort
toolkit_geolocate_ip
Resolve a public IP or hostname to geographic and network metadata.
- Returns country, region, city, latitude/longitude, ASN, owning organization, and timezone
- A hostname is DNS-resolved first;
resolvedIpechoes the IP actually located, andsourcenames the answering provider - SSRF-free β the server calls the provider, never the target; the resolved IP is re-checked against private ranges, and private/reserved addresses are rejected (they have no public geolocation)
- Best-effort and provider-bounded: VPNs, proxies, mobile NAT, and anycast all defeat IP-to-location, accuracy is city-level at best, and absent fields are reported as unknown rather than invented
- Keyless by default (ip-api free tier); results are cached in memory by resolved IP
toolkit_check_network
Gated β registered only when TOOLKIT_ENABLE_NET_DIAGNOSTICS=true. Read-only network diagnostics from the server host.
mode:ping(ICMP round-trip),traceroute(hop path to the target),connectivity(raw TCP connect totargetonport), orpublic_ip(the host's own egress IP)- A host that does not respond is reported as
reachable: falseβ a valid result, not an error - Diagnoses the server's own network, so it is useful on a local or self-hosted deployment; reaching a private/reserved/internal target additionally requires
TOOLKIT_ALLOW_PRIVATE_NETWORK=true, which keeps the cloud-metadata endpoint blocked by default
toolkit_check_system
Gated β registered only when TOOLKIT_ENABLE_SYSTEM_INFO=true. Report a facet of the server host's system state, read-only.
what:os,cpu,memory,load, orinterfaces- Exactly one facet object is populated per call, matching
what - Describes the host this server runs on, not the calling client β meaningful on a local or self-hosted deployment; gated off by default because
osandinterfacesdisclose host topology and version details
Features
Built on @cyanheads/mcp-ts-core:
- Declarative tool definitions β single file per tool, framework handles registration and validation
- Unified error handling β handlers throw, framework catches, classifies, and formats
- Typed error contracts β each fallible tool declares its failure reasons with recovery hints the agent can act on
- Pluggable auth:
none,jwt,oauth - Structured logging with optional OpenTelemetry tracing
- STDIO and Streamable HTTP transports
Toolkit-specific:
- Fail-closed gating β the two host-probing tools are absent from
tools/listunless explicitly enabled, so a hosted instance exposes no SSRF or info-disclosure surface - Two-tier network gate β even with diagnostics enabled, private/reserved/loopback/link-local targets (including the cloud-metadata endpoint) stay blocked until a second flag permits them
- CSPRNG-backed primitives β identifiers and digests come from the platform crypto source, and hash comparison is constant-time via
timingSafeEqual - SSRF-free geolocation β the server calls the provider, then re-checks the DNS-resolved IP against private ranges before the lookup, so a hostname cannot smuggle a request to an internal address
Agent-friendly output:
- Provenance β geolocation echoes the resolved IP and names the answering provider; absent upstream fields are reported as unknown, never invented
- Down-but-valid results β an unreachable host returns
reachable: falseinstead of an error, so callers branch on data, not exception text - Typed failure reasons β decode failures, missing digests, and blocked private targets each carry a structured reason plus a next-step recovery hint
Project structure
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point β registers tools and inits services, with fail-closed gating for the two host-probing tools. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Seven tools β five always-on, two gated. |
src/services/geo | Geolocation service β DNS resolution, provider call with retry/backoff, normalization, in-memory cache. |
src/services/network | Network-diagnostic service plus the shared target validator and private-range classifier. |
tests/ | Unit and integration tests mirroring the src/ structure. |
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 - The two host-probing tools register behind their enable-flags; the network target gate validates after DNS resolution β never fabricate a result for an unlocatable or unreachable target
Contributing
Issues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run testLicense
Apache-2.0 β see LICENSE for details.
Or with npx (no Bun required):Before it works, you'll need: MCP_TRANSPORT_TYPEMCP_LOG_LEVEL
Getting started
Add the following to your MCP client configuration file. No API key is required β the five always-on tools and the default keyless geolocation tier work out of the box.
{
"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/mcpPrerequisites
- Bun v1.3.2 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 installConfiguration
Every variable is optional. Server-specific options are validated at startup via the Zod schema in src/config/server-config.ts.
| 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. Meaningful only on a local or self-hosted deployment. | false |
TOOLKIT_ALLOW_PRIVATE_NETWORK | With network diagnostics on, permit private/reserved/loopback targets. The second explicit gate. | false |
TOOLKIT_GEO_PROVIDER | IP-geolocation provider id. The default ip-api tier is keyless. | ip-api |
TOOLKIT_GEO_API_KEY | API key for the geolocation provider, if it requires one. | none |
TOOLKIT_GEO_BASE_URL | Base URL for the geolocation provider. | 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. | 45 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | 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 -e MCP_TRANSPORT_TYPE=http -p 3010:3010 toolkit-mcp-serverThe 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.
No common issues documented yet. If you hit a problem, the repository's GitHub Issues page is the best place to look.