SQL-MCP-101
SQL-MCP-101
¿Nuevo en MCP? Comienza con el tutorial interactivo, un recorrido clicable por herramientas, recursos y prompts, y cómo decidir cuál debería usar cada característica.
Un servidor MCP para MySQL pequeño y muy comentado que demuestra los tres primitivos del Model Context Protocol (tools, resources y prompts) en unas 1.100 líneas de Python, además de una interfaz de navegador para explorarlo.
Este repositorio existe para ser leído, no solo ejecutado. Si has visto mencionar MCP y quieres entender qué implica realmente construir un servidor, este es un ejemplo completo y funcional, lo suficientemente pequeño para leerlo de una sentada: un primitivo por archivo, comentarios que explican el porqué en lugar del qué, y una base de datos de demostración con fallos deliberados para que los ejemplos encuentren problemas reales en lugar de juguetes.
mcp_server/
├── database.py read-only introspection; the only file not about MCP
├── execution.py running queries and writes, plus every safety control
├── tools.py 6 TOOLS inspect structure, cannot read or change a row
├── data_tools.py 6 TOOLS read rows, and insert / update / delete / alter
├── resources.py 4 RESOURCES content the APPLICATION attaches (+2 templates)
├── prompts.py 6 PROMPTS workflows the USER invokes
└── server.py wires them together, about 10 meaningful linesEl servidor es de lectura y escritura: responde preguntas sobre los datos ejecutando
consultas reales, y puede cambiar datos y esquema. Está bloqueado a una única
base de datos de demostración desechable, y los controles que hacen que eso sea seguro están en
execution.py y se explican a continuación. Ese diseño es en sí mismo parte de la lección.
La única idea que vale la pena llevarse
La mayoría de los tutoriales de MCP solo cubren las herramientas, lo que deja a la gente pensando que MCP es herramientas. Son tres primitivos, y se diferencian por quién tiene el control:
Primitivo | Quién decide | Cuándo ocurre | Analogía |
Tool | el modelo | a mitad de conversación, autónomamente | una función que el modelo puede llamar |
Resource | la aplicación | de antemano, elegido por un humano | un archivo que adjuntas |
Prompt | el usuario | explícitamente, desde un menú | una pregunta experta guardada |
Los mismos datos pueden aparecer como más de uno. En este repositorio, get_table_ddl es una herramienta
y schema://table/{name}/ddl es un recurso. Los mismos bytes, alcanzados de dos
formas diferentes, porque "el modelo los obtiene cuando decide que los necesita" y
"un humano los adjunta antes de comenzar" son necesidades genuinamente diferentes.
Related MCP server: mysql-mcp-server
Inicio rápido
git clone https://github.com/Khushboo-Mishra/SQL-MCP-101.git
cd SQL-MCP-101
bash scripts/setup.shsetup.sh verifica los requisitos previos, crea el virtualenv, instala las dos
dependencias, crea la base de datos de demostración y verifica el servidor de extremo a extremo. Se
detiene con un mensaje específico en lo primero que falta.
Luego ve los tres primitivos en una sola pasada:
bash scripts/run_explorer.shRequisitos
Python 3.10+
MySQL 8.x ejecutándose localmente (
brew services start mysql)Node.js: opcional, solo para el MCP Inspector
Ollama: opcional, solo para el panel de Chat de la interfaz
Por defecto usa root en 127.0.0.1:3306 sin contraseña, que es el valor
predeterminado de Homebrew, por lo que la mayoría de la gente no cambia nada. De lo contrario, exporta MYSQL_USER, MYSQL_PASSWORD,
MYSQL_HOST, MYSQL_PORT.
Lo que se construye
12 herramientas, 4 recursos + 2 plantillas de URI y 6 prompts, sobre una base de datos de demostración de seis tablas.
Herramientas: el modelo las llama
Divididas en dos archivos por radio de explosión, no por subsistema. Esa es una decisión de diseño deliberada que vale la pena copiar: mantiene la superficie riesgosa pequeña y obvia para cualquiera que revise el servidor o escriba su GRANT de base de datos.
tools.py: inspeccionar estructura. No puede leer una fila, no puede cambiar nada.
Herramienta | Propósito |
| todas las tablas y vistas, con estimaciones de filas |
| columnas, tipos, claves, índices, claves foráneas |
| el |
| todas las claves foráneas declaradas |
| columnas cuyo nombre sugiere PII o secretos |
| encontrar una columna cuando olvidas en qué tabla está |
data_tools.py: leer filas y cambiar datos. Esta es la mitad con consecuencias.
Herramienta | Propósito |
| ejecutar un SELECT y obtener las filas; esto es lo que responde preguntas de datos |
| INSERT / UPDATE / DELETE / CREATE / ALTER / DROP / TRUNCATE |
| inserción estructurada, valores enviados como parámetros vinculados |
| actualización estructurada, |
| borrado estructurado, |
| cada declaración que el servidor ha ejecutado |
¿Por qué tanto un execute_statement general como envoltorios estructurados? Las herramientas
estructuradas son más seguras: los argumentos están tipados y los valores están vinculados, por lo que el modelo
nunca escribe texto SQL y no puede producir algo malformado. Pero solo hacen lo que
anticipaste. Una puerta SQL general maneja la cola larga: funciones de ventana,
un ALTER que no previste. La mayoría de los servidores reales terminan enviando ambos, por
exactamente esa razón.
Recursos: la aplicación los adjunta
URI | Tipo | Contenido |
| JSON | inventario de tablas |
| SQL | DDL para todo el esquema |
| JSON | todas las claves foráneas |
| Markdown | resumen legible para humanos |
| JSON | una tabla (plantilla) |
| SQL | DDL de una tabla (plantilla) |
Un recurso estático tiene un URI fijo y aparece en resources/list, por lo que un
cliente puede mostrarlo en un selector. Un recurso con plantilla tiene {placeholders}
y aparece en resources/templates/list en su lugar. No hay una lista fija para
mostrar, por lo que el cliente completa el espacio en blanco.
Prompts: el usuario los invoca
Prompt | Argumentos | Qué hace |
| ninguno | verificación de salud en cinco pasos: claves, relaciones, PII, nombres |
|
| explica una tabla en lenguaje sencillo |
|
| escribe la consulta, la ejecuta y responde en lenguaje sencillo |
|
| vista previa → confirmar → aplicar → verificar, para cambios |
| ninguno | genera documentación de referencia |
|
| una primera mirada guiada, adaptada a un rol |
Decidir: ¿tool, resource o prompt?
La pregunta en la que la gente se atasca. Recórrela en este orden.
1. ¿Realiza una acción, u obtiene algo que el modelo elige? → Tool. Cualquier cosa que el modelo debería poder decidir hacer por sí mismo.
2. ¿Es un documento que un humano adjuntaría razonablemente antes de comenzar? → Resource. Material de referencia, contexto de todo el esquema, cualquier cosa estable.
3. ¿Es una tarea que alguien repite, donde la forma de preguntar es la experiencia? → Prompt. Envía la buena pregunta en lugar de esperar que se redescubra.
Dos heurísticas que resuelven la mayoría de las dudas restantes:
¿Quién inicia? Modelo → tool. Aplicación → resource. Usuario → prompt.
¿Lo querrías en un menú? Si es así, es un prompt. Los menús son para personas, y solo los prompts se muestran a las personas como comandos.
Ejemplos trabajados de este repositorio
Característica | Elección | Por qué |
Obtener la estructura de una tabla | tool | el modelo la necesita a mitad de razonamiento, de forma impredecible |
DDL de todo el esquema | ambos | tool para el modelo; resource para que un humano lo adjunte de antemano |
Auditoría de esquema | prompt | una tarea repetible donde saber qué preguntar es el valor |
Buscar una columna | tool | toma un argumento que el modelo elige en el momento de la llamada |
Resumen en Markdown | resource | referencia pasiva, sin decisión requerida |
Dónde se equivoca la gente
Todo como herramientas. Funciona, pero el modelo quema llamadas obteniendo contexto que un humano podría haber adjuntado una vez, y los usuarios no obtienen puntos de entrada descubribles.
Recursos para cosas que necesitan argumentos que el modelo elige. Si el modelo decide el parámetro, es una herramienta.
Prompts que hacen trabajo. Un prompt devuelve texto. Si te encuentras consultando la base de datos dentro de un prompt, querías una herramienta.
La base de datos de demostración
mcp_demo, seis tablas, deliberadamente imperfectas para que los ejemplos encuentren problemas reales:
Tabla | Fallo deliberado |
|
|
|
|
| (limpia, el ejemplo de referencia) |
|
|
| sin clave primaria en absoluto |
|
|
Ejecuta audit_schema contra ella y cada uno de esos debería salir a la superficie. Ese es el
demo: las herramientas encuentran problemas genuinos, no juguetes.
Ejecutarlo
El explorador: cada primitivo en una sola pasada
bash scripts/run_explorer.shImprime el apretón de manos initialize, luego lista y ejercita herramientas, recursos
(estáticos y con plantilla) y prompts. Ejecuta esto primero, confirma que la configuración
funciona y muestra toda la superficie del protocolo en una sola pantalla.
La interfaz web: los tres primitivos en un navegador
bash scripts/run_ui.sh # http://127.0.0.1:8000
PORT=9000 bash scripts/run_ui.shCuatro paneles, uno por cada cosa que vale la pena mostrar:
Panel | Qué demuestra |
Chat | pregunta en inglés sencillo; cada herramienta que el modelo eligió se lista en línea sobre la respuesta |
Tools | las 12, agrupadas por radio de explosión, cada una invocable desde un formulario |
Resources | estáticos y con plantilla, legibles en el lugar |
Prompts | expande uno para ver el texto, o envíalo directamente al chat |
Una franja de Actividad en vivo en la parte inferior muestra el JSON-RPC real debajo,
tools/call, resources/read, prompts/get, por lo que el protocolo es visible todo el tiempo.
La página es en sí misma un cliente MCP: no tiene acceso a MySQL por sí misma. Todo lo que aparece en pantalla llegó a través del mismo protocolo que usa Claude Desktop.
El chat necesita un LLM local a través de Ollama, gratuito, sin clave de API, y nada sale de la máquina:
brew install ollama && ollama serve
ollama pull qwen2.5:7bEstablece ANTHROPIC_API_KEY en su lugar y cambia automáticamente a la API de Claude.
Los paneles de Tools, Resources y Prompts funcionan sin ningún LLM.
El MCP Inspector: el propio cliente de Anthropic
bash scripts/run_inspector.shAbre la URL impresa http://localhost:6274?...; se requiere el token. Tiene
pestañas separadas de Tools, Resources y Prompts, que es la forma
más convincente de mostrar las tres: nada de eso es nuestro código, así que si
el Inspector maneja el servidor, el servidor es genuinamente compatible con la especificación.
Recorrido sugerido: Tools → describe_table con ORDERS; Resources →
schema://overview; Prompts → audit_schema.
Claude Desktop / Claude Code
bash scripts/add_to_claude_desktop.sh # Claude Desktop, run from Terminal.app
bash scripts/install_claude.sh # Claude Code, safe to run anywhereadd_to_claude_desktop.sh hace una copia de seguridad de tu configuración, conserva cualquier servidor ya registrado, valida el JSON, prueba el comando de lanzamiento exacto y relanza la aplicación. Imprime un guion de demostración sugerido cuando termina.
Luego pregunta: "Audita esta base de datos", o usa el prompt audit_schema desde el menú, que es donde los prompts finalmente se vuelven visibles.
--desktopdebe ejecutarse desde Terminal.app, no desde dentro de Claude Desktop. Claude Desktop mantiene su configuración en memoria y reescribe el archivo desde esa copia, por lo que una edición realizada mientras está en ejecución se descarta silenciosamente. El script cierra la aplicación, edita y relanza, lo que mataría la sesión desde la que lo lanzaste.
Leyendo el código
Aproximadamente una hora de principio a fin. Este orden se construye sin referencias hacia adelante:
1. mcp_server/server.py: empieza aquí. Diez líneas significativas, y toda la arquitectura cabe en una pantalla: crea el servidor, registra los tres primitivos, ejecuta. Todo lo demás es detalle.
2. mcp_server/database.py: código MySQL ordinario sin nada de MCP. Vale la pena leerlo temprano porque muestra lo delgada que es realmente la capa MCP: si ya tienes una capa de acceso a datos, ya estás casi todo el camino.
Mira de cerca safe_identifier. MySQL no te permite vincular un nombre de tabla como parámetro (SHOW CREATE TABLE %s no es SQL válido), por lo que los identificadores deben interpolarse en la cadena. Eso es un riesgo genuino de inyección, y esa pequeña función es lo que lo hace seguro.
3. mcp_server/tools.py: el decorador @mcp.tool(), y la idea que hace más trabajo en todo el proyecto: el docstring es el prompt. Es lo único que el modelo lee al decidir si llamar a una herramienta, por lo que está escrito para el modelo, no para un humano que lea el código fuente.
4. mcp_server/resources.py: URIs estáticos frente a los plantillados, y por qué get_table_ddl existe tanto como herramienta y como recurso. Esa duplicación es deliberada y es la ilustración más clara de la idea de quién controla qué.
5. mcp_server/prompts.py: los prompts devuelven texto, no datos. El texto es una instrucción que normalmente le dice al modelo qué herramientas usar. Archivo corto, y el que la mayoría nunca ha visto.
6. mcp_server/execution.py: léelo cuando quieras saber cómo se puede hacer seguro el acceso de escritura. Cinco controles, cada uno con un comentario que explica qué previene.
7. examples/explore_server.py: el otro lado del protocolo. Un cliente mínimo que lista y llama a todo, para que puedas ver qué cruza realmente el cable.
Yendo más allá
Este servidor está limitado a una base de datos para mantener los ejemplos cortos. Para llevarlo más lejos:
Múltiples esquemas: toma
schemacomo argumento de herramienta en lugar de leerMYSQL_DEMO_SCHEMA. Añade una lista blanca para que un agente no pueda alcanzar producción.Ejecución de consultas: una herramienta
run_query. Factible, pero cambia completamente la historia de seguridad: el servidor entonces necesita credenciales que lean tus tablas, y los resultados entran en el contexto del modelo. Fuerza soloSELECT, inyecta unLIMITy usa un usuario de base de datos de solo lectura.Transporte remoto:
mcp.run(transport="streamable-http"). Mismas herramientas, mismo código, diferente tubería. Añade autenticación antes de exponerlo.Caché:
describe_tablegolpea la base de datos en cada llamada. Una caché con TTL corto vale la pena una vez que un modelo empieza a llamarla en un bucle.
Notas de seguridad
Este servidor puede cambiar tus datos. Eso es deliberado: "¿puede un agente escribir en mi base de datos?" es la pregunta que todo equipo hace, y un ejemplo funcional de cómo hacerlo de forma segura es más útil que uno que evita el tema. Pero sí significa que los controles importan.
Los cinco controles, todos en execution.py
Control | Qué detiene |
Bloqueo de esquema | cada sentencia se ejecuta en una conexión fijada a la base de datos de demostración; se rechaza cualquier referencia a otra base de datos |
Una sentencia por llamada | una segunda sentencia no puede viajar junto a una legítima |
Puertas separadas de lectura/escritura |
|
Límite de filas | un |
Registro de auditoría | cada sentencia se registra y es legible mediante |
Una lista negra también rechaza sentencias que escaparían del bloqueo de esquema, alcanzarían el sistema de archivos o cambiarían el estado a nivel de servidor, cambios de privilegios, gestión de usuarios, importación/exportación de archivos y operaciones a nivel de base de datos.
Una sutileza, porque es un error fácil de repetir: el bloqueo de esquema no puede funcionar solo con patrones. En SQL, a.b suele ser alias.columna (SELECT c.NAME FROM CUSTOMERS c), no esquema.tabla, así que rechazar cada nombre con punto rompe los joins ordinarios, que es exactamente el error que tenía la primera versión de esto. Ahora compara cada calificador contra la lista real de bases de datos en el servidor: un nombre de base de datos real se rechaza, un alias de tabla pasa sin tocarse.
Apúntalo a un usuario restringido
Los controles anteriores son defensa en profundidad, no la defensa. En cualquier cosa más allá de una demostración, conéctate como un usuario de MySQL cuyo permiso cubra solo el esquema que pretendes exponer. Si las credenciales no pueden alcanzar producción, tampoco pueden hacerlo una inyección de prompt o un error del modelo.
Dos cosas más que vale la pena decir claramente:
Los nombres de tabla no pueden ser parámetros vinculados.
SHOW CREATE TABLE %sno es SQL válido, por lo que los identificadores deben interpolarse, un verdadero sumidero de inyección.database.safe_identifieres lo que lo hace seguro, y es la función más importante del proyecto.El usuario MySQL que se conecta es el límite real. Dale un
GRANTde solo lectura limitado a los esquemas que pretendes exponer. La naturaleza de solo lectura del código es defensa en profundidad, no la defensa.
Licencia
MIT, ver LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables interaction with MySQL databases through MCP, supporting query execution, table operations (insert, update, delete), and schema inspection for natural language database management.121MIT
- AlicenseNot gradedqualityDmaintenanceEnables MySQL database operations through MCP, including executing SQL queries, listing databases and tables, and describing table structures.4545MIT
- AlicenseNot gradedqualityDmaintenanceEnables natural language interaction with MySQL databases through MCP, supporting SQL execution, schema exploration, and database management via tools, resources, and prompts.5MIT
- AlicenseNot gradedqualityCmaintenanceEnables natural language interaction with MySQL databases through MCP tools for querying, executing DDL/DML, listing databases/tables, and describing table schemas, with parameterized queries and read-only mode.454MIT
Related MCP Connectors
GibsonAI MCP server: manage your databases with natural language
Connect to PlanetScale databases, branches, schema, query insights, and execute SQL
MCP server for managing Prisma Postgres.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Khushboo-Mishra/SQL-MCP-101'
If you have feedback or need assistance with the MCP directory API, please join our Discord server