pmi-mcp-server
MCP server that exposes a single tool to execute SQL queries directly against Microsoft SQL Server via pyodbc, returning result sets as JSON. It forwards queries without parsing or filtering, relying on database permissions for security.
README
PMI MCP Server for SQL Server
Server MCP in Python che espone il tool execute_query e inoltra il testo SQL
ricevuto direttamente a Microsoft SQL Server tramite pyodbc.
Il server non analizza, riscrive, filtra o limita le query. Autorizzazioni, permessi e guardrail SQL sono responsabilità del database e dell'utenza usata nella stringa di connessione.
Piattaforme supportate
Il codice del server è indipendente dal sistema operativo ed è predisposto per:
- Windows 10/11 e Windows Server
- macOS Intel e Apple Silicon
- Linux nelle distribuzioni supportate dal Microsoft ODBC Driver 18
La matrice CI esegue i test su Windows, macOS e Ubuntu.
Requisiti comuni
- Python 3.10 o successivo
- Microsoft ODBC Driver 18 for SQL Server
- Un ODBC driver manager (
unixODBC) su macOS/Linux; su Windows è già incluso nel sistema operativo - Accesso a un'istanza Microsoft SQL Server
Driver ODBC
Windows
- Installa il Microsoft Visual C++ Redistributable adatto all'architettura del sistema, se non è già presente.
- Scarica e installa Microsoft ODBC Driver 18 for SQL Server scegliendo x64, x86 oppure ARM64 in base al sistema.
- Verifica da PowerShell che il driver sia registrato:
Get-OdbcDriver -Name "ODBC Driver 18 for SQL Server"
Python e il driver ODBC devono avere architetture compatibili. In una normale installazione Windows x64, usa Python x64 e il driver x64.
macOS
Con Homebrew:
brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release
brew update
HOMEBREW_ACCEPT_EULA=Y brew install msodbcsql18
Il pacchetto installa anche unixODBC come dipendenza.
Linux
Microsoft pubblica pacchetti per Ubuntu, Debian, RHEL, Oracle Linux, SLES, Alpine e Azure Linux. Segui la procedura ufficiale per la distribuzione utilizzata. Ad esempio, dopo aver configurato il repository Microsoft su Ubuntu/Debian:
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 unixodbc-dev
Installazione Python
Con uv:
uv sync
Con pip su macOS/Linux:
python -m venv .venv
source .venv/bin/activate
python -m pip install -e .
Con pip da PowerShell su Windows:
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e .
Configurazione
Usa .env.example come riferimento e passa le variabili nell'ambiente del
processo MCP. La sola variabile obbligatoria è:
SQLSERVER_CONNECTION_STRING=DRIVER={ODBC Driver 18 for SQL Server};SERVER=localhost,1433;DATABASE=my_database;UID=my_user;PWD=my_password;Encrypt=yes;TrustServerCertificate=no
Variabili opzionali:
| Variabile | Default | Descrizione |
|---|---|---|
SQLSERVER_AUTOCOMMIT |
false |
Se false, il server esegue commit a fine richiesta e rollback in caso di errore |
SQLSERVER_CONNECTION_TIMEOUT |
30 |
Timeout della connessione in secondi |
MCP_TRANSPORT |
stdio |
stdio oppure streamable-http |
MCP_HOST |
127.0.0.1 |
Host usato con Streamable HTTP |
MCP_PORT |
8000 |
Porta usata con Streamable HTTP |
MCP_BEARER_TOKEN |
— | Token opzionale, lungo almeno 32 caratteri, richiesto a ogni chiamata HTTP |
MCP_TLS_CERT_FILE |
— | Percorso del certificato TLS in formato PEM |
MCP_TLS_KEY_FILE |
— | Percorso della chiave privata TLS in formato PEM |
MCP_TLS_KEY_PASSWORD |
— | Password opzionale della chiave privata |
Il file .env non viene caricato automaticamente dal processo: configura le
variabili nel client MCP o impostale nell'ambiente prima dell'avvio. Questo
evita differenze di caricamento tra Windows e sistemi Unix.
Su Windows è possibile usare anche l'autenticazione integrata dell'identità che avvia il processo MCP:
SQLSERVER_CONNECTION_STRING=DRIVER={ODBC Driver 18 for SQL Server};SERVER=my-server;DATABASE=my_database;Trusted_Connection=yes;Encrypt=yes;TrustServerCertificate=no
Per verificare che Python rilevi il driver installato:
python -c "import pyodbc; print(pyodbc.drivers())"
L'output deve includere ODBC Driver 18 for SQL Server.
Autenticazione Bearer
Imposta MCP_BEARER_TOKEN per proteggere tutte le richieste Streamable HTTP.
Il token deve contenere almeno 32 caratteri. Puoi generarne uno casuale su tutte
le piattaforme con Python:
python -c "import secrets; print(secrets.token_urlsafe(32))"
Esempio di avvio:
MCP_TRANSPORT=streamable-http \
MCP_BEARER_TOKEN='inserire-un-token-casuale-di-almeno-32-caratteri' \
SQLSERVER_CONNECTION_STRING='...' \
uv run pmi-mcp-server
Il client deve inviare questo header:
Authorization: Bearer inserire-un-token-casuale-di-almeno-32-caratteri
Una richiesta priva del token corretto riceve 401 Unauthorized. Il confronto
avviene in tempo costante e il token non viene scritto nei log. Se la variabile
non è configurata, l'autenticazione HTTP resta disabilitata. Usa sempre HTTPS
quando il token attraversa una rete non fidata.
HTTPS
Il trasporto Streamable HTTP usa HTTPS automaticamente quando sono configurati
entrambi MCP_TLS_CERT_FILE e MCP_TLS_KEY_FILE. Se uno dei due manca o il
percorso non esiste, il server termina subito con un errore di configurazione.
I percorsi possono usare la sintassi nativa di Windows, macOS o Linux.
Esempio macOS/Linux:
MCP_TRANSPORT=streamable-http \
MCP_HOST=127.0.0.1 \
MCP_PORT=8443 \
MCP_TLS_CERT_FILE=/percorso/server-cert.pem \
MCP_TLS_KEY_FILE=/percorso/server-key.pem \
SQLSERVER_CONNECTION_STRING='...' \
uv run pmi-mcp-server
Esempio Windows PowerShell:
$env:MCP_TRANSPORT = "streamable-http"
$env:MCP_HOST = "127.0.0.1"
$env:MCP_PORT = "8443"
$env:MCP_BEARER_TOKEN = "inserire-un-token-casuale-di-almeno-32-caratteri"
$env:MCP_TLS_CERT_FILE = "C:\certificati\server-cert.pem"
$env:MCP_TLS_KEY_FILE = "C:\certificati\server-key.pem"
$env:SQLSERVER_CONNECTION_STRING = "..."
.\.venv\Scripts\pmi-mcp-server.exe
Il client si collega quindi a https://127.0.0.1:8443/mcp. Il certificato deve
contenere l'indirizzo o il nome host usato dal client nel campo Subject
Alternative Name e deve essere considerato attendibile dal sistema del client.
Per ambienti di produzione è generalmente preferibile terminare TLS su un
reverse proxy o load balancer e lasciare il server MCP su una rete privata.
Esempio generico di client MCP con URL e Bearer token:
{
"mcpServers": {
"sql-server": {
"url": "https://127.0.0.1:8443/mcp",
"headers": {
"Authorization": "Bearer inserire-un-token-casuale-di-almeno-32-caratteri"
}
}
}
}
La sintassi esatta degli header e l'eventuale lettura del token da una variabile d'ambiente dipendono dal client MCP utilizzato. Non salvare token reali nel repository.
Avvio
macOS/Linux
Trasporto locale stdio:
SQLSERVER_CONNECTION_STRING='...' uv run pmi-mcp-server
Trasporto Streamable HTTP:
MCP_TRANSPORT=streamable-http MCP_PORT=8000 \
SQLSERVER_CONNECTION_STRING='...' uv run pmi-mcp-server
L'endpoint predefinito è http://127.0.0.1:8000/mcp.
Windows PowerShell
Trasporto locale stdio:
$env:SQLSERVER_CONNECTION_STRING = "DRIVER={ODBC Driver 18 for SQL Server};SERVER=localhost,1433;DATABASE=my_database;UID=my_user;PWD=my_password;Encrypt=yes;TrustServerCertificate=no"
.\.venv\Scripts\pmi-mcp-server.exe
Trasporto Streamable HTTP:
$env:MCP_TRANSPORT = "streamable-http"
$env:MCP_HOST = "127.0.0.1"
$env:MCP_PORT = "8000"
$env:SQLSERVER_CONNECTION_STRING = "DRIVER={ODBC Driver 18 for SQL Server};SERVER=localhost,1433;DATABASE=my_database;UID=my_user;PWD=my_password;Encrypt=yes;TrustServerCertificate=no"
.\.venv\Scripts\pmi-mcp-server.exe
Configurazione del client MCP
macOS/Linux:
{
"mcpServers": {
"sql-server": {
"command": "uv",
"args": [
"--directory",
"/percorso/assoluto/pmi-mcp-server",
"run",
"pmi-mcp-server"
],
"env": {
"SQLSERVER_CONNECTION_STRING": "DRIVER={ODBC Driver 18 for SQL Server};SERVER=localhost,1433;DATABASE=my_database;UID=my_user;PWD=my_password;Encrypt=yes;TrustServerCertificate=no"
}
}
}
}
Windows (nei file JSON i backslash devono essere duplicati):
{
"mcpServers": {
"sql-server": {
"command": "C:\\percorso\\pmi-mcp-server\\.venv\\Scripts\\pmi-mcp-server.exe",
"args": [],
"env": {
"SQLSERVER_CONNECTION_STRING": "DRIVER={ODBC Driver 18 for SQL Server};SERVER=localhost,1433;DATABASE=my_database;UID=my_user;PWD=my_password;Encrypt=yes;TrustServerCertificate=no"
}
}
}
}
Tool MCP
execute_query
Input:
{
"query": "SELECT TOP (10) * FROM dbo.Customers"
}
Output:
{
"result_sets": [
{
"columns": ["CustomerId", "Name"],
"rows": [[1, "Acme"]],
"row_count": 1
}
]
}
Il tool restituisce tutti i result set. decimal, date/ore e UUID vengono
serializzati come stringhe; i valori binari sono codificati in Base64. Le query
senza righe restituiscono comunque un result set con columns e rows vuoti.
Guardrail SQL
Il server inoltra il testo SQL senza analizzarlo. I controlli devono quindi essere applicati da SQL Server secondo il principio del privilegio minimo.
Architettura consigliata
- Crea un database di reporting separato, ad esempio
McpReporting. Non collegare l'utenza MCP direttamente al database transazionale. - Popola il database tramite ETL, replica o snapshot gestiti da un'identità diversa da quella MCP.
- Crea uno schema di esposizione per ogni reparto, ad esempio
sales,financeehr. - Inserisci negli schemi di reparto esclusivamente viste. Conserva le tabelle materializzate in schemi interni, non accessibili all'utenza MCP.
- Crea un login, un utente e un ruolo differenti per ogni reparto. Se più reparti devono essere isolati, avvia un'istanza MCP per reparto con la sua stringa di connessione.
- Concedi
SELECTalle singole viste, non all'intero schema. UnGRANT SELECT ON SCHEMAcomprenderebbe anche eventuali tabelle aggiunte in futuro. - Non aggiungere l'utenza ai ruoli
db_owner,db_datareader,db_datawriter,db_ddladmin,db_securityadminosysadmin.
Le viste sono utilizzabili come barriera di sicurezza senza concedere accesso
diretto alle tabelle sottostanti. Quando possibile, usa SCHEMABINDING, elenca
esplicitamente le colonne ed escludi o maschera i dati sensibili.
1. Database e schemi separati
Esegui come amministratore e sostituisci nomi e oggetti di esempio:
USE [master];
GO
IF DB_ID(N'McpReporting') IS NULL
BEGIN
CREATE DATABASE [McpReporting];
END;
GO
USE [McpReporting];
GO
-- Schemi interni: contengono le tabelle alimentate dall'ETL.
CREATE SCHEMA [internal_sales] AUTHORIZATION [dbo];
GO
CREATE SCHEMA [internal_finance] AUTHORIZATION [dbo];
GO
CREATE SCHEMA [internal_hr] AUTHORIZATION [dbo];
GO
-- Schemi pubblicati: devono contenere esclusivamente viste.
CREATE SCHEMA [sales] AUTHORIZATION [dbo];
GO
CREATE SCHEMA [finance] AUTHORIZATION [dbo];
GO
CREATE SCHEMA [hr] AUTHORIZATION [dbo];
GO
Se uno schema esiste già, ometti il relativo CREATE SCHEMA.
2. Esporre esclusivamente viste
Esempio per il reparto vendite:
USE [McpReporting];
GO
CREATE VIEW [sales].[v_customers]
WITH SCHEMABINDING
AS
SELECT
[CustomerId],
[CustomerName],
[CountryCode]
FROM [internal_sales].[Customers];
GO
CREATE VIEW [sales].[v_orders]
WITH SCHEMABINDING
AS
SELECT
[OrderId],
[CustomerId],
[OrderDate],
[NetAmount]
FROM [internal_sales].[Orders];
GO
Non usare SELECT * nelle viste. Con SCHEMABINDING, gli oggetti referenziati
devono trovarsi nello stesso database ed essere indicati con nome a due parti.
3. Utente e ruolo dedicati
Il login può essere SQL Server, Windows o Microsoft Entra. Dopo averlo creato con il sistema di identità scelto, mappalo nel solo database di reporting:
USE [McpReporting];
GO
CREATE USER [mcp_sales_user] FOR LOGIN [mcp_sales_login];
GO
CREATE ROLE [mcp_sales_reader] AUTHORIZATION [dbo];
GO
ALTER ROLE [mcp_sales_reader] ADD MEMBER [mcp_sales_user];
GO
GRANT CONNECT TO [mcp_sales_reader];
GO
Non creare lo stesso utente negli altri database e non concedere
CONNECT ANY DATABASE al login.
4. Consentire SELECT solo sulle viste autorizzate
La variante più restrittiva concede ogni vista esplicitamente:
USE [McpReporting];
GO
GRANT SELECT ON OBJECT::[sales].[v_customers]
TO [mcp_sales_reader];
GRANT SELECT ON OBJECT::[sales].[v_orders]
TO [mcp_sales_reader];
GO
-- Nessun accesso diretto alle tabelle interne.
DENY SELECT ON SCHEMA::[internal_sales]
TO [mcp_sales_reader];
DENY SELECT ON SCHEMA::[internal_finance]
TO [mcp_sales_reader];
DENY SELECT ON SCHEMA::[internal_hr]
TO [mcp_sales_reader];
GO
Ripeti il modello con un ruolo diverso per finance e hr. Quando viene
pubblicata una nuova vista, concedila esplicitamente dopo la revisione.
5. Bloccare scrittura, esecuzione e DDL
L'assenza di permessi è già sufficiente a negare le operazioni; i DENY
seguenti rendono esplicita la policy e impediscono che permessi meno restrittivi
ereditati da altri ruoli le riabilitino:
USE [McpReporting];
GO
-- DML: blocca INSERT, UPDATE, DELETE e quindi anche MERGE.
DENY INSERT, UPDATE, DELETE
TO [mcp_sales_reader];
-- Stored procedure e funzioni eseguibili.
DENY EXECUTE
TO [mcp_sales_reader];
-- Controllo, modifica degli oggetti e cambio di proprietà.
DENY ALTER, CONTROL, TAKE OWNERSHIP, VIEW DEFINITION
TO [mcp_sales_reader];
-- Creazione di oggetti nel database.
DENY CREATE TABLE, CREATE VIEW, CREATE PROCEDURE, CREATE FUNCTION,
CREATE SYNONYM, CREATE TYPE, CREATE SCHEMA
TO [mcp_sales_reader];
GO
Questi permessi bloccano anche:
TRUNCATE TABLE, perché richiedeALTERsulla tabella;DROPeALTERdegli oggetti;SELECT ... INTOpermanente, perché l'utente non può creare tabelle;WITH ... INSERT,WITH ... UPDATE,WITH ... DELETEeWITH ... MERGE, perché mancano i relativi permessi DML;- chiamate a stored procedure tramite
EXEC/EXECUTE.
WITH non è un permesso SQL Server separato: introduce una CTE che può essere
usata da SELECT, INSERT, UPDATE, MERGE o DELETE. Con questa policy è
consentita solo quando l'operazione finale è un SELECT autorizzato.
6. Rendere il database read-only, quando possibile
Per snapshot o database aggiornati solo tramite sostituzione completa, una
seconda barriera consiste nel renderli READ_ONLY dopo il caricamento:
USE [master];
GO
ALTER DATABASE [McpReporting]
SET READ_ONLY
WITH ROLLBACK IMMEDIATE;
GO
SELECT
[name],
[is_read_only]
FROM sys.databases
WHERE [name] = N'McpReporting';
GO
Non applicare questa opzione se l'ETL deve aggiornare continuamente lo stesso
database. In quel caso mantieni READ_WRITE, usa un'identità ETL separata e
lascia l'utente MCP con i soli permessi descritti sopra.
7. Query di collaudo
Esegui come amministratore questa verifica, che non modifica dati:
USE [McpReporting];
GO
EXECUTE AS USER = N'mcp_sales_user';
GO
-- Deve riuscire.
SELECT TOP (10)
[CustomerId],
[CustomerName]
FROM [sales].[v_customers];
-- Deve riuscire: WITH termina con SELECT.
WITH [recent_customers] AS
(
SELECT [CustomerId], [CustomerName]
FROM [sales].[v_customers]
)
SELECT *
FROM [recent_customers];
-- SELECT sulla vista deve valere 1; tutti gli altri controlli devono valere 0.
SELECT
HAS_PERMS_BY_NAME(
N'sales.v_customers', N'OBJECT', N'SELECT'
) AS [CanSelectPublishedView],
HAS_PERMS_BY_NAME(
N'internal_sales.Customers', N'OBJECT', N'SELECT'
) AS [CanSelectInternalTable],
HAS_PERMS_BY_NAME(
N'McpReporting', N'DATABASE', N'INSERT'
) AS [CanInsert],
HAS_PERMS_BY_NAME(
N'McpReporting', N'DATABASE', N'UPDATE'
) AS [CanUpdate],
HAS_PERMS_BY_NAME(
N'McpReporting', N'DATABASE', N'DELETE'
) AS [CanDelete],
HAS_PERMS_BY_NAME(
N'McpReporting', N'DATABASE', N'EXECUTE'
) AS [CanExecute],
HAS_PERMS_BY_NAME(
N'McpReporting', N'DATABASE', N'CREATE TABLE'
) AS [CanCreateTable];
GO
REVERT;
GO
Accedi poi con il login MCP reale ed esegui singolarmente le query seguenti. Ognuna deve fallire per autorizzazione:
UPDATE [sales].[v_customers]
SET [CustomerName] = N'blocked'
WHERE [CustomerId] = -1;
DELETE FROM [sales].[v_customers]
WHERE [CustomerId] = -1;
WITH [target] AS
(
SELECT *
FROM [sales].[v_customers]
)
DELETE FROM [target]
WHERE [CustomerId] = -1;
SELECT *
FROM [internal_sales].[Customers];
CREATE TABLE [sales].[ShouldFail] ([Id] int NOT NULL);
Verifica inoltre che la stringa SQLSERVER_CONNECTION_STRING del server MCP
utilizzi mcp_sales_login e punti esplicitamente a McpReporting.
Limite della policy basata sui permessi
I permessi SQL Server impediscono accesso, modifica ed esecuzione sugli oggetti,
ma non costituiscono una allowlist lessicale delle parole SQL: istruzioni di
sessione come SET o controllo di transazione possono essere accettate senza
consentire modifiche ai dati. Per bloccare sintatticamente qualsiasi testo che
non inizi con SELECT o WITH servirebbe anche un parser nel server o un proxy
SQL. La configurazione sopra mantiene invece i guardrail interamente nel
database, come previsto dal progetto, e rende inefficaci le operazioni non
autorizzate.
Riferimenti Microsoft: sicurezza tramite viste,
permessi sugli schema, CTE WITH e
database READ_ONLY.
Test
uv run --extra dev pytest
Su Windows senza uv:
.\.venv\Scripts\python.exe -m pytest
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。