Local-first MCP server that detects potential PHI flows into LLM, logging, and analytics calls in source code.
- ✓Open-source license (MIT)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Documented (README)
claude mcp add phi-guard-mcp -- npx -y phi-guard-mcp{
"mcpServers": {
"phi-guard-mcp": {
"command": "npx",
"args": ["-y", "phi-guard-mcp"]
}
}
}MCP Servers overview
# phi-guard-mcp
[](https://www.npmjs.com/package/phi-guard-mcp)
[](https://www.npmjs.com/package/phi-guard-mcp)
[](./LICENSE)
A local-first [MCP](https://modelcontextprotocol.io) server that catches PHI
(protected health information) flowing into LLM prompts, log statements, and
analytics calls — in your source code, before it ships.
The server itself makes no network calls: it runs as a local stdio process and
never uploads your code. What it returns is a different matter. Findings go
back to whatever MCP client launched it, so if that client is a hosted
assistant, the findings enter that model's context. Matched PHI values are
masked out of results by default for exactly this reason — see
[Security boundary](#security-boundary).
## Why
The risky moment in a healthcare codebase is rarely the database. It's the line
where a patient record gets interpolated into a prompt, a `console.log`, or an
analytics event. Those lines look harmless in review and never show up in
infrastructure scanning, because nothing is misconfigured — the code is just
doing what it says.
## Demo

Eleven seconds of the walkthrough: `scan_code` flags line 15 — a patient name
and diagnosis interpolated into an `openai.responses.create` call — and
`redact_suggest` masks the same values out of a raw prompt. Every value shown is
synthetic.
> A linter for one class of mistake. Not a HIPAA certification, not a compliance
> attestation, and not a dataflow analyzer — see
> [What this is NOT](#what-this-is-not).
## Install
Requires **Node.js 22 or newer**. The entrypoint uses JSON import attributes
(`with { type: "json" }`), so older runtimes will not start it. Verified on
Node 22.18.0, 24.2.0, and 26.8.2; Node 20 and below are unsupported and
untested.
### From npm (recommended)
Nothing to clone or build. Your MCP client runs it on demand:
```bash
npx -y phi-guard-mcp --version
```
### From source
```bash
git clone https://github.com/Abidit/phi-guard-mcp.git
cd phi-guard-mcp
npm ci
```
`npm ci` runs the `prepare` script, which builds `dist/`. There is no separate
build step to forget. `npm install` works too; `npm ci` is the reproducible one
because it installs exactly what `package-lock.json` pins.
## MCP configuration
Copy-paste one of the following. The npm form needs no paths and is the one to
hand to someone else.
### Claude Code
One command, project scope:
```bash
claude mcp add phi-guard -- npx -y phi-guard-mcp
```
Or commit a `.mcp.json` at your project root:
```json
{
"mcpServers": {
"phi-guard": {
"command": "npx",
"args": ["-y", "phi-guard-mcp"]
}
}
}
```
### Claude Desktop
`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS,
`%APPDATA%\Claude\claude_desktop_config.json` on Windows:
```json
{
"mcpServers": {
"phi-guard": {
"command": "npx",
"args": ["-y", "phi-guard-mcp"]
}
}
}
```
### Cursor
`.cursor/mcp.json` in the project, or `~/.cursor/mcp.json` globally:
```json
{
"mcpServers": {
"phi-guard": {
"command": "npx",
"args": ["-y", "phi-guard-mcp"]
}
}
}
```
### Running a local clone instead of the npm release
Point the client at the build output. Use an **absolute path** unless you are
certain your client launches the server with the project root as its working
directory:
```json
{
"mcpServers": {
"phi-guard": {
"command": "node",
"args": ["/absolute/path/to/phi-guard-mcp/dist/index.js"]
}
}
}
```
The `.mcp.json` committed in this repo uses the relative form (`dist/index.js`)
so the repo can dogfood its own server after `npm ci`.
Whichever form you use: restart the client, or run `/mcp` in Claude Code and
reconnect `phi-guard`. A rebuild alone will not reach an already-running stdio
process.
## Tools
### `redact_suggest`
Takes a raw text snippet — a log line, a prompt, an error message — detects
PHI-shaped values, and returns a redacted version alongside what it found.
**Input**
```json
{ "text": "Patient John Doe (MRN-12345), DOB: 01/01/1980" }
```
**Output**
```json
{
"redacted": "Patient [NAME] ([MRN]), [DOB]",
"detected": [
{ "type": "mrn", "confidence": 0.9, "start": 18, "end": 27 },
{ "type": "dob", "confidence": 0.85, "start": 30, "end": 45 },
{ "type": "name", "confidence": 0.8, "start": 8, "end": 16 }
]
}
```
The matched values are **not** echoed back by default, and neither is the
unredacted `original`. A tool result flows straight into the context of
whatever model called it, so repeating the raw PHI there would undo the point
of the tool. `start`/`end` are offsets into the original text, which is enough
to locate a match without restating it.
`detected` is ordered by pattern, not by position.
Pass `includeMatchedValues: true` when you genuinely need the raw values (a
local CLI, a test harness) and `detected[].value` plus `original` come back:
```json
{ "text": "Patient John Doe (MRN-12345)", "includeMatchedValues": true }
```
Patterns and their confidence scores:
| Type | Confidence | Matches |
| ------- | ---------- | ------- |
| `ssn` | 0.95 | `123-45-6789` |
| `mrn` | 0.90 | `MRN-12345`, `MRN: 12345` |
| `dob` | 0.85 | `DOB: 01/01/1980`, `born 3/14/75` |
| `name` | 0.80 | `Patient John Doe` (captures `John Doe`) |
| `phone` | 0.75 | `555-867-5309`, `(555) 867 5309` |
| `email` | 0.70 | `jane.roe@example.com` |
That table is the complete list. The patterns start deliberately narrow — a
false positive that trains someone to ignore the tool is worse than a missed
match.
### `scan_code`
Walks a directory and flags lines where a sensitive-looking identifier appears
on the same line as a risky sink.
**Sensitive identifiers** — the complete list:
`patient`, `diagnosis`, `dob`, `ssn`, `mrn`, `birthdate`, `medicalrecord`
Matching is case-insensitive and bounded by non-letters, so `patient_name`
matches while `outpatient` and `inpatient` do not.
**Sink categories** — the complete list:
| Category | Matches |
| -------- | ------- |
| LLM providers | `openai`, `anthropic`, `bedrock` |
| Console | `console.log`, `console.error`, `console.warn` |
| Loggers | `logger.`, `winston`, `pino` |
| Analytics | `.track(` |
| Error reporting | `capture(`, `captureException(`, `captureMessage(` |
**Languages scanned** — the complete list:
| Extension | Language |
| --------- | -------- |
| `.ts`, `.tsx` | TypeScript |
| `.js`, `.jsx` | JavaScript |
| `.py` | Python |
| `.go` | Go |
Detection is purely lexical, so language support means "these file extensions
are read". There is no parser and no type information for any of them.
Skips `node_modules`, `dist`, `build`, `coverage`, `out`, `.next`, `.turbo`,
and any dotfile or dot-directory. Whole-line `//` and `#` comments are skipped,
so a file that discusses PHI handling in prose doesn't trip the scanner on its
own documentation.
Given the operative lines of
[`test/fixtures/leaky-example.ts`](test/fixtures/leaky-example.ts):
```ts
const prompt = await openai.responses.create({ input: `Patient: ${patient.name}, diagnosis: ${patient.diagnosis}` });
console.log("Sending patient prompt to LLM:", prompt);
```
**Input**
```json
{ "path": "/abs/path/to/repo/test/fixtures" }
```
**Output** — excerpt. That directory returns **9** findings in total: 2 from
this file, and 7 from the positive fixtures described under
[Evaluation](#evaluation).
```json
[
{
"file": "/abs/path/to/repo/test/fixtures/leaky-example.ts",
"line": 7,
"severity": "high",
"issue": "Sensitive-looking identifier passed to a risky sink (LLM call, logger, or analytics)",
"snippet": "const prompt = await openai.responses.create({ input: `Patient: ${patient.name}, diagnosis: ${patient.diagnosis}` });"
},
{
"file": "/abs/path/to/repo/test/fixtures/leaky-example.ts",
"line": 8,
"severity": "high",
"issue": "Sensitive-looking identifier passed to a risky sink (LLM call, logger, or analytics)",
"snippet": "console.log(\"Sending patient prompt to LLM:\", prompt);"
}
]
```
`file` is built by joining `path` with the entry name, so it comes back in
whatever form you passed in: absolute in, absolute out. `severity` is always
`"high"` — there is one rule, so there is one severity.
`snippet` is the offending line with any literal PHI masked, for the same
reason `redact_suggest` withholds matched values: the finding is going into a
model's context. Identifier names like `patient.diagnosis` are not literal
values, match no PHI pattern, and stay visible — they are the actionable part.
An empty array means every eligible source file discovered under `path` was
read successfully and no line matched both conditions. "Eligible" and
"discovered" are load-bearing: files with an unsupported extension, and
anything under a skipped or dot-prefixed directory, are never opened.
A scan that cannot read a directory or a file **fails** with a named error
rather than returning a shorter list, because a partial result reads as a
clean result:
```
No such path: "/nope". Pass an absolute path to a directory that exists on the
machine running this server. (ENOENT: no such file or directory, stat '/nope')
```
### The same-line rule
Both conditions must hold **on the same physical line**. This is the single
most important thing to understand about the scanner, in both directions.
It is what keeps it quiet. On this repo's own `src/` — which is dense with the
words `patient`, `diagnosis`, `mrn`, and `ssn` inside its pattern definitions —
it reports zero findings.
It is also the main reason it misses things. This leaks and is **not** flagged:
What people ask about phi-guard-mcp
What is Abidit/phi-guard-mcp?
+
Abidit/phi-guard-mcp is mcp servers for the Claude AI ecosystem. Local-first MCP server that detects potential PHI flows into LLM, logging, and analytics calls in source code. It has 0 GitHub stars and its last recorded update is dated 2026-09-18.
How do I install phi-guard-mcp?
+
You can install phi-guard-mcp by cloning the repository (https://github.com/Abidit/phi-guard-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is Abidit/phi-guard-mcp safe to use?
+
Our security agent has analyzed Abidit/phi-guard-mcp and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.
Who maintains Abidit/phi-guard-mcp?
+
Abidit/phi-guard-mcp is maintained by Abidit. The last recorded GitHub activity is dated 2026-09-18, with 0 open issues.
Are there alternatives to phi-guard-mcp?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy phi-guard-mcp to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/abidit-phi-guard-mcp)<a href="https://claudewave.com/repo/abidit-phi-guard-mcp"><img src="https://claudewave.com/api/badge/abidit-phi-guard-mcp" alt="Featured on ClaudeWave: Abidit/phi-guard-mcp" width="320" height="64" /></a>More MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ
The fastest path to AI-powered full stack observability, even for lean teams.