Turn SQL into Conversation: Natural Language Database Queries with MCP

Turn SQL into Conversation: Natural Language Database Queries with MCP
Imaginea este preluată automat împreună cu articolul de pe Kaizen Driven Development

by Horațiu Dan

A previous article [Resource 1] provided general insights regarding Model Context Protocol, more exactly, it outlined how MCP can act as an universal adapter that allows AI assistants to securely access external systems in order to bring in new context that is useful to the interacting LLMs.

The current article continues this analysis and exemplifies how a dedicated MCP server that is able to access a database can enable LLMs to inspect them and offer their users useful pieces of information. Users on the other hand, are now given the opportunity to automatically obtain actual business insights inferred directly from the existing data by using just the natural language.

MCP servers in general expose three primitives – tools, resources and prompt templates. The experiment described further makes use of the first one.

“The actors” involved are the following:

  • Claude Desktop as the AI assistant
  • Claude Sonnet 4.0 as the Large Language Model
  • The user as the person interested in “having a conversation” to the database
  • PostgreSQL Server as the database server that holds the business data
  • PostgreSQL MCP Server as the leading actor that exposes the MCP tool

Use-Case

Let’s assume a user can access the database and that is interested in aspects:

  • concerning the database structure and the particular tables’ structure
  • concerning the data inside a schema
  • business intelligence wise, that provide real business insights in a user friendly manner

When it comes to enquires from the first two categories, these could be responded to easily, by writing and executing the needed SQL queries directly against the database. Concerning the third category, one could still extract such pieces of information, but to accomplish that, a little bit more programming is required. What about not having to do any of these?

The “distribution” above helps us achieve all only via configuration and by using the natural language. As already stated before, the ability to clearly formulate a question on a topic of interest and to write it down grammatically correct is still a useful (if not compulsory from a decency point of view) precondition.

Preliminary Set-up

During this experiment, I used a Windows machine, thus the applications to install are destined for this platform, but the discussion is almost similar for the others as well.

Database Set-up

Considering the PostgreSQL Database Server is up and running, one may create the simple schema that is used in this use-case. Let it be called mcpdata.

create schema mcpdata;

As I currently work for Tangoe – the world leader in telecom expense management solutions’ provider – the experimental database models items from the same domain. Everything is simplified so that it is easier to follow and thus, the entities involved are the following:

  • Vendor – designates a service provider – e.g. Vodafone
  • Service – represents a type of telecom service – e.g. VOIP
  • Status – a state an invoice may have at a certain moment – Under Review, Approved or Paid
  • Invoice – issued for a service from a vendor, at a specific date, having a certain number and amount due

The database initialization can be done with the script below.

drop table if exists vendors cascade;
create table if not exists vendors (
   id serial primary key,
   name varchar not null unique
);

drop table if exists services cascade;
create table if not exists services (
    id serial primary key,
    name varchar not null unique
);

drop table if exists statuses cascade;
create table if not exists statuses (
    id serial primary key,
    name varchar not null unique
);

drop table if exists invoices cascade;
create table if not exists invoices (
    id serial primary key,
    number varchar not null unique,
    date date not null,
    vendor_id integer not null,
    service_id integer not null,
    status_id integer not null,
    total numeric(18, 2) default 0.0 not null,
    constraint fk_invoices_services foreign key (service_id) references services(id),
    constraint fk_invoices_statuses foreign key (status_id) references statuses(id),
    constraint fk_invoices_vendors foreign key (vendor_id) references vendors(id)
);

Next, some experimental data is added. Although not much, it is more than enough for the use-case here, nevertheless, one may add more or make modifications, as appropriate.

do $$
declare
    v_ver int;
    v_vdf int;
    v_org int;
    v_att int;

    s_voip int;
    s_eth int;
    s_tf int;
    s_mpls int;
    s_lo int;

    st_rev int;
    st_app int;
    st_paid int;
begin
    insert into vendors (name) values ('Verizon') returning id into v_ver;
    insert into vendors (name) values ('Vodafone') returning id into v_vdf;
    insert into vendors (name) values ('Orange') returning id into v_org;
    insert into vendors (name) values ('ATT') returning id into v_att;

    insert into services (name) values ('VOIP') returning id into s_voip;
    insert into services (name) values ('Ethernet') returning id into s_eth;
    insert into services (name) values ('Toll Free') returning id into s_tf;
    insert into services (name) values ('MPLS') returning id into s_mpls;
    insert into services (name) values ('Local') returning id into s_lo;

    insert into statuses (name) values ('Under Review') returning id into st_rev;
    insert into statuses (name) values ('Approved') returning id into st_app;
    insert into statuses (name) values ('Paid') returning id into st_paid;

    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('ver-voip-rev-1', '2025-06-02', v_ver, s_voip, st_rev, 151);
    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('ver-eth-rev-1', '2025-06-03', v_ver, s_eth, st_rev, 240);
    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('ver-tf-paid-1', '2025-06-04', v_ver, s_tf, st_paid, 102.44);
    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('ver-mpls-app-1', '2025-06-01', v_ver, s_mpls, st_app, 42.44);
    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('ver-lo-paid-1', '2025-06-05', v_ver, s_lo, st_paid, 113.44);

    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('vdf-lo-paid-1', '2025-06-10', v_vdf, s_lo, st_paid, 85.44);
    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('vdf-mpls-app-1', '2025-05-10', v_vdf, s_mpls, st_app, 80.44);
    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('vdf-tf-rev-1', '2025-05-20', v_vdf, s_tf, st_rev, 10.44);

    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('org-voip-paid-1', '2025-04-10', v_org, s_voip, st_paid, 50.81);
    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('org-voip-paid-2', '2025-05-10', v_org, s_voip, st_paid, 50.81);
    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('org-voip-paid-3', '2025-06-10', v_org, s_voip, st_paid, 50.81);

    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('att-eth-app-1', '2025-06-13', v_att, s_eth, st_app, 100);
    insert into invoices (number, date, vendor_id, service_id, status_id, total)
    values ('att-mpls-paid-1', '2025-06-20', v_att, s_mpls, st_paid, 98);
end;
$$;

AI Host and MCP Server Set-up

Since Claude Desktop will be used, it is first installed locally [Resource 3]. For the purpose of the experiment described in this article, signing up to a Claude AI free plan is enough. Once the application is started, it is possible to begin interacting with the available LLMs – Claude Sonnet 4.0 in this case.

Without connecting it to a PostgreSQL MCP Server, the AI assistant has obviously no clue about the data that resides in the database. It lacks the particular context. To overcome this, PostgreSQL MCP Server is plugged into the AI client via configuration.

In Claude Desktop, go to File – Settings. Select Developer, then click Edit Config. Once the button is pressed, the user is indicated a JSON file the below snippet shall be written to.
In my case, this is C:\Users\horatiu.dan\AppData\Roaming\Claude\claude_desktop_config.json.

{
  "mcpServers": {       
        "postgres": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-postgres",
        "postgresql://postgres:a@localhost:5432/postgres?currentSchema=mcpdata"
      ]
    }
  }
}

The PostgreSQL connection string at line 8 is the important one here and it shall be adapted from case to case. Nevertheless, the previously created mcpdata schema is used.

Before being able to use this MCP server inside Claude Desktop, there is one more prerequisite – the server needs to be run. In this experiment, Node.js is used and consequently, it must be also installed and made available in the local path. If needed,[Resource 1] might help as it provides the configuration steps in detail.

At this point, if all above steps have been accomplished as advised, after restarting Claude Desktop, PostgreSQL MCP server should be available and ready to be used.

“Discussing” with the Database

In this section, several interactions are analyzed, in accordance with the categories mentioned before. As the pieces of information requested are about the mcpdata database schema, Claude client now uses the MCP server integration to find the useful details. Whenever the tool considers whether it should use the server, it asks for permission. If allowed, it continues towards compiling the response.

If interested in the current database structure, one may ask the AI assistant – Generate an entity-relationship diagram of the mcpdata schema.

Depending on the intermediary results and the conclusions inferred, the model might perform several interactions with the database, as it considers fit. The result is the aimed ER diagram:

Additionally, a great deal of useful explanations are returned as well,

plus a one paragraph conclusion:

“This is a classic invoice management system design where the INVOICES table serves as the central fact table, with three lookup/dimension tables (VENDORS, SERVICES, STATUSES) providing normalized reference data. The relationships ensure data integrity and eliminate redundancy.”

Another question in the same category might be – What tables are there in the ‘mcpdata’ schema and how many records are in each?

The results are accurate, as expected, Claude Desktop triggered two queries to respond:

{
  `sql`: `SELECT table_name FROM information_schema.tables WHERE table_schema = 'mcpdata' ORDER BY table_name;`
}

and

{
  `sql`: `SELECT 'invoices' as table_name, COUNT(*) as record_count FROM mcpdata.invoices
        UNION ALL
        SELECT 'services' as table_name, COUNT(*) as record_count FROM mcpdata.services
        UNION ALL
        SELECT 'statuses' as table_name, COUNT(*) as record_count FROM mcpdata.statuses
        UNION ALL
        SELECT 'vendors' as table_name, COUNT(*) as record_count FROM mcpdata.vendors
        ORDER BY table_name;`
}

Moving further, a questions with slightly more business flavor might be – How many paid invoices are in June?

The response is again correct and the way the AI assistant approached the problem is detailed below.

It first checked the structure of the invoices and statuses tables:

{
  `sql`: `SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'mcpdata' AND table_name = 'invoices' ORDER BY ordinal_position;`
}

{
  `sql`: `SELECT * FROM mcpdata.statuses;`
}

Then, it inferred that the unique identifier of the ‘Paid’ Status entity is 3.

Since the user did not specify a year in the prompt, the AI assistant looked up all existing years:

{
  `sql`: `SELECT DISTINCT EXTRACT(YEAR FROM date) as year FROM mcpdata.invoices ORDER BY year;`
}

And as there are only invoices from the year of 2025, it counted these:

{
  `sql`: `SELECT COUNT(*) as paid_invoices_in_june
                FROM mcpdata.invoices 
                WHERE status_id = 3 
                AND EXTRACT(MONTH FROM date) = 6 
                AND EXTRACT(YEAR FROM date) = 2025;`
}

which resulted in the exact number of 5.

Continuing the conversation, maybe the user is interested in what the total amount to be paid is and thus it asks – How much do we have to pay for these?

The response is accurate again, concluding – “The total amount for the 5 paid invoices in June 2025 is $450.13.”. The AI assistant also makes an assumption, which might be true, depending on the design of the invoice statuses – “Since these invoices have a status of “Paid”, this represents money that has already been paid out, not money that still needs to be paid.” Basically, the user and the AI host are now engaged in a conversation held in natural language.

Last but not least, if the user aims for more when it comes to the current state of the invoices in the system, a visual representation might be requested to the AI Assistant.

Generate a visual representation of the data in the invoices table” is the highlight request in this experiment.

Once the pieces information needed are gathered with the help of the used MCP Server, a comprehensive visual and interactive dashboard is created. Details are also outlined as text.

Visually, the generated graphs and reports look pretty good and might help one get a glance of the current state.

“Total Amount by Vendor”, “Amount by Status”, “Monthly Invoice Trends” and “Invoice Details” are just a few examples the AI Assistant decided to compile and display to the user. No doubt it could do more, as the request here was quite vague.

Conclusions

This article exemplified a straight-forward, but insightful use-case where a dedicated PostgreSQL MCP Server helped enriching the context of an AI application, in this case Claude Desktop and allowed the user quicky discover pieces of information inferred directly from the database, in a natural language conversational manner.

SQLite MCP Server is another example of a solution which may be used to accomplish similar tasks. In addition to read-only tools, this one though allows creating / modifying database tables and writing data, thus the interaction may be even more insightful. In the particular case of this article, a user might even most probably be capable of adding invoices, which is indeed pretty spectacular.

All in all, the imagination can fly freely as a lot of ideas are just a few “words” distance to being put into practice by using an AI assistant (Claude Desktop) and several MCP servers. Going even further, if the currently available MCP servers are not enough, one can develop its own brand-new ones that satisfy their needs and plug it into the assistant and Voila!

Resources

[Resource 1] – Enriching AI with Real-Time Insights via MCP

[Resource 2] – PostgreSQL MCP Server

[Resource 3] – Claude Desktop is available here

[Resource 4] – SQLite MCP Server

[Resource 5] – Anthropic – Developer Guide – MCP

[Resource 6] – the picture is of a painting from ‘Harry Potter Warner Bros. Studios’, near London