clinic-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@clinic-mcp-servershow my upcoming appointments"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
clinic-mcp-server
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 machineThe 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 |
| readonly | no | — |
| readonly | no | — |
| readonly | no | patient |
| booking | yes | patient |
| booking | yes (destructive) | patient |
| 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 |
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) |
|
end to end | Three tests run the whole path — MCP client → server → HTTP → an API on an ephemeral port — because everything above stubs |
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.jsSpeaks stdio, so it drops into any MCP client's config the usual way.
Variable | Default | Meaning |
|
|
|
|
| Where the API lives |
| (none) | The caller's token. No default, deliberately |
|
|
|
Related
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 toolslist_doctorsARead-onlyIdempotent
List doctors, optionally narrowed to one specialty. Returns each doctor's id, name, specialty and clinic address.
| Name | Required | Description | Default |
|---|---|---|---|
| specialty | No | Exact specialty name, as returned by list_specialties. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and non-destructive behavior, so the description's job is lighter. It adds useful behavioral context by specifying the return fields (id, name, specialty, clinic address) and the optional filtering behavior, though it does not mention pagination or result limits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is one sentence and conveys the core behavior, the optional filter, and the returned data in an efficient, front-loaded way. There is no wasted text or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only list operation, this description is complete: it states what is listed, how it can be filtered, and what fields are returned. The annotations cover safety and idempotency, and there are no nested objects or complex outputs requiring additional explanation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already provides 100% coverage for the single optional 'specialty' parameter, including that it must be an exact specialty name from list_specialties. The description reinforces the optional narrowing concept but does not add significant meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('List') and names the resource (doctors), while also noting the optional narrowing by specialty. This clearly distinguishes it from sibling tools list_specialties and list_my_appointments, which target different resources.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states that the tool can be narrowed by specialty and the schema parameter notes the exact specialty should come from list_specialties. It does not explicitly discuss when not to use this tool, but the read-only listing context and sibling names make the intended use clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_my_appointmentsARead-onlyIdempotent
List the appointments belonging to the authenticated patient. Returns id, status, doctor and slot time for each.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already convey that the operation is read-only, idempotent, and non-destructive. The description adds value by disclosing the exact return fields (id, status, doctor, slot time), which is especially helpful given there is no output schema. It goes beyond the annotation baseline without contradicting it.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single, front-loaded sentence with the action verb first, followed by the target resource and meaningful output details. No filler or redundant phrasing; every clause adds value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter read-only list tool, the description provides the essential context: ownership by the authenticated patient, the set of siblings it differs from, and the fields returned. Nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters and the schema coverage is 100%, so the description has no parameter burden to carry. The description appropriately focuses on the resource and result shape, meeting the baseline for a no-parameter tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('List'), a precise resource ('appointments belonging to the authenticated patient'), and the returned fields. It is clearly distinguished from the sibling tools list_specialties and list_doctors, which target different resources.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context: this tool is scoped to the authenticated patient's appointments, which naturally separates it from the sibling tools listing specialties and doctors. However, it does not explicitly name alternatives or provide when-not-to-use guidance, so it stops short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_specialtiesARead-onlyIdempotent
List the medical specialties this clinic staffs. Returns specialty names only. Takes no arguments.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the tool read-only, idempotent, and non-destructive. The description adds useful behavioral context beyond annotations by specifying that it 'returns specialty names only' and that the scope is 'this clinic staffs.' This is sufficient for a simple list operation, though it does not discuss extra details like ordering or authorization.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences contain exactly the necessary information: the action, the resource, the scope, the return content, and the absence of arguments. There is no filler or repetition, and the key purpose is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only list tool with no output schema, the description is fully adequate. It states what is returned, the scope, and that no arguments are required. Nothing essential is missing for an agent to invoke this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters and the schema fully reflects that with an empty properties object. The description reinforces this with 'Takes no arguments.' Since there are no parameters to document, the baseline of 4 applies and is fully satisfied.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource: 'List the medical specialties this clinic staffs.' It clearly distinguishes this tool from the siblings list_doctors and list_my_appointments by naming a distinct entity type. The scope is also explicit, so there is no ambiguity about what is listed.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The intended use is implied: call this tool when you need the clinic's specialty names. However, there is no explicit guidance about when to prefer it over sibling tools or when not to use it. For a zero-parameter, self-contained list tool, the implication is fairly clear, but no alternatives or exclusions are stated.
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.
3 tool updates
v1.0.0- First observed
list_doctors - First observed
list_my_appointments - First observed
list_specialties
TDQS
Scored across 3 tools
Each tool addresses a distinct resource: specialties, doctors, and appointments. There is no overlap or ambiguity between the three operations.
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.
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.
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
Related MCP Connectors
Read appointments, types, calendars and availability; create, cancel or reschedule bookings.
Calendar API for AI agents: events, availability, Google/Microsoft setup, scheduling, and iCal.
Book medical appointments with French doctors by specialty and city via AI agents.
Manage an EasyWeek business from AI: bookings, availability, customers, services, orders, messaging.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables 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.-
- FlicenseNot gradedqualityCmaintenanceExposes 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.-

vClinic MCP Serverofficial
FlicenseNot gradedqualityCmaintenanceEnables AI agents to manage virtual clinic data including patients, visits, diagnoses, treatments, lab/radiology orders, and search medical literature and internal knowledge base.-- AlicenseBqualityDmaintenanceExposes the ProDoctor Cloud API as MCP tools for AI agents to manage appointments, patients, users, and procedures.18MIT