AI-Assisted Clinical Trial Explorer built on MCP. Ingestion → Indexing → Retrieval → Forensic reporting.
mcp-bioforensics has 5 tools with inconsistent quality. Tool naming follows verb_noun convention (get_, list_, search_, execute_). Descriptions are present but vary widely in quality: ping (47 chars), list_datasets (98 chars), get_trial (107 chars), search_trials (480+ chars), execute_sql (350+ chars). All tools have input schemas with types and descriptions. However, several critical issues reduce the score: (1) execute_sql accepts free-form SQL strings, creating SQL injection risk without input validation or warnings in the description; (2) search_trials and execute_sql accept flexible payload formats (dict or JSON string) with complex internal parsing logic that isn't documented in the schema or descriptions; (3) output schemas are documented informally (in docstrings) but not formally in the tool schema; (4) error handling is minimal, tools return None or exceptions without recovery guidance; (5) no parameter validation or constraint enforcement visible in the code. The schema dimension is moderately strong (types and descriptions present) but output structures and error patterns need formalization.
Execute a SQL statement against the bioforensics.db SQLite database. Payload options (dict or JSON string): - sql / query: SQL string to execute (required) - params: optional dict/list for parameterized statements - commit: bool (default auto: True for mutating statements, False otherwise) - fetch: bool (default auto: True for read-only statements) - fetch_limit / limit: max rows to return when fetching (default 50). A plain string payload is treated as the SQL to run.
Get one clinical trial by trial_id (and optional dataset_id). Return structured trial metadata, or null if not found.
List all registered datasets with basic metadata: id, name, row count, ingestion time, and source path.
Return a small JSON confirming the server is alive and reachable.
Permissive search over trials via a single JSON `payload`. Payload (dict or JSON string): - query: str (required) - options: dict (optional) with keys {phase, disease, status, min_participants, top_k} - Top-level keys {phase, disease, status, min_participants, top_k} are also accepted and merged into `options`. Notes: - `phase` is normalized to canonical codes: PHASE1, PHASE2, PHASE3, PHASE4, EARLY_PHASE1, or combos like PHASE1|PHASE2, PHASE2|PHASE3. Variants like "Phase 3", "III" are accepted. - Returns a ranked list of trials with fields: dataset_id, trial_id, score, disease, phase, n_participants.
execute_sql exposes raw SQL execution without input sanitization or injection prevention. Description does not warn about SQL injection risk or recommend parameterized queries. Agents can pass arbitrary SQL strings.
search_trials and execute_sql accept flexible payload formats (object or JSON string) with complex internal parsing (_payload_to_dict, _parse_payload, _coerce_sql_payload). Schema declares type: ["object", "string"] but the description does not explain the dual-format support or parsing rules clearly. LLMs may pass invalid payloads.
No formal output schemas defined in tool registration. Tools return Python dicts with implicit field structures (e.g., search_trials returns {dataset_id, trial_id, score, disease, phase, n_participants}), but these are documented only in function docstrings, not in the tool schema. LLMs cannot discover output structure from the schema.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | D | 54 | 2026-07-28+ | v2 |
| 2026-03-09 | F | 0 | - | v1 |
Error handling is minimal. Tools return None (get_trial), raise unhandled exceptions (search_trials with invalid phase), or provide bare error responses. No recovery guidance provided to LLMs. Error messages do not follow pattern:recovery-guide (e.g., 'Trial not found. Try search_trials() with partial disease name.').
execute_sql description is unclear on which columns are returned or what the result structure is. It mentions 'fetch_limit / limit: max rows to return' but does not specify the output format (list of dicts, raw tuples, etc.).
search_trials contains undocumented behavior: phase normalization (_canon_phase) accepts many variants ('Phase 3', 'III', 'PHASE3', etc.) but this is not mentioned in the parameter description. LLMs may pass non-canonical forms and expect failure when actually they work.
execute_sql accepts a 'commit' parameter that defaults to auto-detection based on whether the query is mutating. This auto-behavior is not documented in the parameter description, leading to potential confusion about when changes are persisted.
get_trial returns null if not found but does not suggest alternatives or recovery steps. Per pattern:recovery-guide, the error response should hint at calling search_trials() to discover available trials.