Skip to main content
Glama
Khushboo-Mishra

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 lines

El 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.sh

setup.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.sh

Requisitos

  • 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

list_tables

todas las tablas y vistas, con estimaciones de filas

describe_table(table)

columnas, tipos, claves, índices, claves foráneas

get_table_ddl(table)

el CREATE TABLE exacto

list_relationships

todas las claves foráneas declaradas

find_sensitive_columns

columnas cuyo nombre sugiere PII o secretos

search_columns(keyword)

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

run_query(sql, limit)

ejecutar un SELECT y obtener las filas; esto es lo que responde preguntas de datos

execute_statement(sql)

INSERT / UPDATE / DELETE / CREATE / ALTER / DROP / TRUNCATE

insert_row(table, values)

inserción estructurada, valores enviados como parámetros vinculados

update_rows(table, changes, where)

actualización estructurada, where requerido

delete_rows(table, where)

borrado estructurado, where requerido

show_audit_log(limit)

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

schema://tables

JSON

inventario de tablas

schema://ddl

SQL

DDL para todo el esquema

schema://relationships

JSON

todas las claves foráneas

schema://overview

Markdown

resumen legible para humanos

schema://table/{name}

JSON

una tabla (plantilla)

schema://table/{name}/ddl

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

audit_schema

ninguno

verificación de salud en cinco pasos: claves, relaciones, PII, nombres

explain_table

table

explica una tabla en lenguaje sencillo

ask_data

question

escribe la consulta, la ejecuta y responde en lenguaje sencillo

modify_data

request

vista previa → confirmar → aplicar → verificar, para cambios

document_schema

ninguno

genera documentación de referencia

onboarding_tour

role

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

CUSTOMERS

EMAIL, PHONE, el escaneo de columnas sensibles se activa

PRODUCTS

SKU es UNIQUE pero no la PK, una clave natural que vale la pena discutir

ORDERS

(limpia, el ejemplo de referencia)

ORDER_ITEMS

PRODUCT_ID parece una clave foránea pero no tiene restricción

AUDIT_LOG

sin clave primaria en absoluto

legacy_notes

snake_case mientras todo lo demás es UPPER_CASE

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.sh

Imprime 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.sh

Cuatro 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:7b

Establece 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.sh

Abre 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: Toolsdescribe_table con ORDERS; Resourcesschema://overview; Promptsaudit_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 anywhere

add_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.

--desktop debe 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 schema como argumento de herramienta en lugar de leer MYSQL_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 solo SELECT, inyecta un LIMIT y 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_table golpea 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

run_query se niega a escribir y execute_statement se niega a leer, así que ninguna puede ser convencida para hacer el trabajo de la otra

Límite de filas

un SELECT amplio no puede inundar el contexto del modelo

Registro de auditoría

cada sentencia se registra y es legible mediante show_audit_log

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 %s no es SQL válido, por lo que los identificadores deben interpolarse, un verdadero sumidero de inyección. database.safe_identifier es 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 GRANT de 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.

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables interaction with MySQL databases through MCP, supporting query execution, table operations (insert, update, delete), and schema inspection for natural language database management.
    121
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables MySQL database operations through MCP, including executing SQL queries, listing databases and tables, and describing table structures.
    454
    5
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables natural language interaction with MySQL databases through MCP, supporting SQL execution, schema exploration, and database management via tools, resources, and prompts.
    5
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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.
    454
    MIT

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

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