When you give an artificial intelligence (AI) agent unconstrained access to execute commands, there is always a level of uncertainty about what the agent will actually do. They are random, or stochastic, by nature.

An AI agent might calculate that the best path forward is to truncate table or drop table in production and then recreate the object as some form of code schema migration. Perhaps, in some incomprehensible way, this action was justified to the agent to unblock a task due to an edge case inscribed within its own training data!

For production access, this is frankly, just a little terrifying.

Note: One recommended practice would be to always give agents, or the principal of any credential in use, the least privilege required to any system. If deterministic behavior and safety are requirements, then do not rely on agentic instructions in text files such as AGENTS.md as guardrails when it comes to your critical infrastructure.

An “agent” in this context could be any front end software such as Codex, ChatGPT, Claude, Copilot, OpenCode, OpenClaw, etc. that you use to connect an AI model to external systems. Now let’s take a look at one solution to this least privilege problem, Database Tools SQL reports.

Introducing SQL Reports

The OCI Database Tools service provides a resource call “SQL Reports” that work with OCI Database Tools MCP servers. SQL reports are exactly what they sound like. A deterministic and otherwise boring feature that brings repeatable, well-defined, select statements to agentic interactions with databases in the cloud.

Note, if you are interested in executable PL/SQL processes then check out this related post about Creating Your First Custom SQL Tools for MCP.

You can find SQL Reports in the OCI console. Login to your tenancy and navigate to:

  • The hamburger > Developer Services > Database Tools > SQL Reports

Example of navigating the OCI console to find SQL Reports.

SQL reports are first-class resources in OCI, so you can define them using any of the standard OCI interfaces such as the console, the OCI CLI, the SDK, or using the Terraform provider for OCI. In addition to standard OCI resource fields such as displayName and id, a SQL report can have the following:

  • SQL source
  • Bind variables (optional)
  • A purpose
  • Instructions
  • Column descriptions (for an agent’s contextual awareness)

The first two, SQL source and bind variables, represent the executable code used at runtime to send statements to a database. The last three, purpose, instructions, and column descriptions, are for context that Database Tools will send back to AI agents that are using the Database Tools MCP server with customizable reporting tools.

We will come back to this later, but I want to highlight that good column descriptions are worth your time when it comes to natural language to SQL interpretation by an AI agent. This is especially true if the source tables or views you are querying have complex joins, surrogate key columns, or otherwise, obscure column names.

Prerequisites

To use Database Tools SQL reports, you should know that you need to have a Database Tools MCP server. You can technically use SQL Reports as a useful repository of SQL metadata for an AI agent, but to actually execute SQL reports in the Database Tools service against a database, you will need:

  • A valid Database Tools connection
  • A Database Tools MCP server using this connection
  • A customizable reporting toolset configured in the MCP server

This post covers that last bullet.

If you need to create the MCP server and the connection, you should to pause here and take care of that first. Here are some posts and pages you might find useful:

About connections:

About MCP servers:

Now let’s build our first SQL report.

The Project (SQL Report Requirements)

If you would like to follow along, you can find the human resources example schema and sample data I am using for this tutorial on Github. What follows is a mock project for an MCP server.

I have a schema called HR and a Database Tools connection with this schema user. I also have an MCP server created and ready for new tools. We are going to take on the role of an MCP server administrator or developer and build the necessary SQL report.

Requirements

Here are our customer’s functional requirements for the MCP server that we need to develop:

  1. The MCP server must have a customizable reporting toolset that can list employees.
  2. The toolset must be executable for users with the MCP_User role assigned.
  3. When the AI agent lists tools, this MCP server must return list_employees.
  4. The list_employees tool must accept a :DEPT department name parameter that will be used to filter the list of employees or return all departments given a specified default value is provided.
  5. The records returned need to include the following information:
    • Employee ID
    • First name
    • Last name
    • Department
    • Job title
    • Hire date
    • Email (GUID)
    • Phone number
    • Work location
    • Manager’s name
  6. The tool should allow the AI agent to read the SQL source of the report to enhance contextual understanding of the information returned.

As a non-functional requirement, tool must improve our security posture. It should not allow an AI agent to execute arbitrary SQL statements against the database.

Here is a high-level diagram of the tables that we need to join to get the data required per the customer’s functional requirements. Refer to the sample schema linked above for the actual implementation details!

High-level relations to support the SQL report built in this tutorial.

Develop the Query

First, let’s use the SQL Worksheet in the OCI console to confirm that our SQL is correct and that it returns the data we expect from the joins defined.

Here is the SQL we will use.

SELECT
    E.EMPLOYEE_ID,
    E.FIRST_NAME,
    E.LAST_NAME,
    D.DEPARTMENT_NAME,
    J.JOB_TITLE,
    E.HIRE_DATE,
    E.EMAIL,
    E.PHONE_NUMBER,
    L.CITY
    || ', '
    || L.STATE_PROVINCE AS WORK_LOCATION,
    M.FIRST_NAME
    || ' '
    || M.LAST_NAME AS MANAGER_NAME
FROM
        EMPLOYEES E
    JOIN DEPARTMENTS D ON D.DEPARTMENT_ID = E.DEPARTMENT_ID
    JOIN JOBS J ON J.JOB_ID = E.JOB_ID
    LEFT JOIN LOCATIONS L ON L.LOCATION_ID = D.LOCATION_ID
    LEFT JOIN EMPLOYEES M ON M.EMPLOYEE_ID = E.MANAGER_ID
ORDER BY
    E.EMPLOYEE_ID
FETCH FIRST 5 ROWS ONLY;

Example SQL statement for our first SQL report with a row limit clause for testing.

It is a recommended practice to develop and refine your queries using the Database Tools SQL Worksheet or other tools such as SQL Developer for VS Code or SQLcl before moving on to the SQL Report creation stage.

Example of running SQL statement for testing using the Database Tools connection > SQL Worksheet.

Everything looks good to me, so now I am going to move on to creating the SQL report.

Create the SQL Report

To create a SQL Report resource using the OCI console we will:

  • Navigate to The hamburger > Developer Services > Database Tools > SQL Reports
  • Select the appropriate compartment (mcp-demo in my case)
  • Click on Create SQL report

Once the dialog opens up, we need to fill in the necessary details. We are going to iterate while building this report, so the first step will be wiring everything up with a simple version of the query. We will then come back and parameterize the report with a bind variable and update our instructions for the AI agent accordingly.

I provided the following inputs:

  1. Name: list_employees
  2. Description: A report that lists employees for use with the HR schema
  3. Compartment: (selected compartment)
  4. Purpose: Use this report when you need to get a list of employees
  5. Instructions: Run this report to get a list of all employees
  6. Source: (the SQL query noted above, with the FETCH FIRST... limit removed)

Example creating a Database Tools SQL report in the OCI console.

Now that we have a SQL report, we need to connect it to our MCP server and test it out.

Creating the Toolset

Over in the MCP server that I am using for this tutorial (you need to create one!) we need to configure a new toolset. Toolsets are how the Database Tools MCP server groups related tools that an AI agent can use to list available tools, or in our case, available SQL reports.

Example of creating an MCP toolset within a Database Tools MCP server.

From there select Customizable reporting tools as the Type, and then pick the SQL report we created above which was called list_employees and then change the roles as needed.

Example of configuring the SQL report within a Customizable reporting tools toolset.

Project Requirement: Don’t forget to change the Allowed roles per our second functional requirement. This is where you will configure roles. If you need more information about customizing role-based access to MCP tools refer to How To Customize Tool Access Using Roles in Database Tools MCP Servers.

Just a note about the sixth functional requirement:

The tool should allow the AI agent to read the SQL source of the report to enhance contextual understanding of the information returned.

By default, the Database Tools service enables the AI agent (the MCP client) to retrieve the source code of the SQL report. This can be useful for the agent to understand the context and the relations that return a given result set.

Note: If your requirement is to NOT allow the AI agent to view the source of the report, then you need to edit this setting to disable report_sql. The default is enabled

The other requirements of our fictitious project necessitate that we leave report_list and report_execute enabled.

Example of enabling or disabling an AI agent from reading the source of a SQL report toolset.

Click create, and then confirm your toolset appears in the OCI console as expected.

Testing the SQL Report

We are finally ready to test our new SQL report in the AI agent. Configuring an MCP server in an AI agent is out of scope for this post but you can get an idea for how to do this using a personal access token or by using a client_id by checking out this tutorial provided by Oracle. The tutorial does not provide details for all possible clients but it should give you a good place to start.

First, we can check to make sure the toolset tools are available. In Claude I can do this by entering /mcp after the OAuth flow has completed for the Database Tools MCP server and logging in to my IDCS domain as the demo_hr user which has the MCP_User assigned.

Example of listing MCP tools in a Claude Code session.

Next I will ask Claude to return a list of all employees. Claude correctly identifies that it can request a list of reports by calling the report_list tool. So, I authorize the request and Claude proceeds with the call.

Example of Claude Code requesting to list SQL reports available to a user.

Finally, Claude figured out which report to run and provided back to me the ID as a sanity check. Here I approved the call to report_execute which Claude will use to ask the Database Tools service to execute the pre-defined SQL report against my Database Tools connection.

Example of Claude Code prompting a user to execute a SQL report.

And… voila!

Example of Claude Code returning a SQL report result set from the Database Tools MCP server.

Alas, not all is perfect. Claude correctly summarizes that there are 106 employees and then proceeds to provide details for departments that total to 108. Claude is double-counting two employees somewhere! This is a good example and a reminder. Sometimes trust and always verify the output from an LLM.

Finalizing the SQL Report

Now that we have a working SQL report, we need to wrap up the final requirement which is to allow the AI agent to pass in the name of a department to get the list of employees in that specific department, or to return all employees by default.

Open the SQL report we developed earlier and add a new bind variable called DEPT and adjust the query slightly. Here is the modified SQL report source:

SELECT
    E.EMPLOYEE_ID,
    E.FIRST_NAME,
    E.LAST_NAME,
    D.DEPARTMENT_NAME,
    J.JOB_TITLE,
    E.HIRE_DATE,
    E.EMAIL,
    E.PHONE_NUMBER,
    L.CITY
    || ', '
    || L.STATE_PROVINCE AS WORK_LOCATION,
    M.FIRST_NAME
    || ' '
    || M.LAST_NAME AS MANAGER_NAME
FROM
        EMPLOYEES E
    JOIN DEPARTMENTS D ON D.DEPARTMENT_ID = E.DEPARTMENT_ID
    JOIN JOBS J ON J.JOB_ID = E.JOB_ID
    LEFT JOIN LOCATIONS L ON L.LOCATION_ID = D.LOCATION_ID
    LEFT JOIN EMPLOYEES M ON M.EMPLOYEE_ID = E.MANAGER_ID
WHERE
    :DEPT = 'ALL' OR D.DEPARTMENT_NAME = :DEPT
ORDER BY
    E.EMPLOYEE_ID

An example of a SQL report query with a bind variable and a default value mechanism.

In the Edit SQL report dialog, I made the following changes:

  • Instructions: Run this report to get a list of employees in the specified department name. If the user wants all departments then pass the string "ALL" instead of a specific department.
  • Source: (the source shown above)
  • Variables:
    • Name: DEPT
    • Type: VARCHAR2
    • Description: The string name of a department to filter by or the string "ALL" for all departments.

At this point, we should also define the Columns of the SQL report since we are done editing the code of the report. Now is a good time to provide this metadata.

An example of a SQL report with a bind variable defined and column metadata provided.

Testing the Final SQL Report

With these changes in place, it is time to test the final SQL report changes and check that our AI agent is able to return employees for a single department by name.

Example of Claude Code calling our SQL report with the required DEPT variable.

Here is the output Claude Code shared after running this report.

❯ summarize the list of employees in the IT department as a bulleted list.

● Let me check the available database reports first.

  Called dbtools 2 times (ctrl+o to expand)

● Here are the employees in the IT department:

  - Alexander James — Programmer, hired Jan 3, 2016 (Manager: Lex Garcia)
  - Bruce Miller — Programmer, hired May 21, 2017 (Manager: Alexander James)
  - David Williams — Programmer, hired Jun 25, 2015 (Manager: Alexander James)
  - Valli Jackson — Programmer, hired Feb 5, 2016 (Manager: Alexander James)
  - Diana Nguyen — Programmer, hired Feb 7, 2017 (Manager: Alexander James)

  All five are Programmers based in Southlake, Texas. Alexander James is the senior
  member and serves as manager for the other four.

Example output from Claude Code calling our SQL report for the IT department.

And remember, it is always good to verify the output provided by an agent during testing. A user may not have this luxury but they will typically have the business context to know if the data is accurate.

Here I show running a quick check using the SQL worksheet.

SELECT
	E.FIRST_NAME,
	E.LAST_NAME,
	M.FIRST_NAME || ' ' || M.LAST_NAME AS MANAGER_NAME
FROM 
		EMPLOYEES E
	JOIN DEPARTMENTS D ON D.DEPARTMENT_ID = E.DEPARTMENT_ID
	LEFT JOIN EMPLOYEES M ON M.EMPLOYEE_ID = E.MANAGER_ID
WHERE
	D.DEPARTMENT_NAME = 'IT'
ORDER BY
	E.EMPLOYEE_ID

Example data validation query to check employees in a given department.

Example of the SQL worksheet running a query using a Database Tools connection.

And if you ask your AI agent to return some form of list for “all employees,” it should correctly execute the SQL report by passing ALL as the value for the DEPT bind variable. This worked in my case at least but it would be good for you to test and verify this on your own.

And that is everything I wanted to show you about creating Database Tools SQL reports and using them with a Database Tools MCP server.

Congratulations on making it to this point!

If you followed along to the end you have just completed your first (mock) project by building a SQL report and configuring it in a Database Tools MCP server and toolset. I hope you learned a lot along the way and found this to be useful.

Cheers!