Skip to main content
Glama
clinic-mcp-server

clinic-mcp-server

by Ariless

clinic-mcp-server

CI

An MCP server written so that its security properties can be asserted, together with the suite that asserts them. It exposes a clinic booking API to an agent: read the doctor list, read a patient's own appointments, and — only under a profile that says so — book and cancel.

The interesting half is not what the tools do. It is the gap between what a tool does and what an agent is able to reach, which is why every test here drives a real MCP client over an in-memory transport instead of importing the handlers. tools/list and tools/call are what an agent sees. A test that called the functions directly would answer the first question and miss the second.

npm install
npm test          # 21 tests, no services, no API key, nothing leaves the machine

The three properties worth reading the code for

1. A tool outside the session's profile is never registered. Not registered-and-refused: absent from tools/list, absent from the model's context, unreachable by any prompt. A permission check inside a handler leaves the affordance in place — the name and the description still sit in the model's context, still describable, still part of a plan the model can assemble. That check does hold; no prompt talks a server out of an if. It is simply the only thing left between the plan and the call, and it has to stay correct through every later change to the file.

An unregistered name misses the same lookup that a name nobody ever invented misses, and comes back the same way: Tool <name> not found. The tests assert that wording, because "you are not allowed to call this" states that the capability exists and this session is the wrong one, which is an invitation to go looking for a better session. What a model does with that difference is not measured here; the assertion is about what the server says.

2. The server holds no credential of its own. The tempting design is a service account: one privileged token in the server's environment, with the tools deciding who may ask for what. That is the confused deputy, and it builds OWASP ASI03 in on purpose — a hijacked agent would inherit the server's authority over every patient in the database instead of the operator's over their own record. Here the caller's token is forwarded unchanged, so the API's own auth stays the authority and this server can never grant more than the person running it already had.

3. Tool descriptions are screened before the server will start. A description is not documentation. It is text injected into the model's context over a channel the model treats as trustworthy. The published shape of the attack is a description that tells the agent to read a credentials file "for validation" and pass the contents as an argument: nothing in the protocol prevents it, and no test of tool behaviour would notice, because the tool behaves exactly as written. The description is the payload.

descriptionPolicy.js rejects directive phrasing, references to other tools, invisible characters, and anything long enough to hide a payload in — and it reads parameter descriptions too, since they travel to the model inside the same tool definition. A violation stops the process at start-up: a poisoned description must not reach a model even once, and a server that refuses to boot is a failure someone reads, while a warning in a log is not.

One asymmetry in that policy is deliberate and has its own test. A tool description may not name another tool, because that suggests a call chain nobody asked for. A parameter description may, because there it says where the value comes from, which is what a caller needs in order to pass it correctly. The rule is about the field, not about the words.

Related MCP server: Ecuro Light API MCP Server

Tools

Tool

Profile

Mutates

Role the API will demand

list_specialties

readonly

no

—

list_doctors

readonly

no

—

list_my_appointments

readonly

no

patient

book_appointment

booking

yes

patient

cancel_appointment

booking

yes (destructive)

patient

list_doctor_schedule

full

no

doctor

Profiles nest: booking includes readonly, full includes both. An unrecognised profile yields an empty catalogue and refuses to build the server, rather than falling through to a default nobody chose — a typo in an environment variable should not hand an agent a catalogue.

Two of the tools aggregate locally instead of proxying. GET /doctors takes no query parameters and there is no /specialties route at all, so passing ?specialty= down would have looked like it worked: the API ignores unknown query strings and returns everyone, and the model would have reported a filtered list that was never filtered. Both tools are built on that one route, and the end-to-end test "list_doctors narrows by specialty, and the narrowing is real" is what keeps the aggregation honest — it asserts the narrowed list is both non-empty and strictly smaller than the full one, which is the pair of assertions a filter that silently does nothing would fail.

What the tests cover

21 tests in test/mcpServer.test.js, grouped by the OWASP Top 10 for Agentic Applications category each one makes real:

Category

What is asserted

ASI02 Tool Misuse

A readonly session does not see the mutating tools; calling one by exact name answers "not found", not "not allowed"; an unknown profile refuses to build the server

ASI03 Identity & Privilege Abuse

The caller's token is forwarded verbatim; no request leaves the process when the session has no token; unauthenticated tools send no Authorization header at all; a patient session is refused a doctor tool before the request is built, so it cannot probe which ids exist

ASI04 Agentic Supply Chain

The shipped catalogue passes policy; directive phrasing, zero-width characters, cross-tool references and over-long text are rejected in both tool and parameter descriptions; a poisoned catalogue stops the server from starting

ASI10 Rogue Agents (partly)

readOnlyHint and destructiveHint are derived from the catalogue rather than hand-written, so a client can gate human confirmation on them without them drifting from the handlers

end to end

Three tests run the whole path — MCP client → server → HTTP → an API on an ephemeral port — because everything above stubs fetch, which proves the policy and proves nothing about whether a tool works

The applicability mapping this grouping comes from — which categories a system can have, which it cannot, and what would have to exist for the rest to apply — lives in clinic-booking-api-tests as docs/OWASP_AGENTIC.md.

The stand-in, and what it is honest about

The real system under test is a private Express service. test-double/api.js stands in for it so this repository runs on its own, and the substitution is sound because HTTP is the only thing the server knows about the API: same routes, same status codes, and the same refusal to honour ?specialty= — copied deliberately, because an obliging stand-in would make the tool's own filtering untested and the regression above invisible.

What the stand-in is not: evidence that the tools work against the production service. It is evidence that the whole path is exercised and that a tool calling a route which does not exist turns the suite red. That is what the end-to-end tests are there to do — the check itself was confirmed by deleting the /doctors route and watching exactly the two tests that depend on it fail.

Running it against something

# against the stand-in
npm run start:double                                    # terminal 1, port 3000
MCP_PROFILE=readonly SUT_BASE_URL=http://127.0.0.1:3000 \
  SUT_ACCESS_TOKEN=patient-token node server.js         # terminal 2

# against a real API
MCP_PROFILE=booking SUT_BASE_URL=https://your-api \
  SUT_ACCESS_TOKEN=<the caller's jwt> node server.js

Speaks stdio, so it drops into any MCP client's config the usual way.

Variable

Default

Meaning

MCP_PROFILE

readonly

readonly · booking · full

SUT_BASE_URL

http://127.0.0.1:3000

Where the API lives

SUT_ACCESS_TOKEN

(none)

The caller's token. No default, deliberately

MCP_SESSION_ROLE

patient

patient · doctor · none

  • clinic-booking-api-tests — the suite this server was built for: RAG evaluation against a golden set, prompt-injection tests, the OWASP agentic mapping, a scheduled model-drift run

  • temporal-failure-lab — Kafka, outbox, DLQ and seven planted temporal defects, shipped with its own system under test

Available Tools

3 tools
list_doctorsA
Read-onlyIdempotent

List doctors, optionally narrowed to one specialty. Returns each doctor's id, name, specialty and clinic address.

Inputs: specialty.

list_my_appointmentsA
Read-onlyIdempotent

List the appointments belonging to the authenticated patient. Returns id, status, doctor and slot time for each.

Inputs: no named parameters.

list_specialtiesA
Read-onlyIdempotent

List the medical specialties this clinic staffs. Returns specialty names only. Takes no arguments.

Inputs: no named parameters.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A4.3/5.0

    Scored across 3 tools

    Disambiguation5/5

    Each tool addresses a distinct resource: specialties, doctors, and appointments. There is no overlap or ambiguity between the three operations.

    Naming Consistency5/5

    All tool names follow a consistent 'list_' + noun pattern. The 'my' in list_my_appointments is a minor modifier but does not break the naming convention.

    Tool Count4/5

    Three tools is a small but reasonable set for a read-only clinic lookup server. While the scope is narrow, each tool serves a clear purpose and the count is not deficient enough to score lower.

    Completeness2/5

    The server only provides list/read operations. There are no tools to create, update, or cancel appointments, which are core actions for a clinic appointment system. This creates significant gaps for agents needing to manage appointments.

    Maintenance

    ActivityStale
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables interaction with Athena Health's API for comprehensive healthcare practice management. Supports appointment scheduling, provider and department management, patient search, and available slot discovery through natural language.
      -
    • F
      license
      Not graded
      quality
      C
      maintenance
      Exposes tools from the Ecuro Light API for managing clinical appointments, patient records, and clinic availability. It enables users to perform healthcare management tasks such as scheduling, patient search, and report generation through MCP-compatible clients.
      -
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables AI agents to manage virtual clinic data including patients, visits, diagnoses, treatments, lab/radiology orders, and search medical literature and internal knowledge base.
      -
    • A
      license
      C
      quality
      D
      maintenance
      Exposes the Feegow clinic management API as MCP tools for agents, enabling scheduling, patient management, and more via natural language.
      97
      MIT