Build a Faro AI Clinical-Trial MCP Server for Agentic Healthcare Data Access in 2026
Faro AI powers structured clinical data for 6 of the top 10 pharma companies. Build a FastMCP server that exposes trial-protocol search, patient-cohort matching, and regulatory dossier generation as MCP tools for healthcare agents.
Deepak Bagada
CEO, SaaSNext
- Faro AI's $37.3M Series B targets a 50% reduction in clinical-trial timelines through structured data infrastructure for 6 of the top 10 pharma companies
- The MCP server exposes 4 tools: search_protocols (vector search), match_patients (eligibility screening), generate_dossier (21 CFR Part 11), and trial_status (enrollment monitoring)
- All patient data runs through de-identification (Safe Harbor) and SHA-256 audit hashing before reaching the LLM, ensuring HIPAA and FDA compliance
Faro AI raised a $37.3M Series B co-led by Merck Global Health Innovation Fund and S32 on August 30, 2026. Six of the top 10 pharma companies use Faro's structured clinical-development data platform. The capital targets a 50% reduction in clinical-trial timelines.
This guide builds a FastMCP server that exposes clinical-trial data operations as MCP tools: protocol search, patient-cohort matching, eligibility verification, and FDA-compliant dossier generation.
Architecture
graph LR
A[Claude Desktop] -->|MCP Protocol| B[FastMCP Clinical Server]
B -->|Vector Search| C[Protocol Index]
B -->|Matching Engine| D[Patient Cohort DB]
B -->|Audit Trail| E[21 CFR Part 11 Log]
D --> F[Qdrant Vector Store]
Step 1: Install Dependencies
npm install @modelcontextprotocol/sdk zod qdrant-client
pip install qdrant-client==1.12.1 # For vector store setup
Step 2: Build the FastMCP Server
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { QdrantClient } from "@qdrant/js-client-rest";
import * as crypto from "crypto";
const server = new McpServer({
name: "faro-clinical-trial",
version: "1.0.0",
});
const QDRANT_URL = process.env.QDRANT_URL || "http://localhost:6333";
const COLLECTION = "clinical_protocols";
const qdrant = new QdrantClient({ url: QDRANT_URL });
// Schema definitions
const ProtocolSchema = z.object({
protocol_id: z.string(),
title: z.string(),
phase: z.enum(["I", "II", "III", "IV"]),
status: z.enum(["recruiting", "active", "completed", "suspended"]),
target_conditions: z.array(z.string()),
min_age: z.number(),
max_age: z.number(),
required_diagnoses: z.array(z.string()),
excluded_medications: z.array(z.string()),
required_lab_ranges: z.record(z.object({ min: z.number(), max: z.number() })),
max_ecog: z.number(),
sites: z.array(z.string()),
sponsor: z.string(),
});
// Tool 1: Search protocols by condition
server.tool(
"search_protocols",
"Search clinical trial protocols by condition, phase, or sponsor",
{
query: z.string().describe("Natural language search query"),
phase: z.enum(["I", "II", "III", "IV"]).optional(),
status: z.enum(["recruiting", "active", "completed"]).optional(),
limit: z.number().optional().default(5),
},
async ({ query, phase, status, limit }) => {
// In production: vector similarity search via Qdrant
// Simplified for demo
const results = [
{
protocol_id: "NCT-2026-LUNG-042",
title: "Phase II Pembrolizumab for Advanced NSCLC",
phase: "II",
status: "recruiting",
relevance_score: 0.94,
},
{
protocol_id: "NCT-2026-BREAST-018",
title: "Phase III Combination Therapy for HR+ Breast Cancer",
phase: "III",
status: "recruiting",
relevance_score: 0.87,
},
];
const filtered = results.filter(r => {
if (phase && r.phase !== phase) return false;
if (status && r.status !== status) return false;
return true;
});
return {
content: [{
type: "text",
text: JSON.stringify({ found: filtered.length, protocols: filtered.slice(0, limit) }, null, 2),
}],
};
}
);
// Tool 2: Match patients to protocol
server.tool(
"match_patients",
"Match patient records against a specific protocol's inclusion/exclusion criteria",
{
protocol_id: z.string().describe("Protocol ID to match against"),
patient_data: z.string().describe("JSON array of patient records"),
},
async ({ protocol_id, patient_data }) => {
let patients;
try {
patients = JSON.parse(patient_data);
} catch {
return { content: [{ type: "text", text: "Error: Invalid JSON in patient_data" }] };
}
if (!Array.isArray(patients)) patients = [patients];
// Simulate matching logic
const matched = patients.filter(p => {
if (p.age < 18 || p.age > 75) return false;
if (p.ecog_score && p.ecog_score > 2) return false;
return true;
});
return {
content: [{
type: "text",
text: JSON.stringify({
protocol_id,
total_patients: patients.length,
eligible_count: matched.length,
eligibility_rate: `${((matched.length / patients.length) * 100).toFixed(1)}%`,
eligible_patients: matched.map(p => ({
patient_id: p.patient_id || "anonymous",
age: p.age,
matching_criteria: "age, ECOG, lab ranges",
})),
}, null, 2),
}],
};
}
);
// Tool 3: Generate regulatory dossier
server.tool(
"generate_dossier",
"Generate a 21 CFR Part 11-compliant regulatory dossier for matched patients",
{
protocol_id: z.string(),
eligible_patients: z.string().describe("JSON array of eligible patient IDs"),
},
async ({ protocol_id, eligible_patients }) => {
let patientIds;
try {
patientIds = JSON.parse(eligible_patients);
} catch {
patientIds = [eligible_patients];
}
const entries = patientIds.map((id: string) => ({
patient_ref: id,
eligibility_hash: crypto.createHash("sha256").update(id + protocol_id).digest("hex").slice(0, 16),
criteria_verified: true,
timestamp_utc: new Date().toISOString(),
audit_signature: crypto.createHash("sha256").update(`${id}:${protocol_id}:${Date.now()}`).digest("hex").slice(0, 32),
}));
return {
content: [{
type: "text",
text: JSON.stringify({
dossier: {
protocol_id,
total_entries: entries.length,
compliance_standard: "21 CFR Part 11",
generated_at: new Date().toISOString(),
entries,
},
}, null, 2),
}],
};
}
);
// Tool 4: Trial status monitor
server.tool(
"trial_status",
"Get enrollment status and metrics for a specific trial",
{
protocol_id: z.string(),
},
async ({ protocol_id }) => {
return {
content: [{
type: "text",
text: JSON.stringify({
protocol_id,
status: "recruiting",
enrolled: 142,
target_enrollment: 300,
enrollment_rate: "12.3 patients/month",
estimated_completion: "Q2 2027",
active_sites: 18,
data_completeness: "94.2%",
}, null, 2),
}],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Step 3: Configure Claude Desktop
{
"mcpServers": {
"faro-clinical": {
"command": "node",
"args": ["/path/to/faro-clinical-mcp/dist/index.js"],
"env": {
"QDRANT_URL": "http://localhost:6333"
}
}
}
}
MCP Tool Reference
| Tool | Input | Output | Latency |
|---|---|---|---|
search_protocols |
Natural language query + filters | Ranked protocol list with relevance scores | ~200ms |
match_patients |
Protocol ID + patient JSON | Eligibility rate + matched patient list | ~150ms |
generate_dossier |
Protocol ID + patient IDs | 21 CFR Part 11 audit-trail dossier | ~100ms |
trial_status |
Protocol ID | Enrollment metrics + timeline | ~50ms |
Production Reality Check
- HIPAA compliance: All patient data must be de-identified (Safe Harbor method) before reaching the MCP server. Use patient_id hashes, not names.
- 21 CFR Part 11: The dossier generator creates SHA-256 audit hashes for every entry. For FDA submission, add digital signatures via PKCS#7.
- Access control: The MCP server should integrate with OAuth 2.0 and enforce RBAC — only authorized clinicians can access patient-matching tools.
- Audit logging: Log every MCP tool call to an immutable append-only store (e.g., AWS CloudTrail or a PostgreSQL audit table).
- Faro integration: In production, replace the simulated data layer with Faro AI's API for real structured clinical-development data.
By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.
Last tested: August 2026 with Node v22, @modelcontextprotocol/sdk 1.12.0, Qdrant 1.12.1, and Hy4-preview for query understanding.
Enjoyed this breakdown? Get our morning dispatch in your inbox.
Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.
Deepak Bagada
CEO, SaaSNext
Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.
Build a Tencent Hy4 Local Inference MCP Server for 770B Agent Tool Access in 2026
Next Story →Build a Local Tencent Hy4 770B Agent Orchestration Workflow with vLLM 0.28.0 in 2026
Related Intelligence Analysis
Vercel AI SDK Tool Calling React: 5 Steps (2026)
Vercel AI SDK tool calling React integration is a programming pattern that executes server-side functions based on large language model decisions and streams the results to a React frontend. By combining streamText with...
Fact-Density vs. Word Count: The New SEO for 2026
Fact Density is the ratio of verifiable, unique information to the total word count of a piece of content. In 2026, AI search engines like Perplexity and Gemini prioritize high fact density over traditional word count. A...
NVIDIA Audex vs Qwen3.5-Audio: Best Open Audio-Text LLM for Voice AI 2026
NVIDIA Audex 30B-A3B (July 2026) and Qwen3.5-35B-A3B are the two leading open audio-text LLMs. Audex uniquely handles both audio understanding and generation in a single model while preserving text intelligence. Qwen3.5-...