Reference implementation — a guardrail-wrapped Model Context Protocol server for regulated-bank application security
Let AI assistants drive the bank’s AppSec scanning ecosystem — Checkmarx, Black Duck, Invicti, Corgea — through one server, with security controls wired into every call: audit, per-app authorization, credential isolation, PII redaction, and a human checkpoint before any action.
Application-security backlog scales with the number of applications, not the size of the AppSec team. The traditional answer — wire each scanner into each team’s pipeline by hand — is an N teams × M scanners × P applications integration cost that has to be maintained forever.
At the same time, AI assistants are arriving in developer workflows fast, and banks are — rightly — nervous about handing an autonomous agent the keys to security tooling, source code, and scanner credentials. The hard part isn’t calling a scanner from an LLM. The hard part is doing it in a way that survives an audit.
A single Model Context Protocol (MCP) server that exposes the AppSec scanning ecosystem as a unified, LLM-callable tool surface. Any client — a developer’s IDE, an internal triage agent, Claude Desktop or Claude Code — calls one server. The server orchestrates the right scanner, normalizes the findings, retrieves the bank’s own policy as grounding, and surfaces patches.
Every tool call is wrapped by an ordered stack of security guardrails before anything executes. The thesis: you can give engineers AI-assisted security tooling without loosening a single control. Build the server once; onboard hundreds of apps through a thin manifest.
One server, three transports, eight tools, five guardrails, three pluggable LLM providers, RAG over the bank’s policy catalog, and an end-to-end OpenTelemetry observability plane.
LLM clients → guardrail-wrapped tool runtime → scanners, RAG & LLM providers — every call observable end to end.
Middleware wraps every tool call. The guardrails run in order — an action never reaches a scanner or an LLM until each one has passed.
One NDJSON record per call — caller, tool, app, latency, outcome. The trace ID is the audit-log correlation key.
Per-app authorization re-checked at every tool entry, not just at the gateway. Calling a tool for an app you’re not authorized on returns 403, not a filtered result.
Scanner secrets are pulled from the secret store at call time and never enter LLM context, logs, or trace attributes.
SSN / PCI / email scrubbed before anything reaches an LLM. Every hit emits a metric — a spike is itself a finding.
Any actionable output is flagged requires_approval=true. The server never auto-suppresses, auto-closes, or auto-merges. Agentic ≠ autonomous.
A deliberately bounded surface. The server exposes only the AppSec tools it explicitly catalogs — no shell, no arbitrary code execution.
checkmarx_scanSAST scan orchestrationblackduck_scanSCA / dependency scaninvicti_scanDAST scan orchestrationcorgea_triageLLM-assisted false-positive triagefinding_listNormalized findings queryfinding_patch_suggestSmallest-patch suggestionapp_onboardManifest-driven app onboardingapp_statusPer-app posture & scan stateIn this reference implementation the scanners are FastAPI mock stubs that honor the same adapter contract a production Checkmarx / Black Duck / Invicti / Corgea integration would — so the orchestration, normalization, and guardrail logic is exercised end to end without live vendor credentials.
MCP transports define how JSON-RPC messages travel between client and server. The server speaks more than one so it can serve both local and networked clients.
The client launches the server as a local child process and talks over stdin/stdout. No network, no ports — the lowest attack surface. Right for desktop and IDE clients on the same machine (Claude Desktop, Claude Code).
Server-Sent Events was MCP’s original remote transport: a network HTTP service with a persistent stream plus a separate POST endpoint. Still supported for backward compatibility, but deprecated as of the 2025-03-26 spec.
The current recommended remote transport: a single endpoint, bidirectional, and friendly to load balancers and firewalls. New networked deployments target this; one server can bind both stdio and Streamable HTTP, gated by a flag.
Redaction is a middleware step that runs before any boundary — LLM context, logs, or span attributes. Scan findings carry source code, so this is not optional. It works in three layers:
Deterministic patterns for well-formed sensitive data: SSNs, payment-card PANs (Luhn-validated to cut false hits), emails, API keys and high-entropy strings. Cheap, and catches the bulk.
An NER / classifier pass for what patterns miss — names, addresses, and data classes embedded in code comments or test fixtures.
Matches are masked or tokenized (not deleted, so the finding still reads coherently for triage), and every hit emits a metric. That count is its own security signal.
The server is not locked to one AI vendor. A single LLMProvider interface (complete() / embed()) sits in front of three interchangeable implementations, so the model is a configuration decision — pinned and model-risk-tracked — not a hard dependency.
Fully on-premise / open-weight serving. The default for a regulated bank where source-code findings must never leave the boundary.
Hosted option behind the same interface, available where an approved enterprise tenant and data-handling terms permit.
Hosted Claude models behind the same interface, swappable without touching tool logic.
Triage decisions can’t hallucinate at a bank. Rather than trusting a general model’s judgment on a domain-specific call, the server retrieves the bank’s actual policies, suppression history, and per-app findings and feeds the most relevant policy docs into the prompt — with outputs that cite the retrieved evidence for a human to verify.
| Resource | Role in the RAG knowledge base |
|---|---|
appsec://apps/{id} | Per-application inventory & metadata |
appsec://policies | Policy catalog — the source of truth for triage |
appsec://suppressions | Suppression registry with owner & expiry |
appsec://findings/{app} | Normalized prior findings per app |
Retrieval is backed by a lightweight local SQLite + numpy store — the resource surface is the knowledge base.
Every tool call emits telemetry over OpenTelemetry. Steady-state runs at a 5% sample by default; an X-Debug-Request header flips a single request to 100% capture with DEBUG logging — cheap monitoring plus full forensic detail on demand.
Spans, metrics and logs emitted via the SDK’s on-demand sampler, shipped to an OTLP collector on :4317.
Traces land in Tempo, logs in Loki. The span’s trace ID is the key that ties a request to its audit record.
Four dashboards on :3000 — latency, throughput, redaction hits, and per-tool error rates.
The controls are the point, not an afterthought. Design choices map directly to a financial-services posture:
FastMCPPythonstdio / Streamable HTTP SQLite + numpy (RAG)OllamaOpenAIAnthropic FastAPI (scanner stubs)Secrets Manager OpenTelemetry / OTLPTempoLokiGrafana
Interested in MCP-driven AI orchestration for security tooling, or applying these guardrail patterns in a regulated environment?
Platform & AI-Augmented Application Security