Security & Authentication
The Y.A.N.I. MCP Server implements defense-in-depth security across authentication, data sanitization, rate limiting, and audit logging. Designed for fintech compliance (ACPR/AMF) and RGPD requirements.
Security Model
The security architecture is built on three pillars that work together to protect user data and ensure regulatory compliance. Every request passes through multiple layers before reaching the database.
Authentication
Transport-specific auth: process isolation for stdio, API key Bearer tokens for HTTP/SSE, rate limiting for REST API
Data Sanitization
Email-based user resolution, truncated addresses, never-exposed fields (S3 URLs, publicKeys, IPs), strict Prisma select clauses
Rate Limiting & Logging
100 req/min/IP with standard headers, daily-rotated JSON logs, no sensitive data in log entries
┌──────────────────────────────────────────────────────────────┐
│ AI Client Request │
└───────────────────────────┬──────────────────────────────────┘
│
┌───────────▼───────────┐
│ Transport Layer │
│ │
│ stdio: process-level │
│ HTTP: API key auth │
│ REST: rate limiting │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ Request Logging │
│ JSON format, daily │
│ rotation, sanitized │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ User Resolution │
│ email → user (safe │
│ fields only via │
│ Prisma select) │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ Tool Execution │
│ Per-tool sanitize │
│ rules applied to │
│ response data │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ Sanitized Response │
│ No fileUrl, no │
│ publicKey, truncated │
│ addresses, no PII │
└───────────────────────┘Authentication
Each transport has its own authentication mechanism tailored to its threat model. Authentication is enforced at the middleware level before any tool execution.
stdio Transport (Claude Code)
The stdio transport requires no additional authentication. Security is provided by process-level isolation: only the parent process (Claude Code) can communicate with the MCP server through stdin/stdout pipes. No network surface is exposed.
Safest transport
No network exposure, no API keys to manage. The parent process (Claude Code) has exclusive access through OS-level pipe isolation. This is the recommended transport for local development and Claude Code integrations.
{
"mcpServers": {
"yani-advisor": {
"command": "npx",
"args": ["tsx", "mcp-server-yani/src/index.ts"],
"cwd": "/path/to/yanipay-animation",
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost:5432/yanipay"
}
}
}
}HTTP/SSE Transport (Port 3100)
The HTTP/SSE transport uses API key authentication via the MCP_API_KEY environment variable. When set, all requests to /mcp must include a valid Bearer token. The /health endpoint remains open for monitoring.
const MCP_API_KEY = process.env.MCP_API_KEY;
function apiKeyAuth(req: Request, res: Response, next: NextFunction): void {
if (!MCP_API_KEY) {
// No key configured = open access (dev mode)
next();
return;
}
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.status(401).json({
jsonrpc: '2.0',
error: { code: -32001, message: 'Missing Authorization: Bearer <key>' },
id: null,
});
return;
}
const token = authHeader.slice(7);
if (token !== MCP_API_KEY) {
res.status(403).json({
jsonrpc: '2.0',
error: { code: -32002, message: 'Invalid API key' },
id: null,
});
return;
}
next();
}Auth behavior by endpoint
| Endpoint | Method | Auth Required | Purpose |
|---|---|---|---|
| /health | GET | No | Health check and monitoring |
| /mcp | POST | Yes (Bearer) | MCP JSON-RPC requests |
| /mcp | GET | Yes (Bearer) | SSE stream for server-initiated messages |
| /mcp | DELETE | Yes (Bearer) | Terminate MCP session |
# Set your API key
export MCP_API_KEY="your-secret-api-key-here"
# Start the HTTP server
MCP_API_KEY=$MCP_API_KEY npx tsx src/server-http.ts
# Authenticated request (from client)
curl -X POST http://localhost:3100/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-secret-api-key-here" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "yani_get_balance",
"arguments": { "email": "user@example.com" }
},
"id": 1
}'Dev mode warning
When MCP_API_KEY is not set, the /mcp endpoint is open to all requests without authentication. This is intentional for local development only. Always set MCP_API_KEY in production.
REST API Transport (Port 3101)
The REST API transport relies on rate limiting as its primary protection mechanism, using express-rate-limit at 100 requests per minute per IP address. It is designed to be placed behind an authenticated reverse proxy in production.
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute window
max: 100, // 100 requests per IP per window
standardHeaders: true, // RateLimit-* headers (RFC draft)
legacyHeaders: false, // Disable X-RateLimit-* headers
message: { error: 'Too many requests, please try again later.' },
});
app.use(limiter);Data Sanitization
Data sanitization is the most critical security layer. The MCP server never exposes raw database records to AI clients. Every tool applies strict field selection and transformation rules to prevent leaking sensitive information through LLM tool responses.
Email-Based User Resolution
All platform tools identify users by email address, never by internal UUID. The resolveUser() function acts as the security gate, returning only a safe subset of user fields through a strict Prisma select clause.
/**
* Resolve a user by email address.
* This is the security gate: all platform tools identify users by email,
* never by internal UUID. This ensures auditability and prevents
* direct ID enumeration.
*/
export async function resolveUser(email: string) {
const db = getDb();
const user = await db.user.findUnique({
where: { email },
select: {
id: true, // Internal use only, not exposed in responses
email: true, // Safe: provided by the caller
name: true, // Safe: display name
firstName: true, // Safe: display name
lastName: true, // Safe: display name
role: true, // Safe: non-sensitive enum
kycStatus: true, // Safe: status enum
createdAt: true, // Safe: non-sensitive timestamp
},
});
if (!user) throw new UserNotFoundError(email);
return user;
}Exposed Fields
email- Provided by callername- Display namefirstName- First namelastName- Last namerole- User role enumkycStatus- KYC status enumcreatedAt- Account creation date
Never Exposed
passwordHash- Authenticationphone- Personal contactaddress- Physical locationdateOfBirth- IdentitysocialSecurityNumber- Government IDbankAccountDetails- Financialsessions- Active sessions
Per-Tool Sanitization Rules
Beyond user resolution, each tool applies its own field-level sanitization. These rules are enforced through Prisma select clauses (never findMany without select) and post-query transformations.
yani_get_kyc_status
HIGH SENSITIVITYfileUrl is NEVER exposed (S3 signed URLs contain temporary access tokens)fileName, fileSize, mimeType, documentSide, and uploadedAt are returned for documentsyani_get_crypto_wallets
CRYPTO SECURITYpublicKey and privateKey are NEVER exposed0x1234...abcd (first 6 + last 4 characters)yani_get_referrals
PII PROTECTIONrefereeId (filleul internal ID) NEVERexposed — prevents user enumerationipAddress and userAgent NEVERexposed — RGPD personal datafunction truncateAddress(address: string | null | undefined): string {
if (!address) return 'N/A';
if (address.length <= 12) return address;
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
// Examples:
// "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18"
// → "0x742d...bD18"
// "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
// → "bc1qar...5mdq"Rate Limiting
The REST API transport (:3101) enforces rate limiting at 100 requests per minute per IP address using express-rate-limit. Rate limit information is communicated via standard headers conforming to the IETF RateLimit header fields draft specification.
Rate limit headers
| Header | Value | Description |
|---|---|---|
| RateLimit-Limit | 100 | Maximum requests per window |
| RateLimit-Remaining | 97 | Requests remaining in current window |
| RateLimit-Reset | 42 | Seconds until the window resets |
// HTTP/1.1 429 Too Many Requests
// RateLimit-Limit: 100
// RateLimit-Remaining: 0
// RateLimit-Reset: 23
// Retry-After: 23
{
"error": "Too many requests, please try again later."
}The HTTP/SSE transport (:3100) does not include built-in rate limiting because it is session-based and protected by API key authentication. In production, you should add rate limiting at the reverse proxy level (nginx, Cloudflare, etc.).
Request Logging
All HTTP and REST API requests are logged in structured JSON format with daily file rotation. Logs are designed for audit compliance while strictly excluding sensitive data.
export function requestLogger() {
return (req: Request, res: Response, next: NextFunction): void => {
const start = Date.now();
res.on('finish', () => {
const entry = {
timestamp: new Date().toISOString(),
method: req.method,
path: req.path,
status: res.statusCode,
durationMs: Date.now() - start,
ip: req.ip || req.socket.remoteAddress || 'unknown',
};
appendFileSync(getLogFile(), JSON.stringify(entry) + '\n');
});
next();
};
}{"timestamp":"2026-02-22T14:30:01.123Z","method":"POST","path":"/mcp","status":200,"durationMs":45,"ip":"127.0.0.1"}
{"timestamp":"2026-02-22T14:30:02.456Z","method":"GET","path":"/health","status":200,"durationMs":2,"ip":"127.0.0.1"}
{"timestamp":"2026-02-22T14:30:15.789Z","method":"POST","path":"/tools/yani_get_balance","status":200,"durationMs":120,"ip":"192.168.1.50"}
{"timestamp":"2026-02-22T14:31:00.012Z","method":"POST","path":"/tools/yani_get_balance","status":429,"durationMs":1,"ip":"10.0.0.5"}Format
JSON lines (one entry per line). Fields: timestamp, method, path, status, durationMs, ip
Rotation
Daily rotation with ISO date format: logs/YYYY-MM-DD.log. Directory auto-created at startup.
Sanitization
No request bodies, no response bodies, no headers, no API keys, no user data. Only method, path, status, duration, IP.
Security Best Practices
Follow these guidelines when deploying the Y.A.N.I. MCP Server in any environment beyond local development.
Always set MCP_API_KEY in production
criticalGenerate a strong random key (32+ characters). Without it, the HTTP/SSE transport is open to all requests.
Use HTTPS reverse proxy
criticalPlace nginx, Caddy, or Cloudflare in front of HTTP/SSE and REST transports. The MCP server itself only speaks HTTP.
Rotate API keys regularly
highImplement a key rotation schedule (monthly minimum). Update MCP_API_KEY and restart the server. Coordinate with all connected clients.
Monitor logs and access patterns
highReview daily logs for unusual patterns: repeated 401/403 errors, high request volumes from single IPs, or unexpected tool access patterns.
DATABASE_URL with SSL in production
criticalAlways use ?sslmode=require or ?sslmode=verify-full in the PostgreSQL connection string. Never connect to the database over unencrypted channels.
Never expose MCP server directly to the internet
criticalThe MCP server should only be accessible from trusted networks or through an authenticated reverse proxy. Bind to 127.0.0.1 in production, not 0.0.0.0.
Restrict CORS in production
highThe REST API currently allows all origins (Access-Control-Allow-Origin: *). In production, restrict to your specific client domains.
Implement per-user access control
mediumThe current email-based resolution does not restrict which user data an authenticated client can access. Add RBAC or user-scoped API keys for multi-tenant scenarios.
# Generate a strong API key
export MCP_API_KEY=$(openssl rand -hex 32)
# Database with SSL
export DATABASE_URL="postgresql://user:pass@db.example.com:5432/yanipay?sslmode=verify-full"
# Start with explicit host binding
HOST=127.0.0.1 PORT=3100 MCP_API_KEY=$MCP_API_KEY npx tsx src/server-http.ts
# Example nginx reverse proxy config
# server {
# listen 443 ssl;
# server_name mcp.yanipay.com;
#
# ssl_certificate /etc/ssl/certs/yanipay.pem;
# ssl_certificate_key /etc/ssl/private/yanipay.key;
#
# location /mcp {
# proxy_pass http://127.0.0.1:3100;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# }
# }ACPR/AMF Compliance Notes
As a fintech platform operating under French regulation, YaniPay must comply with ACPR (Autorite de Controle Prudentiel et de Resolution) and AMF (Autorite des Marches Financiers) requirements. The MCP server is designed with these obligations in mind.
KYC Data Handling (LCB-FT Obligations)
- KYC documents (identity cards, passports, selfies) are stored in S3 with signed URLs. The MCP server never returns file URLs— only metadata (fileName, fileSize, mimeType, documentSide).
- KYC status is exposed for operational purposes (helping users understand their verification state), but the underlying biometric data, OCR results, and face-match scores are never accessible through MCP tools.
- Rejection reasons are exposed to enable AI assistants to guide users through resubmission, consistent with ACPR guidance on transparent KYC communication.
Transaction Data Access Auditing
- All tool invocations (including
yani_get_transactions) are logged with timestamp, path, and source IP for regulatory audit trails. - Transaction tools are read-only (
readOnlyHint: true). No MCP tool can create, modify, or delete transactions. All mutations go through authenticated application flows. - Audit logs RGPD/LCB-FT implémentés Mis à jour — commit
465f4216(T17+T18) : chaque action sensible (KYC, paiement, carte, connexion, consultation IBAN) est tracée avec horodatage ISO, userId, targetId et metadata. Rétention conforme Article L.561-12 (5 ans). Rate limiting renforcé sur les endpoints de conformité.
RGPD Considerations for LLM Tool Exposure
- User data exposed through MCP tools is minimized by design: only fields necessary for the tool's purpose are returned (data minimization principle, Article 5(1)(c) RGPD).
- IP addresses from referral conversions are never exposed through MCP tools, as they constitute personal data under RGPD (CJUE C-582/14 Breyer).
- LLM data flow consideration: Data returned by MCP tools may be processed by third-party AI models (Claude, GPT, Gemini). Ensure your DPA (Data Processing Agreement) with AI providers covers the categories of personal data exposed through MCP tools.
- Right to erasure:When a user exercises their Article 17 right, ensure that MCP tool responses for that user return appropriate "user not found" messages and that no cached data persists in AI model contexts.
Regulatory disclaimer
This documentation describes the current security implementation of the MCP server. It does not constitute legal advice. YaniPay's compliance with ACPR, AMF, and RGPD requirements is subject to ongoing review with legal counsel and regulatory authorities. Always consult qualified legal and compliance professionals before deploying financial data through AI tools in production.
Last updated: 2026-04-09