How I Connected Claude MCP to an Enterprise Identity Database for Natural Language Querying &… A developer connected Claude's Model Context Protocol (MCP) to an enterprise identity database, enabling natural language querying of operational data. The system uses dynamic schema discovery and a secure SQL validation layer to allow read-only queries while blocking destructive commands. This approach aims to make enterprise data more accessible without requiring SQL expertise, addressing security concerns by enforcing strict controls. Using Model Context Protocol MCP , dynamic schema discovery, and a secure SQL validation layer to query enterprise identity data with natural language. Over the last few months, I’ve been experimenting with ways to make enterprise data more accessible through AI. One challenge I kept running into was querying and reporting. In many enterprise identity platforms, valuable operational data is stored inside relational databases, but accessing that data typically requires knowledge of the schema, SQL expertise, and familiarity with the platform itself. A seemingly simple question like: How many failed provisioning transactions occurred last week? can require searching through documentation, identifying the correct tables, understanding status values, and writing a custom SQL query. For example , an operational team investigating provisioning failures, or a GRC team gathering evidence for an audit, would need to know that status data lives in a column with inconsistent values like “ Sometimes, going through the UI can take a significant amount of time to load when there are millions of records at the enterprise level. At the same time, allowing an AI model unrestricted database access is not something most organizations would be comfortable with. This led me to explore a different approach: combining Model Context Protocol MCP with an enterprise identity database to enable natural language reporting while maintaining strict security controls. The result was an MCP server capable of understanding a live database schema, generating read-only SQL queries, and returning meaningful answers through a conversational interface. Model Context Protocol MCP has quickly become one of the most interesting developments in the AI ecosystem. Before MCP, every AI application needed a custom integration for every external tool or data source. If you had: you quickly ended up maintaining dozens of integrations. MCP changes this model by introducing a standard interface between AI systems and external resources. Instead of building separate integrations for every combination, tools can expose capabilities through a common protocol and AI models can consume them consistently. In this project, MCP became the bridge between Claude and an enterprise identity database. The goal was straightforward: Allow users to ask questions such as: without requiring any SQL knowledge. At the same time, the solution needed to satisfy three requirements: The architecture consists of four major components: The MCP server acts as the orchestration layer. It exposes tools that Claude can invoke whenever it needs information from the database. Examples include: One of the biggest challenges with AI-generated SQL is schema awareness. Most demonstrations rely on hardcoded table definitions. That works initially, but quickly becomes difficult to maintain. Instead, I implemented automatic schema discovery. At startup, the server: This allows the AI to work with the current database structure instead of a manually maintained copy. Security was a non-negotiable requirement. Every generated query passes through a validation layer before reaching the database. Allowed: SELECT ...WITH ... Blocked: INSERT ...UPDATE ...DELETE ...DROP ...ALTER ...TRUNCATE ... Beyond SQL validation, every HTTP and webhook endpoint requires a separate authentication token, distinct from the AI provider’s API key. This ensures that even if someone discovers the server’s address, they cannot invoke any tool without proper authorization. The validation layer also enforces row limits to prevent accidental large-scale queries. The adapter layer abstracts database connectivity. Instead of coupling the implementation to a single database technology, adapters provide a consistent interface. This makes it possible to support: through configuration rather than code changes. One feature that proved particularly valuable was schema discovery. Traditional AI-to-database integrations often fail because the model has incomplete knowledge of the schema. For example, a user might ask: Show failed provisioning transactions. The AI has no way of knowing: unless that information is provided. To solve this, the MCP server automatically inspects the database during startup. Database |Read Tables |Read Columns |Read Data Types |Read Distinct Values |Generate AI Context The generated metadata becomes part of the prompt supplied to the AI model. As a result, the model understands: before generating SQL. This significantly improves accuracy. When a user asks a question, several steps occur behind the scenes. User Question | vMCP Tool | vSchema Context | vClaude Generates SQL | vSafety Validation | vDatabase Execution | vResponse Formatting Let’s walk through an example. User question: How many failed provisioning transactions occurred during the last seven days? The MCP tool builds a prompt containing: Claude then generates SQL. The safety layer validates the query. The database executes the query. Results are formatted and returned to the user. The entire process feels conversational while still leveraging structured database operations. One aspect I wanted to preserve was flexibility. The same MCP tools can be consumed through multiple transport mechanisms. Using STDIO transport, Claude Desktop can communicate directly with the MCP server. This provides a seamless conversational experience. js import { Server } from '@modelcontextprotocol/sdk/server/index.js';import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';import { CallToolRequestSchema, ListToolsRequestSchema,} from '@modelcontextprotocol/sdk/types.js';import { config } from '../config/config.js';import { createAdapter } from './adapters/adapter-factory.js';import { createAI } from './adapters/ai-factory.js';import { SchemaDiscovery } from './discovery/schema-discovery.js';import { SafetyGuard } from './discovery/safety.js';import { ToolRegistry } from './tools/registry.js';async function main { console.error \n MCP DB Server starting....... ; console.error DB: ${config.database.type} / ${config.database.name || config.database.file} ; console.error AI: ${config.ai.provider} / ${config.ai.model} ; console.error Tools: ${config.tools.enabled.join ', ' }\n ; // Connect to database const adapter = await createAdapter config.database ; // Discover schema const schema = new SchemaDiscovery adapter, config.schema ; if config.schema.autoDiscover { await schema.build ; console.error Schema ${schema.getSummary } ; } // Initialize AI provider const ai = await createAI config.ai ; // Safety guard const safety = new SafetyGuard config.safety ; // Shared context passed to all tools const context = { adapter, schema, ai, safety, safetyConfig: config.safety }; // Load tools const registry = new ToolRegistry ; await registry.load config.tools.enabled, context ; console.error Tools Active: ${registry.list .join ', ' } ; // Create MCP server const server = new Server { name: config.server.name, version: config.server.version }, { capabilities: { tools: {} } } ; // List tools server.setRequestHandler ListToolsRequestSchema, async = { tools: registry.getDefinitions , } ; // Execute tool server.setRequestHandler CallToolRequestSchema, async request = { const { name, arguments: args } = request.params; console.error Tool Calling: ${name} , JSON.stringify args ; try { return await registry.execute name, args || {} ; } catch e { console.error Tool Error in ${name}: , e.message ; return { content: { type: 'text', text: Tool error: ${e.message} } , isError: true, }; } } ; // Connect transport const transport = new StdioServerTransport ; await server.connect transport ; console.error \n MCP server ready. Listening on stdio.\n ;}main .catch e = { console.error 'Fatal error:', e ; process.exit 1 ;} ; The MCP tools can also be exposed through HTTP endpoints. This enables integration with: Webhook support allows external systems to trigger database-driven AI analysis automatically. In the current implementation, the triggering system either supplies the question directly in the payload, or the server applies simple pattern matching against known event shapes such as a GitHub pull request payload to infer one. A more mature version of this would let administrators define question templates per event type, rather than relying on inferred defaults. For users who prefer a browser-based experience, the same functionality can be exposed through a lightweight web interface. Because all transports use the same underlying tools, the business logic remains consistent across every access method. python import 'dotenv/config';import express from 'express';import cors from 'cors';import { dirname, join } from 'path';import { fileURLToPath } from 'url';import { createAdapter } from './adapters/adapter-factory.js';import { createAI } from './adapters/ai-factory.js';import { SchemaDiscovery } from './discovery/schema-discovery.js';import { SafetyGuard } from './discovery/safety.js';import { ToolRegistry } from './tools/registry.js';const dirname = dirname fileURLToPath import.meta.url ;// Build config directly from process.envfunction getConfig { return { database: { type: process.env.DB TYPE || 'mysql', host: process.env.DB HOST || 'localhost', port: parseInt process.env.DB PORT || '3306' , name: process.env.DB NAME || 'identityiq', user: process.env.DB USER || 'root', password: process.env.DB PASSWORD || '', file: process.env.DB FILE || './data.db', tablePrefix: process.env.DB TABLE PREFIX || '', connectionLimit: 10, }, ai: { provider: process.env.AI PROVIDER || 'anthropic', model: process.env.AI MODEL || 'claude-sonnet-4-5', apiKey: process.env.ANTHROPIC API KEY || process.env.OPENAI API KEY || '', maxTokens: parseInt process.env.AI MAX TOKENS || '2048' , }, tools: { enabled: process.env.MCP TOOLS || 'query database,describe schema,sample data,search records' .split ',' .map t = t.trim , }, schema: { autoDiscover: process.env.SCHEMA AUTO DISCOVER == 'false', maxTablesInContext: parseInt process.env.SCHEMA MAX TABLES || '50' , enumColumnNames: process.env.SCHEMA ENUM COLUMNS || 'status,state,type,phase,action,op,operation,source,level,completion status,result,severity,outcome' .split ',' , maxEnumValues: parseInt process.env.SCHEMA MAX ENUM VALUES || '15' , skipColumns: process.env.SCHEMA SKIP COLUMNS || 'attributes,xml,extended attributes,scorecard,preferences,arguments,config' .split ',' , }, safety: { allowedStatements: 'SELECT', 'WITH' , blockedKeywords: 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER', 'CREATE', 'TRUNCATE' , maxRowsReturned: parseInt process.env.MAX ROWS || '500' , }, };}// Authconst API TOKEN = process.env.API TOKEN || 'XXXXXXXX';function requireAuth req, res, next { const header = req.headers 'authorization' || ''; const token = header.startsWith 'Bearer ' ? header.slice 7 : req.query.token; if token || token == API TOKEN { return res.status 401 .json { error: 'Unauthorized. Pass: Authorization: Bearer