Skip to main content
Glama

Docs: https://bkraad47.github.io/ramen/ · Get started · The MCP repo · Connect a client with OAuth · How it works · Deploy on GCP · Deploy on AWS · Deploy on Azure · Contracts · Releases

Streamable HTTP is the front door. Every worker serves POST /mcp — a URL and a bearer header, nothing to install — next to the gRPC service it has had since 0.3.1, on the same port, through the same guards. Phones, browsers and hosted agent platforms connect directly; the stdio bridge stays for clients that only speak stdio. Per-user access through OAuth (the console is the authorization server), live-verified on GKE since 0.5.1.

What is true today, before the pitch. Current release 0.7.5. The local stack and CI prove both transports on real node processes on Linux and Windows. 0.7.5 itself was deployed on GKE, EKS and AKS on 2026-10-10/11 and passed the same 33 checks on each: a canary deploy of the demo repo with its NeMo rails and golden cases, custom roles and per-tool access, a person's token over Streamable HTTP and through the stdio bridge, and the guardrail block on both. Microsoft Entra ID sign-in with group lookup ran live on all three (including removal from a group and return), Google sign-in on GKE; Google Workspace groups are tested against a fake only. Autoscaling was load-tested on all three (64 concurrent calls, up to the max and back to 1, no 5xx). Each environment was torn down afterwards. Earlier releases did the same on GKE and AWS since 0.5.x. Everything below is written so those lines stay findable.

Build and deploy your own MCP tools across zones, on GKE, EKS or AKS. Ramen turns a git repo of tools, resources and prompts into a fleet of MCP workers behind a cloud load balancer, and keeps who-can-call-what, secrets, canary gates and the audit trail in one console. It is a platform for your tools, not a gateway in front of someone else's. Each worker pairs a Rust MCP node (Streamable HTTP and gRPC, bearer auth, IP allow-lists, health, logs) 1:1 with a Python 3.12 runtime that pip-installs and runs your code. One console manages groups (tenants), environments, zones, secrets, canary deploys, autoscaling, IP rules, logs, audit and backups — in the browser or through an API key.

  • Git → bucket → worker. Deploy syncs the repo to a bucket; workers load by content hash. No git creds on pods.

  • Canary by default, gated. Roll a canary, smoke-test tools/list, diff its tool schemas against stable (breaking changes stop unless you say breaking: true), run the repo's golden cases, then roll stable. Failure leaves stable untouched.

  • Multi-zone from day one. Group → Environment → Zone → Worker; the LB routes on ramen-group / ramen-zone metadata, so one client config works for every zone.

  • Per-tool access and a drift signal (0.7.2). Decide per environment who may see and who may call each tool (group keys, group admins, viewers, MCP users); clients learn about a rollout through tools/list_changed; the group page shows how often the same caller repeated the same call, with a warning threshold.

  • Guardrails inside the worker (0.7.5). Opt a tool into a pre-call and post-call check that runs in the Python runtime next to it: NVIDIA NeMo Guardrails rails (deterministic or LLM-backed, your model and key) or a policy.py of your own. A blocked call is a readable tool error, the tool never ran or its output never left the pod, and the access log says which rail. Per tool, fail-closed by default.

  • Your identity provider decides, your admins override (0.7.5). Add Microsoft Entra ID, Google Workspace or any OIDC provider on the Config page; read a person's groups from Graph or Cloud Identity at sign-in, map them to roles per group, and keep what admins set by hand. Any IdP on any cloud.

  • Custom roles (0.7.5). Define analyst on the Viewer base, use it in memberships, provider rules and per-tool access; the person's token carries it to the worker. Mark a role OAuth only and its holders cannot use a password.

  • Autoscaling by Ramen (0.7.5). Each zone runs between 1 worker and a max that super admins and group admins set. The console scales up fast under load and back down when quiet, the same on all three clouds, with no HPA and every step in the audit log. How it decides.

  • Three clouds (0.7.5 adds AKS). Terraform and Helm for GKE (Gateway, Cloud Armor), EKS (ALB, WAF) and AKS (Envoy Gateway, Let's Encrypt, Table Storage, Key Vault, workload identity). The same console and guards on each.

  • Enterprise controls. A role per group (Group Admin, Viewer, MCP User or one you define) plus global super admins, rmk_ MCP keys and rmn_ API keys stored as salted hashes, IP rules (per zone at the node, and at the edge: Cloud Armor, WAF or an Envoy policy), secrets that are never displayed, an audit line for every action.

  • Transport: Streamable HTTP at the edge, gRPC inside. POST /mcp for any client that can make an HTTP request; ramen.v1.Mcp/Call for teams that want gRPC internally. One set of guards serves both — the same functions, spelled 401 / 403 / 429 on one and UNAUTHENTICATED / PERMISSION_DENIED / RESOURCE_EXHAUSTED on the other — so the two paths cannot drift.

  • Cheaper: one Deployment per zone, not one per tool. A worker is one Rust node plus one Python runtime that loads every tool of the group. A team with thirty small tools runs them on one Deployment per zone (two pods with a canary), behind one load balancer. Container-per-MCP-server designs run thirty. Ramen's autoscaling adds pods for load, not for tool count.

  • Secure, in one sentence. User code never runs in the process that holds the keys and does the auth: the Rust node checks every call and hands the message to a separate Python process it can kill and respawn.

Built on how organizations work

Groups own tools in git, environments pin a ref and a set of zones, people hold a role per group, agents and clients sign in as themselves, and every deploy is a canary, a smoke test and a rollout. Underneath it is gRPC, JSON-RPC 2.0 and a Rust node; you code in Python. The longer argument is How it works and why.

Start here · the demo group repo ramen-demo-mcp-group (point a group at it and press Deploy) · the stdio bridge ramen-mcp-bridge on PyPI (uvx ramen-mcp-bridge@latest; signs you in with --oauth or carries a group key) · HTTP clients such as Claude Code, Cursor and VS Code need no bridge with a group key, and Claude Code can also sign you in with OAuth. See Connect a client. Every feature and where it is managed: How it works.

Related MCP server: MCP Manager

Quickstart (local, 5 commands)

Needs Docker with compose v2, uv, git and make. The first run builds two images and takes three to five minutes.

git clone https://github.com/bkraad47/ramen && cd ramen
make up       # Firestore emulator + console https://localhost:8443 + one worker (localhost:8080: Streamable HTTP + gRPC)
make demo     # zone, group `demo` from the demo repo, an rmk_ key, a canary deploy, then a tools/call
#   PASS: demo_calculator_tool(2,3,add) -> 5      <- the success line
open https://localhost:8443    # self-signed cert; login admin@ramen.local / changeme-ramen
make down     # stop and remove volumes

make demo is safe to re-run: an existing zone, group or environment answers "exists" and a fresh key is generated each time. Then connect your own client with an rmk_ MCP key (Groups → demo → Generate key → Deploy). It is a URL and a header — put the key in RAMEN_MCP_KEY and drop this into any mcpServers config:

{"mcpServers": {"ramen-demo": {"url": "http://localhost:8080/mcp",
  "headers": {"Authorization": "Bearer ${RAMEN_MCP_KEY}", "ramen-group": "demo", "ramen-zone": "local"}}}}

Anything that can make an HTTP request is a client:

curl -s http://localhost:8080/mcp -H "Authorization: Bearer $RAMEN_MCP_KEY" -H 'Content-Type: application/json' \
  -H 'ramen-group: demo' -H 'ramen-zone: local' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"demo_calculator_tool","arguments":{"var1":2,"var2":3,"func":"add"}}}'
# {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"5"}],"isError":false}}

Clients that only speak stdio use the bridge, which forwards to the same worker over gRPC:

# its own package (github.com/bkraad47/ramen-mcp-bridge); uvx runs the newest release without installing it
RAMEN_MCP_KEY=<the key shown once> uvx ramen-mcp-bridge@latest --target localhost:8080 --insecure --group demo --zone local

rmk_ MCP keys go to workers (Authorization: Bearer, on HTTP or as gRPC metadata) and are generated on the group page. rmn_ API keys go to the console (X-Ramen-Api-Key) for automation and are generated on the API keys page. They are not interchangeable. For a token scoped to one person rather than a shared key, register an OAuth client on the Config page: the worker's 401 tells an OAuth-capable client where to sign in. Full walkthrough with the mcp SDK and a raw grpcurl call: Get started (also in deploy/local/README.md).

Connect a client

Two ways in, through the same guards. Streamable HTTP: the client talks to https://<edge>/mcp itself. The bridge (ramen-mcp-bridge): a small stdio program the client starts; it speaks gRPC to the edge and, for a person, does the browser sign-in. Ramen has no dynamic client registration, so a person's OAuth sign-in works over HTTP in Claude Code (it takes a client id) and through the bridge everywhere else. Admins do not have to type any of this: the group page's How MCP users sign in card prints every command filled in for the install, zone by zone.

Who signs in

Claude Code

Cursor, Claude Desktop

VS Code / GitHub Copilot

A group key (agents, CI)

HTTP: claude mcp add --transport http … --header

Cursor: HTTP, url + headers; Claude Desktop: bridge --key

HTTP: type: http, url, headers

A Ramen password account

HTTP with oauth.clientId, or the bridge --oauth

bridge --oauth

bridge --oauth

Microsoft Entra ID or Google Workspace

same as a password account: the console's login page hands off to the provider

bridge --oauth

bridge --oauth

Group key over Streamable HTTP

claude mcp add --transport http ramen-demo-a https://<edge>/mcp \
  --header "Authorization: Bearer <rmk_ key>" --header "ramen-group: demo" --header "ramen-zone: a"
{"mcpServers": {"ramen-demo-a": {"url": "https://<edge>/mcp",
  "headers": {"Authorization": "Bearer ${RAMEN_MCP_KEY}", "ramen-group": "demo", "ramen-zone": "a"}}}}

That is Cursor's ~/.cursor/mcp.json; VS Code's .vscode/mcp.json takes the same entry under "servers" with "type": "http".

A person, over Streamable HTTP (Claude Code). A super admin registers a client on Config → OAuth clients (redirect http://localhost/callback) and gives the person a role in the group; MCP User is enough.

claude mcp add-json ramen-demo-a '{"type":"http","url":"https://<edge>/mcp",
  "headers":{"ramen-group":"demo","ramen-zone":"a"},
  "oauth":{"clientId":"<client id>","scopes":"mcp:demo:a"}}'

Then /mcp in Claude Code and sign in: a Ramen password, or the Entra ID / Google button when a super admin has added the provider; a role marked OAuth-only allows only the provider.

Through the bridge, with a key or as a person

claude mcp add ramen-demo-a -- uvx ramen-mcp-bridge@latest --target <edge>:443 --tls --key <rmk_ key> --group demo --zone a
claude mcp add ramen-demo-a -- uvx ramen-mcp-bridge@latest --target <edge>:443 --tls \
  --oauth https://<edge> --client-id <client id> --group demo --zone a
{"mcpServers": {"ramen-demo-a": {"command": "uvx", "args": ["ramen-mcp-bridge@latest",
  "--target", "<edge>:443", "--tls", "--oauth", "https://<edge>", "--client-id", "<client id>",
  "--group", "demo", "--zone", "a"]}}}

The JSON is Cursor's and Claude Desktop's; in VS Code put it under "servers" with "type": "stdio". Bridge 0.3.0 keeps a person's token in memory (--keychain for the OS keychain, --token-file for automation), forgets it after 30 idle minutes, and when a sign-in is needed mid-session answers the call with error -32001 and the sign-in link, so the AI client can hand it to the person. @latest keeps a cached uvx from running an old bridge. Details: Connect with OAuth · Entra ID and Google Workspace · Transport.

Screenshots

Login

Dashboard, load per zone and group

login

dashboard

Group: environments, deploy jobs, zones/workers, MCP keys

Deploy job with streamed log

group

deploy job

Secrets (names only, values never shown)

Logs (one JSON line per MCP call, downloadable)

secrets

logs

Identity providers: Entra ID with group lookup and role rules

Custom roles, one OAuth-only

identity providers

roles

Zone actions: count, max, last autoscale decision

Zones and workers with the max per group

autoscale

zones

Transport, and what secures each hop

Workers speak Streamable HTTP (POST /mcp, one JSON-RPC 2.0 message per request, MCP spec 2025-06-18) and gRPC (ramen.v1.Mcp/Call, one message as bytes body) on the same port (contract §16, §11). MCP itself is unchanged — your client and your tools see the standard messages. The two transports share one implementation of every check: the HTTP handler turns the request headers into the same metadata map and calls the same guard and dispatch functions the gRPC service calls, so a check added to one is on both or on neither.

Why the node is Rust. A worker runs two processes with one job each. ramen-node (Rust + tonic) owns what must not be slowed down or broken by user code: the gRPC surface, key checking, source-range checking, the blocked-name filter, concurrency bounds, deadlines, health and the access log. It is a small static binary with no interpreter and no user code in its address space. ramen_runtime (Python 3.12) owns what users write: pip install, validation, secret substitution, the call. They talk over newline-delimited JSON-RPC on stdin/stdout (§2), so there is no extra socket to secure, and the runtime is killed after an idle timeout — a crash or leak in tool code costs one respawn, not the process holding the keys.

Hop

What protects it

client → edge (HTTP)

TLS at the load balancer; the credential in Authorization: Bearer (an rmk_ key or a per-user OAuth token); browser origins only from the zone's allowlist; session ids signed and bound to the credential

client → bridge → edge (stdio)

a child process on the client's own machine, speaking gRPC to the edge; plaintext unless --tls (--ca <pem> pins the certificate)

edge → node

TLS ends at the load balancer; h2c to the node unless the node has its own certificate; Cloud Armor (GCP) or WAF (AWS) IP rules, one policy per group; an Envoy SecurityPolicy per zone (AKS)

every Mcp/Call

constant-time key compare; source range against the right x-forwarded-for entry; blocked names; 4 MiB and in-flight caps

edge → node, without a key

only grpc.health.v1.Health; reflection is off on deployed workers

anything else → the pod

the worker NetworkPolicy plus a hardened container context

node → runtime

stdio inside the pod; no network surface

runtime → bucket

the zone's own cloud identity (GCP service account with Workload Identity, AWS IAM role with IRSA, Azure managed identity with workload identity), scoped to the group's prefix (on Azure the container) and secrets

runtime → your tool

opt-in guardrails per tool: NeMo rails or a policy.py before the call and after the result; secret values never reach a rail

console → node

cluster-internal, never through the load balancer; Admin/* needs an admin key and an admin CIDR

Five details behind that table matter in practice. The origin allowlist is empty by default, so every browser Origin is refused until you add one. The source-range check reads the x-forwarded-for entry a proxy appended (hop count 2 on GCP, 1 on AWS and AKS), and a wrong count denies rather than admits. The allowlist itself defaults to everything until you set IP rules. An IP lock must include the console's own range, because a deploy smoke-tests tools/list as an ordinary call. grpc.health.v1.Health is deliberately unauthenticated so load balancers can probe it, and reports SERVING only once the runtime has loaded the group's code.

What is verified, in five lines.

  • Both transports through every guard, on real node processes, in CI on Linux and on a Windows runner that builds the node natively (0.5.0).

  • 0.7.5 live on GKE, EKS and AKS: the 33-check run on each, Entra ID sign-in with group lookup on each, autoscaling load tests on each, every environment torn down afterwards.

  • The GCP and AWS paths live every release since 0.5.x, with two zones, OAuth, the Redis throttle and real teardown in 0.6.0 and 0.6.1.

  • Covered by tests only, never on a real load balancer: node TLS, and the size and in-flight caps.

  • By design, a group's code runs in a process that holds that group's secrets. Isolation between groups is the pod, the namespace and the per-zone identity.

Full write-up: Transport and what secures each hop.

Deploy to the cloud

Target

Status

Guide

GCP — GKE Autopilot, Firestore, GCS, Secret Manager, global HTTPS LB (GKE Gateway, header-routed gRPC and Streamable HTTP, gRPC health checks), Cloud Armor

verified on a throwaway project every release, most recently 0.7.5: 33 checks, Entra ID and Google sign-in, autoscaling load tests

docs · deploy/README.md

AWS — EKS, DynamoDB, S3, Secrets Manager, ALB (gRPC and HTTP/1.1 target groups), WAF (Terraform or CloudFormation)

applied to a real account since 0.5.6, most recently 0.7.5: 33 checks, Entra ID sign-in, autoscaling load tests

docs

Azure — AKS (workload identity), Table Storage, Blob Storage, Key Vault, Envoy Gateway (Gateway API, header-routed gRPC and Streamable HTTP) with a Let's Encrypt certificate

new in 0.7.5, verified live in westus2: 33 checks, Entra ID sign-in, autoscaling load tests

docs

Local — docker compose

CI e2e on every push

deploy/local/README.md

Bring-up is the same shape on each cloud. On GCP it is terraform apply → make push → helm upgrade --install → add a zone and a group in the console → Deploy. About 25 minutes, mostly waiting for GKE and the load balancer. The load balancer gets a publicly-trusted certificate automatically (a free sslip.io hostname derived from the static IP — no domain to buy, since 0.5.5; on AKS from Let's Encrypt, since 0.7.5). Clients then use https://<public_hostname>/mcp with Authorization: Bearer rmk_ and the ramen-group / ramen-zone headers (the same address serves the console and, by those headers, every zone); stdio-only clients point the bridge at <public_hostname>:443 --tls — no --ca, nothing to import.

Write your own tools

Full guide with the demo repo, env.yaml and local development: The MCP repo, structure and local development.

A group repo is any git repo with mcp/tools/<name>/<name>.py + <name>.json (and resources/, prompts/, requirements.txt). Start from ramen-demo-mcp-group; the contract is in the MCP repo page. Secrets are referenced as {{$group.NAME}} and substituted by the runtime at call time. Nothing about the transport leaks into tool code. Declare output and the worker publishes it as outputSchema and validates every result; add mcp/tests.yaml and your cases gate every deploy (0.7.0). Add mcp/guardrails.yaml and NeMo rails or a policy.py check the arguments before a tool runs and its result before it leaves the worker (0.7.5, Guardrails).

Repository

Dir

What

console/

FastAPI + Jinja2 + HTMX manager UI and /api/v1; gRPC client to workers

node-rs/

Rust MCP server node (tonic: ramen.v1.Mcp, ramen.v1.Admin, grpc.health.v1.Health; auth, CIDRs, sidecar supervisor)

runtime-py/

Python 3.12 runtime (loads protos, pip installs, runs calls, resolves secrets, runs guardrails)

proto/

ramen/v1/mcp.proto, admin.proto — the transport contract, single source for Rust and Python stubs

deploy/

compose, Helm charts, Terraform (GCP, AWS, Azure), CloudFormation

skills/

Cloud-ops agent skills: deploy-gcp, deploy-aws, rotate-keys, backup-restore, scale-zone

tests/

Black-box conformance (gRPC + bridge), e2e and cloud suites

docs/

This site's sources; docs/CONTRACTS.md is binding for every component (§11 = transport)

Architecture: ARCHITECTURE.md · Changes: CHANGELOG.md · Versions: tracker

Develop

make test            # runtime-py and console (pytest, 90% coverage gate) plus node-rs (fmt, clippy, test)
make test-harness    # tests/: conformance + e2e (skips without a running stack)
make proto           # regenerate Python stubs from proto/ (Rust stubs build via tonic-build)
make demo-worker     # node + runtime locally without Docker
uv run --project docs --group docs mkdocs serve   # docs at http://127.0.0.1:8000

Contributing

See CONTRIBUTING.md — use it, fork it, change it, with attribution; renaming it as a new commercial product of your own is not acceptable. Related repositories and which versions go together: Releases.

License

BSD-3-Clause © 2026 Raad. See LICENSE.

Available Tools

3 tools
demo_calculator_toolA

Add, subtract, multiply or divide two numbers and return the result as a number. Pure and stateless: no side effects, no network, no auth beyond the group's key, no rate limit of its own. Division by zero returns a tool error (isError) with a one-sentence message instead of a value. Use it when an exact arithmetic result matters; for anything beyond one binary operation, chain calls or compute in the model.

ParametersJSON Schema
NameRequiredDescriptionDefault
funcYesoperation to apply as var1 <func> var2
var1Yesfirst operand (left-hand side); any finite number
var2Yessecond operand (right-hand side); must be non-zero for divide

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYesvar1 <func> var2

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: pure/stateless, no side effects, no network, auth only via the group key, no own rate limit, and the division-by-zero path returns isError with a one-sentence message rather than a value. That is exactly the behavioral context an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four short sentences, front-loaded with purpose, then behavior, then the error case, then routing guidance. Every sentence earns its place with no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need no explanation, and the description still covers the one non-obvious edge case (division by zero producing a tool error). Nothing needed to call this tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents func's enum and both operands (including the non-zero constraint for divide). The description adds only the 'two numbers' framing and the left/right ordering implied by var1 <func> var2, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb set (add, subtract, multiply, divide) plus the resource (two numbers) and the return type. It is trivially distinguishable from the arithmetic-free siblings unit_convert and word_count.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names when to use it ('when an exact arithmetic result matters') and when not to ('for anything beyond one binary operation, chain calls or compute in the model'). The alternative path is stated, not implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

unit_convertA

Convert a value between units of length (mm, cm, m, km, inch, foot, yard, mile), mass (mg, g, kg, tonne, ounce, pound) or temperature (celsius, fahrenheit, kelvin) and return the converted number. Pure and stateless, no side effects, no auth beyond the group's key. Units are matched case-insensitively by the names listed; an unknown unit or a conversion across families (length to mass) returns a tool error (isError) naming the accepted units. Use it for exact factor-based conversions; it does not handle currencies, dates or compound units such as km/h.

ParametersJSON Schema
NameRequiredDescriptionDefault
valueYesthe quantity to convert, in from_unit
to_unitYesunit of the result, same family as from_unit
from_unitYesunit of value, e.g. km, pound, celsius

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYesvalue expressed in to_unit

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: pure/stateless, no side effects, no auth beyond the group key, case-insensitive unit matching, and explicit error semantics (tool error with isError naming accepted units on unknown unit or cross-family conversion). That is unusually complete behavioral disclosure for a stateless utility.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose and return value in the first sentence, then behavior, then routing guidance. Every clause carries distinct information (units, families, case rules, error behavior, exclusions) with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return formatting need not be elaborated, yet the description still states the result is a converted number. Combined with the auth, statelessness and error details, an agent has everything needed to call this correctly with three required parameters.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3; the description still adds real value by listing the accepted unit names, stating that matching is case-insensitive, and defining the cross-family failure mode for from_unit/to_unit. It stops short of documenting numeric ranges or precision/rounding behavior for value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb (convert) and resource (units of length, mass, temperature) and enumerates the exact accepted unit symbols, so an agent knows precisely what domain this covers. It also carves itself apart from a generic calculator sibling by excluding currencies, dates and compound units like km/h.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Use it for exact factor-based conversions; it does not handle currencies, dates or compound units such as km/h" gives an explicit when-to-use plus a concrete when-not-to-use set. Although it does not name the sibling tools, the exclusions are the operationally relevant routing signal and leave nothing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

word_countA

Count the characters, words and lines of a text and return the three counts as an object. Pure and stateless: the text is not stored or sent anywhere; no auth beyond the group's key. Words are whitespace-separated runs; lines are newline-separated (a trailing newline does not add a line); characters are Unicode code points including whitespace. Use it when a model needs an exact count rather than an estimate; it does not tokenise or detect language.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesthe text to count; may be empty, which yields zeros

Output Schema

ParametersJSON Schema
NameRequiredDescription
linesYesnewline-separated lines; 0 for an empty text
wordsYeswhitespace-separated runs
charactersYesUnicode code points, whitespace included

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden, and it delivers: 'Pure and stateless: the text is not stored or sent anywhere; no auth beyond the group's key.' It also pins down exact counting semantics (whitespace-separated words, newline-separated lines without a trailing-newline artifact, Unicode code points including whitespace), which is unusually complete for a no-annotation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four sentences, front-loaded with what it counts and what it returns, then safety, then precise definitions. Every sentence contributes something an agent cannot get from the schema, with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-value documentation is unnecessary, yet the description still names the object shape. For a stateless one-parameter utility it covers purpose, selection, safety/auth, and edge-case semantics, leaving nothing an agent needs before calling it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single parameter is already documented, including the empty-string case. The description's word/line/character rules govern how the result is computed rather than adding new meaning to the `text` parameter, so the baseline of 3 for high schema coverage is the right level.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb and resource ('Count the characters, words and lines of a text') plus the exact return shape ('return the three counts as an object'). It is trivially distinguishable from the unrelated siblings demo_calculator_tool and unit_convert, and no other tool in the set could be confused with it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly gives the selection condition ('Use it when a model needs an exact count rather than an estimate') and the when-not cases ('it does not tokenise or detect language'). An agent knows both when to reach for this tool and which related tasks it must not use it for, without any inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.7.1
    • Changeddemo_calculator_tool4 fields changed
      • changedInput schema / properties / func / description
        Previous value: -"operation"New value: +"operation to apply as var1 <func> var2"
      • changedInput schema / properties / var1 / description
        Previous value: -"first operand"New value: +"first operand (left-hand side); any finite number"
      • changedInput schema / properties / var2 / description
        Previous value: -"second operand"New value: +"second operand (right-hand side); must be non-zero for divide"
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "properties": {
        +    "result": {
        +      "description": "var1 <func> var2",
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "result"
        +  ],
        +  "type": "object"
        +}
    • Addedunit_convert
    • Addedword_count
  2. 1 tool updatev0.1.0
    • First observeddemo_calculator_tool

TDQS

A4.6/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a completely different operation: arithmetic (demo_calculator_tool), unit conversion (unit_convert), and text counting (word_count). There is no overlap or plausible misselection between them, and each description clearly states its scope and limits.

Naming Consistency4/5

All names are snake_case and readable, and two follow a clean noun_verb pattern (unit_convert, word_count). The outlier demo_calculator_tool adds an unnecessary 'demo_' prefix and '_tool' suffix, a minor deviation from the otherwise consistent convention.

Tool Count4/5

Three tools is on the thin side for a general-purpose utility server, but each is distinct and clearly earns its place with no filler. It is reasonable if the server is intentionally a small demo/utility set.

Completeness3/5

Each individual tool is self-contained and complete for its narrow function, but the server has no coherent domain, so there is no lifecycle or CRUD surface to evaluate. As a set it reads as a few unrelated utilities rather than a complete toolkit.

Maintenance

ActivityMaintained
ResponsivenessSlow

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    A Cloudflare Workers-based MCP server that enables secure remote connections using built-in OAuth authentication via Cloudflare Access. It provides identity-based access control for MCP tools and supports persistent state management through Durable Objects and SSE.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Self-hosted MCP proxy and aggregation platform. Register multiple upstream MCP servers and expose them through a single unified endpoint with namespace routing, multi-transport support (HTTP/SSE, stdio, OpenAPI→MCP), per-tool overrides, and a web admin UI.
    18
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Remote MCP server with built-in OAuth authentication via Cloudflare Access, enabling secure tool access and user identity-based restrictions.
    -