script enforces the pipeline programmatically.
Architecture Decisions and Rationale
- stdio Transport: The MCP specification supports multiple transports. stdio is chosen for local agent setups because it requires no network configuration, inherits process-level sandboxing, and aligns with Claude Desktop's native tool discovery mechanism.
- Composable over Monolithic: Bundling all checks into a single server creates coupling. A vulnerability in the injection detector would force a coordinated release of the PII and cost modules. Separation enables independent auditing, selective disabling, and targeted scaling.
- Process Isolation: Each guardrail runs in its own Node.js runtime. Memory leaks or unhandled exceptions in one filter cannot corrupt the Notion MCP server or the host application. This matches the Unix philosophy of small, focused utilities.
- Read vs Write Path Separation: Security requirements differ between ingestion and generation. Read filters sanitize incoming data before it reaches the context window. Write filters scrub LLM output before it commits to the knowledge base. The architecture enforces this boundary explicitly.
Implementation Pattern: TypeScript Guardrail Server
Below is a production-ready template for a single guardrail server using the official MCP TypeScript SDK. This example implements a secret-scanning filter. The structure is identical for PII redaction, injection detection, and output sanitization; only the transformation logic changes.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Secret detection regex patterns (production would use a maintained library)
const SECRET_PATTERNS = [
/AKIA[0-9A-Z]{16}/, // AWS Access Key
/ghp_[0-9a-zA-Z]{36}/, // GitHub Personal Access Token
/xox[baprs]-[0-9a-zA-Z-]+/, // Slack Token
/sk-[0-9a-zA-Z]{48}/, // OpenAI/Stripe Key
];
const server = new McpServer({
name: "credential-guard",
version: "1.0.0",
});
server.tool(
"scan_and_redact",
"Scans input text for hardcoded credentials and returns a redacted version with findings.",
{
raw_content: z.string().describe("Unsanitized text from knowledge base"),
},
async ({ raw_content }) => {
const findings: string[] = [];
let sanitized = raw_content;
for (const pattern of SECRET_PATTERNS) {
const matches = raw_content.match(pattern);
if (matches) {
findings.push(`Detected credential pattern: ${matches[0].slice(0, 8)}...`);
sanitized = sanitized.replace(pattern, "[REDACTED_CREDENTIAL]");
}
}
return {
content: [
{
type: "text",
text: JSON.stringify({
status: findings.length > 0 ? "redacted" : "clean",
findings,
sanitized_content: sanitized,
}),
},
],
};
}
);
async function main() {
const transport = new StdioTransport();
await server.connect(transport);
console.error("credential-guard MCP server initialized");
}
main().catch(console.error);
Orchestration Strategy
The host application (Claude Desktop, Cursor, or a custom agent runtime) discovers all registered tools. The LLM can be prompted to chain them explicitly:
1. Call credential-guard.scan_and_redact on the raw page content.
2. If status is 'redacted', log findings and proceed with sanitized_content.
3. Pass the result to pii-masker.redact for personal data handling.
4. Use the final output as your working context.
For stricter enforcement, a wrapper script can intercept tool calls and apply the pipeline deterministically, removing reliance on LLM compliance. This is recommended for enterprise deployments where audit trails are mandatory.
Pitfall Guide
1. Assuming Redaction is Irreversible
Explanation: LLMs can sometimes reconstruct masked values from context clues or partial patterns. Simple regex replacement does not guarantee cryptographic security.
Fix: Use token-mapping redaction for sensitive workflows. Store the original-to-masked mapping in a secure, ephemeral cache. Restore values only when the agent needs to perform a write operation, and purge the cache after the session.
2. Stdio Buffer Overflow on Large Pages
Explanation: Notion databases can contain pages exceeding 100KB of markdown. stdio pipes have finite buffer limits. Feeding unchunked payloads can cause silent truncation or process crashes.
Fix: Implement streaming chunking in the guardrail server. Split payloads at logical boundaries (headings, paragraphs) and process them sequentially. Return aggregated results only after all chunks are sanitized.
3. Prompt-Dependent Guardrail Execution
Explanation: Relying on the LLM to remember to call security tools introduces human error. Agents may skip filters under time pressure or when context windows are full.
Fix: Enforce the pipeline at the transport layer. Use a middleware wrapper that intercepts tools/call requests and routes them through the guardrail chain before forwarding to the inference engine. This removes agent discretion from security-critical paths.
4. Ignoring the Write Path
Explanation: Teams often secure reads but leave writes unfiltered. Generated code, HTML, or SQL injected into Notion pages can persist indefinitely and execute in downstream integrations.
Fix: Deploy an output sanitizer as the final step in the write pipeline. Strip executable tags, validate markdown structure, and enforce allowlists for embedded scripts. Log all write attempts for compliance auditing.
5. Static Budget Caps Without Context Awareness
Explanation: Hard token limits fail when agents traverse linked databases or summarize long-running threads. A flat cap may terminate sessions mid-workflow, causing data loss.
Fix: Implement dynamic budgeting. Track cumulative token consumption across turns. Set warning thresholds at 70% and 90% of the cap. Allow graceful degradation (e.g., switching to a cheaper model or requesting user confirmation) instead of abrupt termination.
Explanation: Multiple guardrail servers may expose similarly named tools (e.g., sanitize, clean, filter). The LLM may invoke the wrong one, causing inconsistent behavior.
Fix: Namespace tools explicitly. Use prefixes like guard.pii.redact, guard.injection.detect, guard.output.scrub. Document the exact invocation order in the system prompt or wrapper configuration.
7. Missing Health and Liveness Checks
Explanation: stdio processes can hang or exit silently. If a guardrail server crashes, the pipeline fails open or blocks indefinitely.
Fix: Implement a heartbeat mechanism. Each guardrail server should expose a health.check tool that returns process uptime and memory usage. The host application should poll this endpoint periodically and restart failed processes automatically.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Small team, internal docs only | Direct Notion MCP + credential-guard + cost-guard | Low compliance overhead; focuses on accidental secret exposure and budget control | Minimal; adds ~250MB node_modules |
| Customer-facing knowledge base | Full guardrail stack + injection-detector + pii-masker | External content carries high injection and PII risk; requires strict read-path sanitization | Moderate; increases latency per read due to multi-step pipeline |
| Enterprise compliance (SOC 2/ISO) | Wrapper-enforced pipeline + audit-logger + change-review | Deterministic execution removes agent discretion; audit trails satisfy regulatory requirements | High; requires infrastructure for logging, approval workflows, and process monitoring |
| Cost-sensitive research workflows | Dynamic budgeting + context-window limiter + model fallback | Prevents runaway API spend during deep database traversal; maintains workflow continuity | Low to moderate; optimizes token usage without blocking access |
Configuration Template
Copy this structure into your host application's MCP configuration file. Replace placeholder values with your environment variables.
{
"mcpServers": {
"notion-source": {
"command": "npx",
"args": ["-y", "@notionhq/notion-mcp-server"],
"env": {
"NOTION_INTEGRATION_TOKEN": "ntn_..."
}
},
"credential-guard": {
"command": "npx",
"args": ["-y", "@yourorg/credential-guard-mcp"]
},
"pii-masker": {
"command": "npx",
"args": ["-y", "@yourorg/pii-masker-mcp"],
"env": {
"PII_REDACTION_MODE": "token-map",
"PII_CACHE_TTL_SECONDS": "300"
}
},
"injection-detector": {
"command": "npx",
"args": ["-y", "@yourorg/injection-detector-mcp"],
"env": {
"INJECTION_CONFIDENCE_THRESHOLD": "0.6"
}
},
"text-normalizer": {
"command": "npx",
"args": ["-y", "@yourorg/text-normalizer-mcp"]
},
"output-scrubber": {
"command": "npx",
"args": ["-y", "@yourorg/output-scrubber-mcp"],
"env": {
"SCRUB_HTML_TAGS": "script,iframe,object",
"SCRUB_SHELL_PATTERNS": "true"
}
},
"cost-guard": {
"command": "npx",
"args": ["-y", "@yourorg/cost-guard-mcp"],
"env": {
"MAX_TOKENS_PER_SESSION": "120000",
"MAX_DOLLARS_PER_SESSION": "4.50",
"WARNING_THRESHOLD_PERCENT": "75"
}
}
}
}
Quick Start Guide
- Install the Notion MCP server: Run
npx @notionhq/notion-mcp-server --help to verify connectivity. Generate an integration token in your Notion workspace settings and assign it to the NOTION_INTEGRATION_TOKEN environment variable.
- Deploy guardrail utilities: Install each filtering server via npm. Verify stdio connectivity by running
echo '{"jsonrpc":"2.0","id":1,"method":"initialize"}' | npx @yourorg/credential-guard-mcp and confirming a valid JSON-RPC response.
- Configure the host runtime: Paste the configuration template into your application's MCP settings file. Restart the host to trigger tool discovery. Verify that all guardrail tools appear in the available tools list alongside Notion MCP.
- Test the pipeline: Query a page containing known test data (e.g.,
AKIAIOSFODNN7EXAMPLE or test@example.com). Confirm that the credential-guard and pii-masker tools intercept the payload, return redacted output, and log findings without exposing raw values to the context window.
- Enforce execution order: Add a system prompt or wrapper script that mandates the read pipeline:
credential-guard β pii-masker β injection-detector β text-normalizer. Validate that writes route through output-scrubber before committing to Notion. Monitor cost-guard logs to ensure budget thresholds trigger correctly.