Elekto MCP for SQL Server
OfficialThis is a read-only MCP server for SQL Server 2017+ that lets AI agents explore database structure, inspect schema metadata and object definitions, profile data, and run safe SELECT queries without ever modifying anything.
Discover configuration: list configured databases, see row limits and timeouts, and get setup guidance when nothing is configured.
Survey a database: get high-level overviews, per-schema summaries, schemas, tables, views, procedures, and functions.
Inspect structure: read full table/view structure (columns with unambiguous
type_declaration, extended properties, PKs, FKs, checks, uniques, indexes), and retrieve CREATE text for views, procedures, and functions.Find objects: locate columns by name pattern, filter tables/views by schema or name pattern.
Analyze dependencies: get dependency edges as data, render Graphviz DOT diagrams, and trace everything that references a given table.
Query data safely: run SELECTs with column selection, WHERE, ORDER BY, GROUP BY, aggregates, pagination, and random sampling, capped by
max_query_rowswith a measuredtruncatedflag.Profile data: get null ratios, distinct counts, min/max, and top values per column without paging rows.
Assess index health: find duplicate/unused indexes and missing-index suggestions.
Compare databases: diff table and column structure between two configured databases to check migrations or drift.
Verify permissions: check what the connected login can see, which GRANTs are missing, and whether write access exists.
Fail safely: return
ok: falsewith error, hint, and example instead of throwing; flag incomplete visibility when SQL Server silently hides objects.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Elekto MCP for SQL Servershow me the schema for the Customers table, including indexes and foreign keys"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Elekto.Mcp.Sql
Read-only MCP server for SQL Server 2017+ introspection and querying (tested on 2019 and 2022). Exposes schema metadata, object definitions, and data queries via the MCP protocol (stdio), allowing GitHub Copilot (and other MCP clients, like Claude, etc.) to understand your database structure without storing credentials in the repository.
⚠️ Privacy and Data Security Warning
MCP servers act as a bridge between your local data and AI language models. When you use this server with an AI assistant (such as GitHub Copilot, Claude, or others), the following happens:
The AI agent calls tools on this server to read data from your SQL Server database.
The results — which may include table schemas, stored procedure definitions, or actual row data — are sent back to the AI agent and transmitted to the LLM provider's infrastructure for analysis.
This means your data leaves your machine and is sent to a third-party service (Microsoft, Anthropic, OpenAI, etc.), subject to their respective terms of service and privacy policies.
Before connecting this server to any database, carefully consider:
What data could be read? Does it include PII, financial records, trade secrets, or other sensitive information?
Who is the LLM provider and what are their data retention and privacy policies?
Are you authorized to share this data with that third party under applicable laws and regulations?
Recommendations:
Never connect to databases containing sensitive data unless you have explicitly assessed and accepted this risk.
Use database accounts with the minimum required privileges (read-only, restricted to specific schemas where possible).
Use
max_query_rowsto limit how much data can be returned in a single call.Prefer databases with anonymized or synthetic data for development and exploration.
AI agents can be extremely creative in finding ways to execute a task. Altouht this server is designed to be read-only and to validate all inputs, there is always a risk of unintended consequences when exposing database access to an AI agent.
Regardless of the precautions you take, the responsibility for any consequences arising from the use of this tool rests entirely with you. This software is provided as is with no warranties of any kind.
Related MCP server: MSSQL MCP Python Server
Available Tools
Tool | Description |
| Databases registered in the configuration |
| High-level database summary (counts, size, connection metadata), and whether the login sees everything |
| What the connected login can and cannot see, the GRANT that completes it, and whether it could write |
| Aggregated metrics by schema (objects, rows, size) |
| Schemas in a database (excluding system schemas) |
| User tables with schema, dates, approximate rows and estimated size; filterable by schema and name pattern |
| User views, filterable by schema and name pattern |
| Every table and view holding a column whose name matches a pattern |
| User stored procedures (with basic complexity metrics) |
| User-defined functions (with basic complexity metrics) |
| Columns with unambiguous type declarations, all extended properties, PKs, FKs, checks, uniques and indexes with key order and declared key width |
| DDL definition + columns of a view, with the same column detail |
| CREATE PROCEDURE text |
| CREATE FUNCTION text |
| Object dependency edges (FK + SQL dependencies) |
| References to a table across FKs and SQL modules |
| Column profile (null ratio, distinct count, min/max, top values) |
| Duplicate/unused index diagnostics + missing-index suggestions |
| Compares table/column structure between two configured databases |
| Graphviz DOT dependency graph with node metadata ( |
| SELECT from a table or view with filtering, grouping, secure aggregates, sorting, sampling and pagination |
Upgrading from 1.x
Version 2.0.0 changes what the tools return and how their parameters are declared. Nothing
needs reconfiguring — connection files, .mcp.json and the CLI arguments are unchanged —
but anything that parses the output will notice:
Change | What to do |
| Read the rows from |
Failures return | Test for |
Column results gained | Prefer |
| Nothing; it still works |
Optional tool parameters are no longer nullable | Nothing over MCP. Direct C# callers pass |
New: | Nothing; both are additive |
Everything else — every other tool, every other field — is unchanged.
What changed in 2.1.0
Nothing changes shape: arrays are still arrays and objects keep every field they had. A few values now say "unknown" instead of passing a guess for a fact, which is worth knowing if you parse them:
Change | Why |
New: | Says what the login is missing, per tool, with the GRANT that fixes it |
| So a result the login could only partly see says so |
| A hidden definition used to read as a body of 0 lines |
| Both used to fail the whole call with error 229 |
| It used to fail outright, losing the part that needs no permission |
|
|
SQL errors carry their number, and the hint tells permission, missing name and syntax apart | One generic hint covered all three |
What changed in 2.2.0
No tool result changes shape. What changes is how the server starts and how it is packaged:
Change | Why |
The server starts without any connection, states it in its server instructions, and every tool answers | A server that exited at startup showed only as "failed", and failed in every project when registered for all of them |
A configuration that cannot be read (invalid JSON, a | The error now reaches whoever calls the tools |
While nothing is loaded, every call looks for the configuration again | A connections file created after the server started is used without a restart |
The package is listed among nuget.org's MCP servers and carries | So it can be found there, with a ready configuration on its MCP Server tab |
The process no longer exits with code 1 when the configuration is missing or unreadable. Anything that relied on that exit code should read a tool's answer instead. The README also gained setup instructions for Claude Code and Codex.
What changed in 2.3.0
No tool result changes shape; what changes is what the tools say about themselves, which is what an agent chooses them by:
Change | Why |
Every tool declares a title and the MCP annotations | Clients can show the tools as safe, and agents need not infer it from prose |
Every description says what the tool does, when to use it and which tool to use instead | So an agent picks the right one of 21 tools the first time |
The server always sends instructions on connecting, describing how the tools fit together | Previously it sent them only when no connection was configured |
What changed in 2.3.1
Nothing changes shape. generate_dependency_dot now says which way its arrows point, what it returns
besides the DOT text, how to render it and when another tool fits better. The server is listed in
the MCP Registry as "Elekto MCP for SQL Server", and each release now also appears under the
repository's GitHub Releases.
What changed in 2.3.2
Nothing changes shape. When no connection is configured, the guidance returned to the agent now
tells it to agree with the user before writing anything, and never to write a password into the
connections file or anywhere else, using %{VARIABLE} in its place. It also points to connections
the project may already keep where this server does not look (spring.datasource.url in
application.properties or application.yml, or a sqlserver:// or mssql:// URL in a .env
file), for the agent to show the user rather than for the server to parse.
Reading the Results
Three things about the shape of what comes back are worth knowing before you rely on it.
Column types are reported twice, on purpose
sys.columns.max_length is documented in bytes. A nvarchar(250) column therefore
reports 500, and reading that as characters is wrong by a factor of two — silently, because
nothing downstream contradicts it. That raw value is still reported, for fidelity to the
catalog, but never on its own:
{
"column_name": "Tag1",
"data_type": "nvarchar",
"type_declaration": "nvarchar(250)",
"max_length": 500,
"max_length_chars": 250
}Use type_declaration or max_length_chars. They cannot be misread.
Columns also carry extended_properties — every property, not only MS_Description —
so an application's own conventions (display formats, units, masks) are visible, and
is_persisted for computed columns.
query_table returns an envelope, not a bare array
A bare array of two rows cannot be told apart from a table that holds two rows. The result therefore states what it is:
{
"table": { "schema": "Feeder", "name": "GenericSecurity" },
"row_count": 100,
"truncated": true,
"top_applied": 100,
"skip": 0,
"max_query_rows": 10000,
"rows": [ ... ]
}truncated is measured rather than inferred: one row past the limit is fetched and
discarded. top_requested appears only when max_query_rows overrode what was asked for.
Failures come back as content
An exception thrown from an MCP tool does not reach the caller — the host replaces it with
a generic line. Failures are therefore returned as a normal result carrying ok: false:
{
"ok": false,
"tool": "query_table",
"error": "'columns' looks like JSON: [\"Source\", \"Name\"]",
"hint": "'columns' is a plain comma-separated string, not a JSON array or object.",
"example": { "columns": "Source, Name, ReferenceDate" }
}The trade-off is deliberate: the host no longer marks the call as an error, but the caller
can read what went wrong and correct it. Successful results are unchanged and never carry
an ok field.
What the login cannot see is left out without an error
SQL Server shows a login only the objects it holds some permission on (metadata visibility),
and it filters the rest out silently. A login in db_datareader alone sees every table but
not one procedure it cannot execute, and sees views without their text. Every catalog query
then returns a normal, well-formed, short answer — "this database has 6 procedures" when
it has 672.
Since the data cannot reveal that, the server checks the permissions that decide it and says
so. get_database_overview, the tool to call first, carries:
"visibility": {
"complete": false,
"notes": [
"The login lacks VIEW DEFINITION on the database. SQL Server then leaves out, without any error, every procedure, function and view the login holds no permission on, so their listings and counts may be short.",
"756 of the 756 visible views, procedures and functions have their definition hidden from this login."
],
"hint": "Call check_permissions for what this login is missing and the GRANT statements that fix it."
}check_permissions then reports, for each group of tools, complete, partial or
unavailable, the permission that is missing and the GRANT that adds it, in the syntax of the
server's version. It also reports write_access: whether a role, a database permission or a
schema- or object-level GRANT lets the login change anything, so that an account meant to be
read-only can be checked rather than assumed.
Installation
As a .NET global tool (recommended)
Requires .NET 10 Runtime or SDK.
dotnet tool install -g Elekto.Mcp.SqlUpgrade to a newer version:
dotnet tool update -g Elekto.Mcp.SqlAfter installation the elekto-mcp-sql command is available on PATH.
Use it directly in .mcp.json — no path needed:
{
"servers": {
"sql": {
"type": "stdio",
"command": "elekto-mcp-sql"
}
}
}Zero-config: if your project already has a
ConnectionStringssection inappsettings.json,web.configorApp.config, the server picks it up automatically and no further configuration is required.
For Claude Code and Codex, see Claude Code Setup and Codex Setup.
Without installing (dnx)
The .NET 10 SDK (not the runtime alone) brings dnx, which downloads the package from NuGet
and runs it, with no dotnet tool install:
{
"servers": {
"sql": {
"type": "stdio",
"command": "dnx",
"args": ["Elekto.Mcp.Sql", "--yes"]
}
}
}The package is listed among the MCP servers on nuget.org, whose MCP Server tab gives this configuration ready to paste into VS Code or Visual Studio.
From a local publish (air-gapped / corporate environments)
cd src
dotnet publish -c Release -o C:\Tools\Elekto.Mcp.Sql{
"servers": {
"sql": {
"type": "stdio",
"command": "dotnet",
"args": ["C:\\Tools\\Elekto.Mcp.Sql\\Elekto.Mcp.Sql.dll"]
}
}
}Configuration
--connections <path>, when given, is used on its own and no other source is consulted.
Otherwise every source below is read and merged, so a database defined in one source and a database defined in another are both available. Where the same database name appears in more than one, the higher-priority source wins:
Priority | Source |
1 (highest) |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 (lowest) |
|
Note that the home-directory file sits below the project's appsettings.json: it holds
your defaults, and the project it is used in overrides them.
At startup the server logs every source that contributed to stderr, making it easy to diagnose which files are in effect.
Zero-config for existing .NET projects
If your project already has appsettings.json or web.config with a ConnectionStrings
section, the server will pick them up automatically — no extra file needed.
Be Careful: the automatic discovery is convenient but may use a project connection too powerful for safe use with AI agents.
If your existing connection strings have write permissions or access to sensitive data, consider using a separate connections file
with read-only credentials and specifying it explicitly via --connections or by placing it in the project root.
Connection file format
The file is a JSON object mapping logical database names to their configurations.
Simple format (direct connection string):
{
"MyDatabase": "Server=SQLSRV01\\INST;Database=MyDatabase;Integrated Security=SSPI"
}Full format (with options):
{
"MyDatabase": {
"connection_string": "Server=SQLSRV01\\INST;Database=MyDatabase;Integrated Security=SSPI",
"max_query_rows": 5000,
"default_timeout_seconds": 30
}
}Both formats can be mixed in the same file. See sample-connections.json
for a ready-to-use example.
The recommended location for the local file is the project root (auto-discovered) or ~
(shared across all projects). The file can hold credentials, so add .elekto.mcp.sql.local.json
to your project's .gitignore, as this repository does.
Options per database
Option | Type | Default | Description |
| string | required | SQL Server connection string |
| integer | 10 000 | Maximum rows returned per query call |
| integer | 30 | SQL command timeout in seconds |
Environment variable expansion in connection strings
Use %{VARIABLE_NAME} inside connection strings to avoid storing credentials in plain text.
Variables are resolved from the process environment at server startup.
{
"CRM": {
"connection_string": "Server=SQLSRV01;Database=CRM;User Id=%{CRM_DB_USER};Password=%{CRM_DB_PASS}",
"max_query_rows": 2000
}
}%{CRM_DB_USER} and %{CRM_DB_PASS} are replaced by the values of the corresponding
OS environment variables. If a referenced variable does not exist, no connection is loaded
and every tool names the missing variable (see When no connection is configured).
Fallback: MCP_SQL_CONNECTIONS environment variable
If --connections is not supplied, the server falls back to reading the
MCP_SQL_CONNECTIONS environment variable, which must contain the JSON directly.
This is provided for backward compatibility; the file-based approach is recommended.
When no connection is configured
The server starts even when it finds no connection, or when the configuration cannot be read
(invalid JSON, a %{VARIABLE} that is not set, a --connections file that does not exist).
An MCP client shows a server that exits at startup only as "failed", with the reason buried
in a log; a server that answers can say what is missing. So it:
states the problem in its server instructions, which the client hands to the model as soon as it connects;
answers every tool with
ok: false, what is wrong, the file to create, every place it looked and an example of the content:
{
"ok": false,
"tool": "list_databases",
"error": "No database connection is configured: none of the places this server reads holds a connection string.",
"hint": "Ask the user for a SQL Server connection string, preferably of a login that can only read, and save it in the file named in 'setup.file', with the shape shown in 'example'. ...",
"example": {
"MyDatabase": "Server=SQLSRV01;Database=MyDatabase;Integrated Security=True;TrustServerCertificate=True",
"Reporting": { "connection_string": "...User Id=%{REPORTING_USER};Password=%{REPORTING_PASS}...", "max_query_rows": 5000 }
},
"setup": {
"file": "C:\\Projects\\Risk\\.elekto.mcp.sql.local.json",
"file_for_every_project": "C:\\Users\\YourName\\.elekto.mcp.sql.local.json",
"searched": [ "C:\\Projects\\Risk\\.elekto.mcp.sql.local.json", "ConnectionStrings in C:\\Projects\\Risk\\appsettings.Development.json", "..." ],
"notes": [ "..." ],
"documentation": "https://github.com/elekto-com-br/elekto-mcp-sql#configuration"
}
}looks again on every call while nothing is loaded, so a connections file created afterwards (by you, or by the agent once you give it a connection string) is used by the next call, with no restart.
Once connections are loaded they are kept until the server restarts, so a change to a file
already read needs a restart of the server (in Claude Code, /mcp and reconnect it; in Codex,
start Codex again).
Claude Code Setup
Install the tool (see Installation) and register it with claude mcp add.
Everything after -- is the command Claude Code runs:
dotnet tool install -g Elekto.Mcp.Sql
claude mcp add sql -- elekto-mcp-sqlClaude Code starts the server in the project directory, so the zero-config discovery works as
it does in Visual Studio: a .elekto.mcp.sql.local.json, or the ConnectionStrings of
appsettings.json / web.config, in the project is found with no arguments.
-s (--scope) chooses where the registration is kept:
Scope | Command | Applies to |
|
| This project, for you only (kept in |
|
| Every project, for you |
|
| This project, for everyone who clones it (writes |
Registered with -s user, the server also starts in projects that have no database; there the
tools say how to add a connection instead of failing (see
When no connection is configured).
Other forms:
# An explicit connections file; no other source is read
claude mcp add sql -- elekto-mcp-sql --connections C:\Users\YourName\sql-connections.json
# Without installing the tool (needs the .NET 10 SDK, which brings dnx)
claude mcp add sql -- dnx Elekto.Mcp.Sql --yesOn Windows the installed command is a real executable (%USERPROFILE%\.dotnet\tools\elekto-mcp-sql.exe),
so it needs no cmd /c wrapper, unlike servers started through npx.
Secrets. claude mcp add -e NAME=value stores the value in plain text in ~/.claude.json,
or in .mcp.json with -s project, which is usually committed. For a password, write
%{NAME} in the connection string and set NAME as an environment variable of your user
account instead: Claude Code passes its environment on to the server.
Check the registration with claude mcp list (the server should show Connected), or with
/mcp inside Claude Code, which also lists the tools and reconnects the server.
A .mcp.json written by hand for Claude Code uses the key mcpServers, where Visual Studio
and VS Code use servers:
{
"mcpServers": {
"sql": {
"type": "stdio",
"command": "elekto-mcp-sql",
"args": []
}
}
}Codex Setup
Codex, OpenAI's coding agent (CLI, IDE extension and app),
runs local MCP servers as well. Install the tool (see Installation) and register
it with codex mcp add. Everything after -- is the command Codex runs:
dotnet tool install -g Elekto.Mcp.Sql
codex mcp add sql -- elekto-mcp-sqlThis adds the server to ~/.codex/config.toml (%USERPROFILE%\.codex\config.toml on Windows),
so it applies to every project:
[mcp_servers.sql]
command = "elekto-mcp-sql"Codex starts the server in the project directory, so the zero-config discovery works here too. In a project with no database the tools say how to add a connection (see When no connection is configured).
To register the server for one project only, put the same table in .codex/config.toml at the
project root instead. Codex reads that file only in projects marked as trusted.
codex mcp list and codex mcp get sql show the configuration. Unlike claude mcp list, they do
not start the server, so a mistake in the command only shows when a Codex session starts.
Environment variables are not passed on
Unlike Claude Code, Codex starts an MCP server with a short list of environment variables of its
own (such as HOME and PATH), not with all of yours. A %{VARIABLE} in a connection string is
therefore not found unless the variable is listed in env_vars, which forwards it from your
environment:
[mcp_servers.sql]
command = "elekto-mcp-sql"
env_vars = ["CRM_DB_USER", "CRM_DB_PASS"]Without it the server still starts, and every tool names the variable it could not find.
codex mcp add --env NAME=value sets a value directly (the env table), but stores it in plain
text in config.toml. Keep it for values that are not secret.
Other forms
An explicit connections file, with no other source read:
[mcp_servers.sql]
command = "elekto-mcp-sql"
args = ["--connections", 'C:\Users\YourName\sql-connections.json']A Windows path goes in single quotes, which TOML takes literally. Inside double quotes the
\U of C:\Users is read as an escape sequence and Codex refuses the whole file; with double
quotes every backslash must be doubled. codex mcp add writes single quotes by itself.
Without installing the tool (needs the .NET 10 SDK, which brings dnx):
[mcp_servers.sql]
command = "dnx"
args = ["Elekto.Mcp.Sql", "--yes"]
startup_timeout_sec = 30The first start downloads the package, which can take longer than Codex waits for a server by
default; startup_timeout_sec gives it more time. The installed tool starts in under a second and
needs no such setting.
If Codex runs inside WSL, it starts the server inside Linux, which cannot see the tool installed
on Windows: install .NET and the tool in WSL as well. Integrated Security from Linux also needs
Kerberos configured; a SQL Server login, with its password in a %{VARIABLE} listed in
env_vars, is simpler there.
Visual Studio 2026 Setup (.mcp.json)
Create or edit .mcp.json at the solution root (or in your user profile for global use).
Recommended: local connections file (zero-config)
Drop a .elekto.mcp.sql.local.json file in the project root or in ~; the server
finds it automatically. No arguments needed in .mcp.json:
{
"servers": {
"sql": {
"type": "stdio",
"command": "dotnet",
"args": ["D:\\Tools\\Elekto.Mcp.Sql\\Elekto.Mcp.Sql.dll"]
}
}
}Alternative: explicit path via --connections
Point the server to any file via --connections. Useful when the file lives outside the
project tree or when you need to switch between profiles:
{
"servers": {
"sql": {
"type": "stdio",
"command": "dotnet",
"args": [
"D:\\Tools\\Elekto.Mcp.Sql\\Elekto.Mcp.Sql.dll",
"--connections",
"C:\\Users\\YourName\\sql-connections.json"
]
}
}
}The connection file itself stays outside the repository, so credentials are never committed to source control.
Alternative: environment variable (legacy)
If you prefer not to use a file, you can still pass the JSON via an environment variable.
Note that backslashes require double escaping inside JSON-within-JSON (\\\\):
{
"servers": {
"sql": {
"type": "stdio",
"command": "dotnet",
"args": ["D:\\Tools\\Elekto.Mcp.Sql\\Elekto.Mcp.Sql.dll"],
"env": {
"MCP_SQL_CONNECTIONS": "{\"MyDb\": {\"connection_string\": \"Server=SQLSRV01\\\\INST;Database=MyDb;Integrated Security=SSPI\"}}"
}
}
}
}After saving .mcp.json, Copilot automatically restarts the server.
Tools are disabled by default: enable them in the Copilot Chat tools panel.
Build and Publish
cd Elekto.Mcp.Sql\src
dotnet publish -c Release -o C:\Tools\Elekto.Mcp.SqlRequires .NET 10 installed on the machine. The published directory is ~7 MB (NuGet dependencies). For internal use, this is preferred over self-contained (~81 MB).
dotnet pack also packs src/.mcp/server.json, which tells nuget.org
how to start the server. The file in the repository carries $version$ where the version
goes, and the pack writes the version being packed in its place, so there is nothing to
update in it for a release.
Each tag also publishes the server to the MCP Registry
as io.github.elekto-com-br/elekto-mcp-sql, once nuget.org has indexed the new version. The
registry accepts the package as ours only if its README holds the line
mcp-name: io.github.elekto-com-br/elekto-mcp-sql, which is why the comment at the top of this
file must stay; CI fails if the packed README loses it.
Running the tests
From the repository root:
dotnet testThe test project holds two kinds of tests:
Unit tests (such as
ConnectionConfigTests) need nothing beyond the .NET SDK.Integration tests (
SchemaReaderTests) run against a real SQL Server. Each run creates a database namedElektoMcpTest, fills it with a small seed schema, and drops it at the end.
The integration tests look for a SQL Server in this order and use the first one found:
The
ELEKTO_MCP_SQL_CONN_TESTenvironment variable. If set, it must hold a connection string to a server the tests may use. The database named in it, if any, is ignored. The login needs permission to create and drop databases. If this server cannot be reached, the tests fail right away instead of trying the next options, since an explicit choice that does not work is an error worth seeing.LocalDB, only on Windows and only if the
(localdb)\MSSQLLocalDBinstance answers.A disposable container started through Testcontainers from the
mcr.microsoft.com/mssql/server:2022-latestimage. This needs Docker (rootless Docker works too). The container is started only when an integration test needs it, and it is removed when the run ends. The first run also downloads the image, which takes a while.
If none of these is available, the integration tests fail with a message listing what was tried and why each option did not work. The test output states which server was used.
Some of the integration tests run as restricted logins, to check what the tools say when SQL Server
hides objects. The fixture creates those logins (ElektoMcpTest_*) and drops them at the end, which
needs ALTER ANY LOGIN as well; a server that does not allow it reports those tests as skipped,
with the reason.
Example, pointing the tests at an existing server:
export ELEKTO_MCP_SQL_CONN_TEST="Server=localhost,1433;User Id=sa;Password=<password>;TrustServerCertificate=True"
dotnet testDo not point ELEKTO_MCP_SQL_CONN_TEST at a server where a database called ElektoMcpTest
matters to anyone: the tests drop and recreate it.
To run only the unit tests, with no SQL Server at all:
dotnet test --filter "FullyQualifiedName!~SchemaReaderTests"Recommended permissions
Everything the tools read, with no permission to change anything:
USE [YourDatabase];
CREATE USER [mcp_reader] FOR LOGIN [mcp_reader]; -- if the user does not exist yet
ALTER ROLE db_datareader ADD MEMBER [mcp_reader]; -- tables and views; also covers sys.sql_expression_dependencies
GRANT VIEW DEFINITION TO [mcp_reader]; -- procedures, functions, view text, complete dependencies
USE master;
GRANT VIEW SERVER STATE TO [mcp_reader]; -- get_index_health: unused and missing indexesOn SQL Server 2022 and later the last grant can be narrowed to the permission the index DMVs actually need:
USE master;
GRANT VIEW SERVER PERFORMANCE STATE TO [mcp_reader];Permission | Without it |
| Tables and views the login holds no grant on are left out; |
| Procedures and functions are left out, view and module text is hidden, dependencies are incomplete |
|
|
None of these lets the login change data or schema. Run check_permissions against the
database to see which are missing — it prints the statements for that login and that
server version.
Limits and Security
Read-only: only SELECT on tables and views. DML and procedure/function execution are not supported.
query_tablebuilds SQL internally from validated parameters. Identifiers (table, schema, columns) are validated against a regular expression before being composed into SQL.The WHERE clause is accepted as free text (necessary for flexibility), but DML is impossible since the command is always built as
SELECT TOP n ... FROM [t] WHERE ....max_query_rowscaps the maximum number of rows returned per database (default 10,000). Thetopparameter inquery_tableis always clamped to this value, and the result says so viatop_requestedandtruncatedrather than quietly returning a short answer.Even so, avoid exposing this server in untrusted environments or with sensitive data. Use firewalls and access policies to restrict who can execute queries via MCP. Use database accounts with the minimum required privileges (read-only) for all configured connections.
Available Tools
21 toolscheck_permissionsCheck login permissionsARead-onlyIdempotent
Reports what the connected login can and cannot see in a database: identity, roles, effective permissions, server version and, for each group of tools, whether it sees everything ('complete'), only part ('partial') or nothing ('unavailable'), with the missing permission and the GRANT that adds it in this server version's syntax. Also reports 'write_access', so an account meant to be read-only can be verified rather than assumed. Call it when a listing looks short, a definition comes back hidden, get_database_overview reports incomplete visibility, or a tool fails with a permission error: SQL Server leaves out what a login cannot see without raising any error.
| Name | Required | Description | Default |
|---|---|---|---|
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, so safety is covered; the description goes well beyond by disclosing the return contents ('complete'/'partial'/'unavailable'), that it includes the missing permission and a GRANT in this server version's syntax, and the non-obvious SQL Server behavior of silent filtering. The write_access note adds a verification rationale not present in annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One dense paragraph that is front-loaded with what it reports, then when to call it. Every clause carries information, though the enumeration of return fields plus trigger list makes it heavier than strictly necessary for a one-parameter tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description must carry the return-value burden, and it does so thoroughly: visibility states, missing permission, version-specific GRANT, and write_access. Combined with the explicit trigger list, an agent has everything needed to call and interpret it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Single parameter with 100% schema description coverage, so the schema already documents 'database'. The description adds no format or naming nuance beyond what the schema provides, which is the expected baseline when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Reports what the connected login can and cannot see in a database') and enumerates exactly what it returns: identity, roles, effective permissions, server version, per-tool-group visibility states, and write_access. This is clearly distinguishable from the listing/definition siblings, which fetch schema objects rather than diagnose visibility.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit triggering conditions: a listing looks short, a definition comes back hidden, get_database_overview reports incomplete visibility, or a tool fails with a permission error. It even names a sibling (get_database_overview) as the signal source and explains the failure mode ('SQL Server leaves out what a login cannot see without raising any error'), so the agent knows when to reach for this instead of assuming missing data.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
compare_schemasCompare two databasesARead-onlyIdempotent
Compares the table and column structure of two configured databases, such as development and production: tables present in only one, columns present in only one, and columns whose type or nullability differ, each with its type_declaration so a difference reads as 'nvarchar(250) vs nvarchar(50)'. Use it to check a migration or detect drift. It compares tables and columns only, not views, code, indexes or data; to see one differing table in full, call get_table_schema on each database.
| Name | Required | Description | Default |
|---|---|---|---|
| source_schema | No | Source schema filter. Empty means every schema. | |
| target_schema | No | Target schema filter. Empty means every schema. | |
| source_database | Yes | Source database name as registered in configuration. | |
| target_database | Yes | Target database name as registered in configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent and non-destructive, so safety is covered; the description adds real behavioral context by scoping the comparison to tables and columns and by disclosing that differences carry a type_declaration rendered as 'nvarchar(250) vs nvarchar(50)'. It does not describe pagination, output size, or how large diffs are presented, keeping it short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The core action and its diff categories are front-loaded, then usage, then scope exclusions and the alternative. Dense but every clause carries information, including the illustrative type_declaration example.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by explaining exactly what the result contains (one-sided tables, one-sided columns, type/nullability mismatches with type_declaration). Combined with the explicit scope boundary and the handoff to get_table_schema, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with source/target_database and the empty-means-every-schema filters fully documented in the schema, so the baseline is 3. The description mentions 'two configured databases' but adds no format or constraint detail beyond what the schema already provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb (compares) and resource (table and column structure of two configured databases) and enumerates the exact diff categories it produces. An agent can distinguish it from siblings like get_table_schema or get_schema_summary without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
States when to use it ('check a migration or detect drift'), what it explicitly does not cover ('tables and columns only, not views, code, indexes or data'), and routes the agent to a named alternative ('to see one differing table in full, call get_table_schema on each database'). This is explicit when/when-not/alternative guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find_columnsFind columns by nameARead-onlyIdempotent
Finds every table and, by default, every view holding a column whose name matches a pattern, with each column's type_declaration and nullability. Use it to answer 'which objects have this column?' before a rename, a widening or an impact review; to find objects by their own name use list_tables or list_views.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Restrict to one schema. Empty means every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. | |
| include_views | No | Include views as well as tables. True by default. | |
| column_pattern | Yes | Part of a column name. A pattern without % matches anywhere in the name, so 'Date' finds ReferenceDate and AuxDate; add % yourself for a prefix or suffix match, as in 'Aux%'. Example: 'ReferenceDate' |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false and a closed-world scope, so safety is covered. The description still adds behavioral value by disclosing the default inclusion of views and the content of each result (type_declaration and nullability).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight clauses: the capability first, then the routing guidance. No filler, and the scope constraint (tables plus default views) is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema present, the description compensates by naming the returned fields (type_declaration, nullability) and the default view behavior. Combined with the fully documented input schema and safety annotations, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all four parameters in detail, including the unanchored pattern semantics and the schema default. The description only echoes the view default and adds no parameter detail beyond the schema — the expected baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: finds every table and (by default) every view holding a column matching a name pattern. It also names the sibling tools (list_tables, list_views) that find objects by their own name, so an agent can discriminate without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit scenarios — before a rename, a widening, or an impact review — and explicitly routes the alternative case ('to find objects by their own name') to list_tables or list_views. When and when-not are both covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
generate_dependency_dotDependency diagram (DOT)ARead-onlyIdempotent
Generates a Graphviz DOT diagram of the dependencies between database objects: an arrow runs from each table, view, procedure or function to the object it depends on, through a foreign key or a reference in its code. Returns the DOT text in 'dot', ready for 'dot -Tsvg', and the same graph as 'nodes' (with node_kind) and 'edges' (with dependency_kind). Use it to draw the graph; for the edges alone use get_dependency_graph, and for what references a single table use get_table_usage. A whole database makes an unreadable diagram, so narrow it with schema. 'visibility' says when references from code are missing for lack of permission.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Filter by schema name. Empty means every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, but the description adds real context beyond them: the return payload structure ('dot', 'nodes' with node_kind, 'edges' with dependency_kind) and a permission caveat via 'visibility'. The caveat that references from code can be missing for lack of permission is valuable behavioral disclosure that annotations do not carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with purpose and edge semantics, then output shape, then alternatives and caveats. Dense but nearly every clause earns its place; the mention of 'visibility' without saying whether it is an input or output field adds slight ambiguity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description carries the return-value burden and does so by naming the 'dot', 'nodes' and 'edges' fields. Combined with alternatives, the schema-filter caveat and the permission caveat, an agent has everything needed to call and interpret this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3; the description goes further by explaining the practical consequence of the schema filter ('a whole database makes an unreadable diagram, so narrow it with schema'), which gives the agent a reason to set it rather than just its format.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Generates a Graphviz DOT diagram of the dependencies between database objects') and immediately clarifies the edge semantics (arrow from each object to what it depends on, via FK or code reference). It is clearly distinguishable from the sibling get_dependency_graph, which returns edges only.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: use this to draw the graph, use get_dependency_graph for edges alone, and get_table_usage for references to a single table. It also warns that a whole-database diagram is unreadable and instructs narrowing with schema, which is actionable when-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_database_overviewDatabase overviewARead-onlyIdempotent
Summarizes one database: real name, server and instance, connected login, counts of tables, views, procedures, functions and schemas, and allocated size in MB. Use it after list_databases to size up a database before exploring it; for the same figures per schema use get_schema_summary. Counts cover only what the login can see: 'visibility.complete' is false when SQL Server hides objects, and check_permissions then says which GRANT is missing.
| Name | Required | Description | Default |
|---|---|---|---|
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, and the description goes well beyond them: counts are scoped to what the login can see, the 'visibility.complete' flag is explained as false when SQL Server hides objects, and check_permissions is named as the follow-up for missing GRANTs. This is real behavioral disclosure an agent cannot get from the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, front-loaded with what is returned, then usage, then a caveat about visibility. Every clause carries information; nothing is redundant with the name or title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by enumerating the returned figures and even naming the 'visibility.complete' field, and it explains the degraded-visibility case plus the remediation path. Nothing needed to call or interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single parameter is fully documented as 'Name of the database as registered in the configuration.' The description doesn't add format or lookup detail for the parameter itself, so the baseline of 3 applies — the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb ('Summarizes one database') plus an explicit enumeration of what the summary contains (real name, server/instance, login, object counts, allocated MB). This is clearly distinguishable from siblings like get_schema_summary and list_databases.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
States the workflow position explicitly ('after list_databases to size up a database before exploring it') and names the alternative for a different granularity ('for the same figures per schema use get_schema_summary'). When-to-use and the sibling selector are both spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_data_profileProfile column dataARead-onlyIdempotent
Profiles the columns of one table or view: null ratio, distinct count, minimum, maximum and most frequent values. Use it to learn how data is distributed without paging through rows with query_table. It reads actual row values and scans the table, so on a large table it can take a while; limit it to the columns you need.
| Name | Required | Description | Default |
|---|---|---|---|
| table | Yes | Table name, bare, with no schema prefix. | |
| schema | No | Table schema. Empty means dbo. Example: 'Feeder' | |
| columns | No | Columns to profile as ONE comma-separated string, not a JSON array. Example: 'Source, Name'. Empty profiles every column. | |
| database | Yes | Name of the database as registered in the configuration. | |
| top_values | No | Top frequent values to return per column (default 5). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish that this is a safe, idempotent, non-destructive read, so the description needn't restate safety. It does add genuinely useful behavioral context beyond the annotations: it 'reads actual row values and scans the table', so on a large table 'it can take a while'. That performance warning is the kind of thing an agent needs and annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two dense sentences with zero filler: the first defines what it returns, the second covers usage and cost. Nothing is buried and nothing is repeated from the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by naming the exact metrics returned per column, and it flags the cost profile of the operation. For a read-only profiling tool of this complexity, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all five parameters are already documented, including the comma-separated column format and the top_values default. The description only echoes the columns-scoping idea ('limit it to the columns you need') without adding syntax or format detail. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (profiles) and resource (columns of one table or view), then enumerates the exact metrics returned: null ratio, distinct count, min, max, most frequent values. This is unmistakable against siblings like query_table or get_table_schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent away from the alternative: 'without paging through rows with query_table', and adds the scoping advice 'limit it to the columns you need'. Both when-to-use and a practical constraint are stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_dependency_graphDependency graphARead-onlyIdempotent
Returns the dependency edges between database objects as data: foreign keys between tables, and references among views, procedures and functions. Use it to analyze dependencies programmatically; for a diagram use generate_dependency_dot, and for what references one table use get_table_usage. Fails, saying so, when the login cannot read sys.sql_expression_dependencies; without VIEW DEFINITION the module edges cover only modules the login can read.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Filter by schema name. Empty means every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, but the description adds substantive operational context: it fails with an explicit message when the login lacks read access to sys.sql_expression_dependencies, and it warns that without VIEW DEFINITION the module edges only cover readable modules — i.e. results can be silently partial. That is exactly the beyond-annotation disclosure this dimension rewards.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, each carrying distinct load: what it returns, when to use it vs alternatives, then failure and permission caveats. Nothing is redundant and the purpose is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description must describe the return — and it does, specifying that the result is edge data with concrete edge categories. Combined with the failure/permission caveats, an agent has everything needed to call it and interpret partial results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both parameters (schema filter, database name) are already documented in the schema, and the description adds no syntax or format detail beyond that. Baseline 3 is correct when the schema carries the parameter burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('returns the dependency edges between database objects as data') and enumerates the covered edge types (foreign keys, view/procedure/function references). It explicitly distinguishes itself from siblings generate_dependency_dot and get_table_usage, so an agent can route without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says to use it 'to analyze dependencies programmatically' and names the two alternatives with the conditions that select them: a diagram via generate_dependency_dot, and single-table references via get_table_usage. When/when-not is fully covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_function_definitionUser function definitionARead-onlyIdempotent
Returns the CREATE FUNCTION text of one user-defined function. Use it to read or review a function found with list_functions; this server never executes functions. A function the login can see but not read comes back with definition null and definition_visible false.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Function schema. Empty searches every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. | |
| function | Yes | Function name, bare, with no schema prefix. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, and the description adds substantive behavior beyond them: the server never executes functions, and unreadable-but-visible functions return definition null with definition_visible false. That permission-driven return behavior is exactly the kind of trait an agent could not infer from the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, zero filler, with the core purpose front-loaded and the permission caveat and non-execution guarantee following logically. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, and the description compensates by explaining the returned payload (CREATE FUNCTION text) and the null/false outcome for restricted functions. With a well-documented 3-param schema and covered safety annotations, nothing an agent needs is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% (schema, database, function all documented with examples and defaults), so the schema carries parameter meaning. The description adds nothing further about parameter format or the empty-schema wildcard behavior, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb and resource: 'Returns the CREATE FUNCTION text of one user-defined function.' It clearly differentiates itself from siblings like get_procedure_definition and get_view_definition by naming the exact object type and payload.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names the workflow it belongs to ('use it to read or review a function found with list_functions') and clarifies the server never executes functions, giving clear context for selection. It stops short of stating explicit when-not-to-use conditions or naming a directly competing sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_index_healthIndex healthARead-onlyIdempotent
Reports index health by schema: duplicate index candidates, unused indexes and missing-index suggestions. Use it when reviewing indexing or slow queries; for one table's indexes use get_table_schema. Unused and missing indexes come from server DMVs, which reset when SQL Server restarts; without VIEW SERVER STATE they come back null and 'visibility.unavailable' says why, while duplicates are still reported.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Filter by schema name. Empty means every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent annotations to disclose that unused and missing-index data comes from server DMVs, that these reset on SQL Server restart, that without VIEW SERVER STATE the values come back null, and that 'visibility.unavailable' explains why while duplicates are still reported. This is exactly the kind of operational caveat an agent needs to interpret results correctly.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tightly packed sentences: purpose/outputs first, routing guidance second, data caveats third. No filler, and the key scoping information is front-loaded. It is dense but every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the return-value burden and does so by naming the three result categories and explaining the null/'visibility.unavailable' failure mode. Combined with the sibling routing and permission caveat, an agent has everything needed to call and interpret this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters (schema filter, database) are already documented in the input schema, including the empty-string default and an example. The description reinforces the by-schema scoping but adds no syntax or format detail beyond the schema, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Reports) and resource (index health) and enumerates the three output categories: duplicate index candidates, unused indexes, and missing-index suggestions. It also names the sibling it is not (get_table_schema for a single table), so an agent can distinguish it without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to use it ('when reviewing indexing or slow queries') and routes the agent to an alternative for a narrower case ('for one table's indexes use get_table_schema'). Both the selection condition and the exclusion are stated, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_procedure_definitionStored procedure definitionARead-onlyIdempotent
Returns the CREATE PROCEDURE text of one stored procedure. Use it to read or review a procedure found with list_procedures; this server never executes procedures. A procedure the login can see but not read comes back with definition null and definition_visible false.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Procedure schema. Empty searches every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. | |
| procedure | Yes | Stored procedure name, bare, with no schema prefix. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnly/idempotent/non-destructive, so the safety profile is covered. The description adds genuinely useful behavior beyond that: it never executes procedures, and a procedure the login can see but not read returns definition null with definition_visible false — a non-obvious permission edge case.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each earning its place: what it returns, when to use it, and the permission edge case. Front-loaded with the core purpose with zero filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description carries the return-value burden and does so: it names the payload (CREATE PROCEDURE text) and the null/definition_visible failure shape. Combined with full param coverage and annotations, an agent has everything needed to call and interpret it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents schema, database, and procedure semantics including wildcard behavior for empty schema. The description adds no parameter-level detail beyond that, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns the CREATE PROCEDURE text of one stored procedure'), scoping it to a single procedure and distinguishing it from the listing siblings. An agent can tell instantly that this reads source text, not metadata or execution results.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: use it to read or review a procedure found with list_procedures, and clarifies that this server never executes procedures. Clear context, though it does not name alternatives such as get_function_definition or state when-not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_schema_summarySchema summaryARead-onlyIdempotent
Summarizes each schema of a database: counts of tables, views, procedures and functions, approximate rows and estimated data and index size. Use it to see where a database's weight lies before exploring or planning a refactoring; for whole-database totals use get_database_overview, and for the tables of one schema use list_tables.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Filter by schema name. Empty means every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, non-destructive, and closed-world, so the safety profile is fully covered. The description adds useful content-level detail about what the summary contains (row estimates, index/data size), which matters since there is no output schema, but it says nothing about cost, limits, or behavior on unregistered/large databases. With annotations carrying the safety burden, a 3 is appropriate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero filler: the first delivers the payload contents, the second front-loads the routing decision before naming alternatives. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only aggregate tool with no output schema, the description supplies the return contents, the intended planning use case, and disambiguation from the two nearest siblings. Nothing an agent needs in order to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%: both parameters are documented in the schema, including the empty-string default meaning 'every schema' and an example value. The description adds no parameter-level syntax or filtering semantics beyond what the schema already provides, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (summarizes) and resource (each schema of a database) and enumerates exactly what is produced: table/view/procedure/function counts, approximate rows, and estimated data/index size. It explicitly distinguishes itself from get_database_overview and list_tables, so an agent can place it among 20 siblings without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit use case ('before exploring or planning a refactoring') and names two alternatives with the conditions that select them: whole-database totals -> get_database_overview, tables of one schema -> list_tables. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_table_schemaTable structureARead-onlyIdempotent
Returns the full structure of one table or view: columns with an unambiguous type_declaration (such as 'nvarchar(250)'), max_length_chars, every extended property, computed-column definitions and whether they are persisted; plus primary key, foreign keys, check and unique constraints, and indexes with key column order and declared key width. Call it before query_table to learn exact column names and types; for a view's SQL text use get_view_definition. max_length is the raw sys.columns value in BYTES, so reason about text length with type_declaration or max_length_chars.
| Name | Required | Description | Default |
|---|---|---|---|
| table | Yes | Table name, bare, with no schema prefix. Example: 'GenericSecurity' | |
| schema | No | Table schema. Empty searches every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, and non-destructive behavior, so the safety profile is covered. The description adds real interpretive value beyond that — the warning that max_length is a raw sys.columns BYTES value and that type_declaration/max_length_chars should be used instead — which prevents a common agent error.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core statement of what is returned, followed by a dense enumeration of return contents and a usage/caveat sentence. It is long but information-dense; the enumerated return fields are justified because no output schema exists.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the burden of describing return values and does so thoroughly, listing every included element plus key interpretation caveats. An agent has everything needed to call and interpret this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents table, schema, and database parameters including the bare-name format and empty-schema-wildcard behavior. The description adds nothing parameter-specific beyond the notion of one table or view, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource (returns the full structure of a table or view) and enumerates exactly what is included: columns, type declarations, extended properties, computed columns, keys, constraints, and indexes. It is clearly distinguishable from siblings like get_view_definition or list_tables.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells the agent when to call it ('before query_table to learn exact column names and types') and names the alternative for a related need ('for a view's SQL text use get_view_definition'). The routing decision is unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_table_usageTable usageARead-onlyIdempotent
Lists everything that references one table: foreign keys to and from it, and the views, procedures and functions whose code uses it. Use it for impact analysis before changing a table; for the dependency graph of the whole database use get_dependency_graph. sql_module_usage is null when the login cannot read dependencies, and 'visibility' says when the list may be incomplete.
| Name | Required | Description | Default |
|---|---|---|---|
| table | Yes | Table name, bare, with no schema prefix. | |
| schema | No | Table schema. Empty searches every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly/idempotent/non-destructive, so the bar is lower, and the description adds real operational context: sql_module_usage is null when the login cannot read dependencies, and 'visibility' flags when the list may be incomplete. This permission-dependent degradation is genuinely useful and not derivable from the annotations, though it does not cover rate limits or pagination.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, front-loaded with the core behavior and followed by routing and caveats. No filler; every clause carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description compensates by naming the returned fields that matter (sql_module_usage, visibility) and explaining their edge cases. Combined with full schema coverage and safety annotations, an agent has everything needed to call and interpret it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all three parameters (table, schema, database) are already documented in the schema, including the empty-schema-means-all-schemas behavior and the bare-name constraint. The description adds no further parameter meaning, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists everything that references one table') and enumerates the exact reference kinds returned: foreign keys in/out plus views, procedures and functions using it. It explicitly differentiates itself from the sibling get_dependency_graph, so an agent can distinguish the two without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit use case ('impact analysis before changing a table') and names the alternative for the other case ('for the dependency graph of the whole database use get_dependency_graph'). Both the when-to-use and the when-to-use-something-else are stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_view_definitionView definitionARead-onlyIdempotent
Returns the CREATE VIEW text of one view together with its columns, in the same detail as get_table_schema. Use it to see how a view derives its data; for the objects it reads from use get_dependency_graph. A view the login can see but not read comes back with definition null and definition_visible false, which check_permissions explains.
| Name | Required | Description | Default |
|---|---|---|---|
| view | Yes | View name, bare, with no schema prefix. | |
| schema | No | View schema. Empty searches every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover safety (readOnly, idempotent, non-destructive), so the bar is lower, and the description adds genuinely useful behavior beyond annotations: the null-definition/definition_visible=false case for unreadable views and the pointer to check_permissions. It does not cover existence errors or output shape details, but the access-edge behavior is a substantive addition.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the return value, then usage, then the edge case. No filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only tool with exhaustive annotations, full schema coverage, and no output schema needed for an access-edge case, the description covers purpose, sibling routing, upstream-navigation pointer, and the permission edge case. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema documents the parameters (view, schema, database) including formats and defaults. The description adds nothing parameter-specific, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource: returns the CREATE VIEW text plus columns. It explicitly positions itself against get_table_schema (same detail level) and get_dependency_graph (for source objects), which are the natural alternatives in this sibling set.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clear context for use: see how a view derives data, and a routing hint to get_dependency_graph for upstream objects. It does not state exclusions (e.g., that it errors if the view does not exist) nor explicitly contrast with list_views, but the intended usage context is well established.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_databasesList configured databasesARead-onlyIdempotent
Lists the logical database names this server is configured to reach, with each one's row limit and command timeout. Call it first: every other tool, starting with get_database_overview, takes one of these names as its 'database' argument. It reads only the server's configuration and never connects to SQL Server; when nothing is configured it answers ok: false with the file to create, where the server looked and an example.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnly/idempotent annotations, the description discloses that it reads only server configuration and never connects to SQL Server, plus the exact failure shape when nothing is configured (ok: false with file to create, search locations, and an example). These operational details are not available in the annotations and materially affect how an agent handles the result.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each load-bearing: what is returned, why to call it first, and what the failure path looks like. The most important guidance ('Call it first') is front-loaded after the purpose statement.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fully describes the returned payload (names plus row limit and timeout per entry) and the error payload shape. Nothing an agent needs to call this zero-parameter tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so the baseline of 4 applies. The description does explain the downstream convention that the returned names are consumed as a 'database' argument by other tools, which adds a little semantic value, but there is no parameter syntax to clarify.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists the logical database names this server is configured to reach') and further narrows scope by naming what each entry contains (row limit, command timeout). An agent can distinguish it from siblings like get_database_overview or list_schemas immediately.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly instructs 'Call it first' and explains why: every other tool, starting with get_database_overview, requires one of these names as its 'database' argument. The ordering dependency and the downstream consumer are both named, leaving no inference to the agent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_functionsList user functionsARead-onlyIdempotent
Lists the user-defined functions the login can see (scalar, inline table-valued and multi-statement table-valued), with the same complexity metrics and visibility flags as list_procedures. Use it to find functions; for one function's text use get_function_definition. SQL Server leaves out functions the login has no permission on without an error: get_database_overview says whether that happens, check_permissions why.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Filter by schema name. Empty means every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover safety (readOnly, idempotent, non-destructive), so the bar is lower. The description adds genuinely useful non-obvious behavior: SQL Server silently omits functions the login lacks permission on without error, and it points to get_database_overview/check_permissions for diagnosing that. Doesn't mention return format, but that's acceptable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences, front-loaded with the core purpose, then routing, then the non-obvious permission caveat. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 2-param read-only list tool whose safety profile is covered by annotations, the description covers purpose, scope, routing, and the silent-permission-omission edge case that would otherwise cause confusing empty results. Nothing an agent needs is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema fully documents both parameters (schema filter with example, database name). The description adds no parameter-level syntax beyond what the schema provides – baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists the user-defined functions'), and precisely enumerates scope (scalar, inline TVF, multi-statement TVF), distinguishing it from siblings like list_procedures and get_function_definition.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says 'Use it to find functions; for one function's text use get_function_definition' – names both the use case and the alternative tool with the condition that selects it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_proceduresList stored proceduresARead-onlyIdempotent
Lists the user stored procedures the login can see, with line count, join count and number of referenced objects as rough complexity metrics. Use it to find procedures and pick the complex ones; for one procedure's text use get_procedure_definition. definition_visible is false, and the metrics null, for a procedure whose text is hidden from the login; referenced_object_count is null when the login cannot read dependencies. SQL Server leaves out procedures the login has no permission on without an error: get_database_overview says whether that happens, check_permissions why.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Filter by schema name. Empty means every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, but the description adds substantial behavior beyond them: definition_visible=false and null metrics when text is hidden, referenced_object_count null when dependencies are unreadable, and SQL Server's silent omission of unauthorized procedures.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and routing, then the caveats. Dense but every sentence carries information; the caveat sentence is long and could be split, but nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no output schema, the description explains the returned complexity metrics and their null semantics, and covers the edge case of silently omitted procedures plus where to diagnose it. Nothing an agent needs to call or interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters (database, schema filter with default and example) are already fully documented in the schema. The description adds no parameter-level syntax or format detail, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists the user stored procedures') plus scope ('the login can see') and the extra data returned. It is clearly separable from the sibling get_procedure_definition, which it names.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to use it ('find procedures and pick the complex ones') and names the alternative for the other case ('for one procedure's text use get_procedure_definition'). It further routes the agent to get_database_overview and check_permissions for the silent-permission-omission situation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_schemasList schemasARead-onlyIdempotent
Lists the user schemas of a database with their owners, leaving out system schemas. Use it to learn the names the 'schema' filter of the other tools accepts; for object counts and sizes per schema use get_schema_summary instead.
| Name | Required | Description | Default |
|---|---|---|---|
| database | Yes | Name of the database as registered in the configuration. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive and closed-world, so the safety profile is covered. The description adds real behavioral context beyond that: system schemas are excluded and owners are included in the output.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero waste, with the scope constraint front-loaded and the routing guidance relegated to the second sentence. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter read-only list tool with full annotation coverage and no output schema, nothing an agent needs to invoke it correctly is missing; scope, exclusions, and sibling routing are all present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is a single parameter with 100% schema description coverage ('Name of the database as registered in the configuration'). The description references the database implicitly but adds no syntax or format detail beyond the schema, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (lists), resource (user schemas of a database), and scope (with owners, excluding system schemas). It is clearly distinguishable from the sibling get_schema_summary, which it explicitly names.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit use case ('learn the names the schema filter of the other tools accepts') and names the alternative tool with the condition that selects it (object counts and sizes per schema). No inference required.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_tablesList tablesARead-onlyIdempotent
Lists user tables with schema, approximate row count, data and index size in MB, and creation and modification dates. Use it to find tables by name or schema and to spot the large ones; to find tables by a column they hold use find_columns, and for one table's columns and keys use get_table_schema. Narrow large databases with schema and name_pattern.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Filter by schema name. Empty means every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. | |
| name_pattern | No | Filter by table name. A pattern without % matches anywhere in the name, so 'Security' finds GenericSecurity; add % yourself for a prefix or suffix match, as in 'Anbima%'. Empty means every table. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the safety profile (readOnly, idempotent, non-destructive, closed-world), so the bar is lower. The description adds value by disclosing what is returned, notably that row counts are approximate and sizes are in MB, which an agent cannot infer from structured fields alone. It stops short of covering pagination or result-limit behavior on large databases.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences: the first front-loads what the tool returns, the second covers routing and narrowing. No filler, and the most load-bearing information (scope plus alternatives) leads.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the burden of describing return values and does so field by field. Combined with the sibling routing and parameter narrowing advice, an agent has everything needed to select and invoke this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including the non-obvious semantics of name_pattern (substring match without %, add % for prefix/suffix). The description only echoes 'narrow with schema and name_pattern' and adds no syntax beyond the schema, so the baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource ('Lists user tables') and enumerates the exact return fields (schema, approximate row count, data/index size in MB, creation/modification dates). It also explicitly distinguishes itself from find_columns and get_table_schema, so an agent can disambiguate without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete when-to-use cases ('find tables by name or schema', 'spot the large ones') and routes to alternatives by naming both find_columns (column-based lookup) and get_table_schema (single-table details) with their selecting conditions. The closing hint about narrowing with schema and name_pattern is actionable guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_viewsList viewsARead-onlyIdempotent
Lists user views with their schema. Use it to find views by name or schema; for a view's columns and SQL text use get_view_definition, and for tables use list_tables. Narrow large databases with schema and name_pattern.
| Name | Required | Description | Default |
|---|---|---|---|
| schema | No | Filter by schema name. Empty means every schema. Example: 'Feeder' | |
| database | Yes | Name of the database as registered in the configuration. | |
| name_pattern | No | Filter by view name. A pattern without % matches anywhere in the name; add % yourself for a prefix or suffix match. Empty means every view. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint=false, and destructiveHint=false, so the safety profile is fully covered elsewhere. The description adds the operational hint that schema and name_pattern narrow large databases, plus a hint that the result includes each view's schema, but says nothing about pagination or the full return shape. Useful context on top of annotations, but not rich.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with zero filler: the purpose and output scope come first, then the alternatives, then the filtering advice. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple three-parameter read-only list tool with annotations covering safety and a fully documented schema, the description supplies purpose, alternatives, and narrowing guidance. The only omission is any mention of result limits or pagination, which is minor here.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all three parameters including pattern semantics and defaults, making 3 the baseline. The description reinforces how schema/name_pattern are used for narrowing large databases but adds no syntax or meaning beyond what the schema states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Lists user views with their schema') and explicitly distinguishes itself from the sibling tools that return view details (get_view_definition) and tables (list_tables). An agent can route correctly without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly gives the when ('find views by name or schema') and names two alternatives with the condition that selects each: get_view_definition for columns/SQL text, list_tables for tables. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
query_tableQuery a table or viewARead-onlyIdempotent
Runs a SELECT on one table or view, with optional column list, filter, ordering, grouping, aggregates (COUNT, SUM, AVG, MIN, MAX), pagination and random sampling, and returns the rows in an object with row_count and a measured 'truncated' flag, so a short result is never mistaken for a complete one. Rows are capped by the database's max_query_rows. Call get_table_schema first for exact column names; for null ratios, distinct counts and frequent values use get_data_profile instead of paging through rows. Reads actual data; never runs INSERT, UPDATE, DELETE or procedures.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | Maximum number of rows to return (default 100, capped by the per-database limit). | |
| skip | No | Number of rows to skip before returning results (for pagination, default 0). | |
| table | Yes | Table or view name, bare, with no schema prefix. Example: 'GenericSecurity' | |
| where | No | WHERE clause without the WHERE keyword, as SQL. Example: "Source = 'BDS' AND ReferenceDate >= '2026-01-01'" | |
| schema | No | Table/view schema. Empty means dbo. Example: 'Feeder' | |
| columns | No | Columns as ONE comma-separated string, not a JSON array. Example: 'ReferenceDate, Source, Close'. Empty or '*' returns every column. Reserved words need no quoting or brackets. | |
| database | Yes | Name of the database as registered in the configuration. | |
| group_by | No | GROUP BY columns as ONE comma-separated string. Example: 'Source, Name' | |
| order_by | No | ORDER BY clause without the ORDER BY keyword. Example: 'ReferenceDate DESC, Name' | |
| aggregates | No | Aggregates as ONE comma-separated string of FUNC(column) [AS alias], with FUNC one of COUNT, SUM, AVG, MIN, MAX. Only a bare column name is allowed inside the parentheses. Example: 'COUNT(*) AS Total, MAX(ReferenceDate) AS Ultima' | |
| sample_percent | No | Random sampling percentage, from 0.01 to 100. Zero (the default) means no sampling. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnly/idempotent annotations, it discloses the return shape (row_count plus a measured 'truncated' flag), that rows are capped by the database's max_query_rows, and that it never runs INSERT/UPDATE/DELETE or procedures. The truncation semantics are exactly the kind of behavior that prevents misreading a short result.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the operation and its capabilities, then the output contract, then the sibling routing, then the read-only boundary. Dense but every clause earns its place; the single long sentence is slightly overloaded but stays scannable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 11-parameter query tool with no output schema, the description compensates by describing the returned object (rows, row_count, truncated) and the row cap. An agent has everything needed to call it and interpret results correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and each of the 11 parameters is documented with examples, so the description need not carry parameter detail. It only summarizes the parameter surface (filters, grouping, aggregates, sampling) without adding syntax or format beyond the schema, which is the expected baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb (SELECT) and resource (one table or view) and enumerates the supported clauses: column list, filter, ordering, grouping, aggregates, pagination, sampling. An agent can distinguish this from sibling readers like get_table_schema or get_data_profile without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: call get_table_schema first for exact column names, and use get_data_profile for null ratios, distinct counts and frequent values instead of paging through rows. Both a prerequisite and a when-not alternative are stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
21 tool updates
v0.1.0- First observed
check_permissions - First observed
compare_schemas - First observed
find_columns - First observed
generate_dependency_dot - First observed
get_data_profile - First observed
get_database_overview - First observed
get_dependency_graph - First observed
get_function_definition - First observed
get_index_health - First observed
get_procedure_definition - First observed
get_schema_summary - First observed
get_table_schema - First observed
get_table_usage - First observed
get_view_definition - First observed
list_databases - First observed
list_functions - First observed
list_procedures - First observed
list_schemas - First observed
list_tables - First observed
list_views - First observed
query_table
TDQS
Scored across 21 tools
Each tool targets a distinct resource or action: list_* tools are per object type, get_*_definition tools return text for specific object types, and dependency tools split cleanly into data (get_dependency_graph), diagram (generate_dependency_dot), and single-table usage (get_table_usage). The descriptions explicitly cross-reference overlapping-sounding tools (e.g., get_database_overview vs get_schema_summary, get_table_schema vs get_view_definition) to remove ambiguity.
All 21 tool names use snake_case with a consistent verb_noun pattern (list_*, get_*, check_*, find_*, generate_*, compare_*, query_*). There is no mixing of camelCase or vague verb styles, making the set highly predictable.
21 tools is on the heavy side for a typical MCP server, but the breadth of SQL Server introspection (databases, schemas, tables, views, procedures, functions, columns, dependencies, permissions, profiling, indexing, comparison) largely justifies each tool. A few pairs (e.g., list_procedures/list_functions, get_database_overview/get_schema_summary) could be merged with parameters, so the set is slightly over-scoped.
The read-only exploration surface is broad, covering major object types from listing through definition to usage and impact analysis. Notable gaps remain for triggers, synonyms, sequences, and other code objects, though agents can often work around these by querying system views with query_table.
Maintenance
Related MCP Connectors
- dataOAuthco.thinair
PostgreSQL, MySQL, and SQL Server in one session. 26 read-only MCP tools for AI agents.
Draxlr's remote MCP server connects AI assistants to your SQL databases and dashboards. Explore schemas, run read-only queries, manage saved queries and dashboards, and export results, all with row-level security so each user sees only their own data.
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server providing read-only access to SQL Server databases for AI assistants, enabling schema exploration, query execution, and foreign key inference with token-efficient TOON responses.91MIT
- AlicenseNot gradedqualityBmaintenanceMCP server for safely exposing SQL Server database capabilities to LLM clients, with read-only mode, security features, and observability.28MIT
- AlicenseBqualityCmaintenanceRead-only MCP server for exploring and analyzing SQL Server objects (tables, views, triggers, stored procedures) from Claude Code.81,313 npmMIT
- AlicenseAqualityBmaintenanceMCP server for Microsoft SQL Server that lets LLMs explore schema and execute read-only SELECT queries, with optional stored procedure execution when explicitly enabled.341 npmMIT