Building a remote MCP Server in PL/SQL that
returns the current date
(complete code, cURL samples, unit tests & ChatGPT Desktop integration) • May 03, 2025
1. What is MCP, and why “remote MCP”?
Model Context Protocol (MCP) is a JSON-RPC 2.0–based contract that lets
large-language-model hosts call your back-end “tools”.
A remote MCP server exposes the same contract over plain HTTP/SSE so that the
code can live anywhere on your network instead of inside the model’s runtime.
In this guide, we will:
• Implement a minimal getCurrentDate tool in PL/SQL,
• expose it through Oracle REST Data Services (ORDS),
• cover it with unit tests using utPLSQL, and
• show how to connect it to ChatGPT Desktop (v 1.9 or newer).
All snippets work on Oracle Database 19c or 23ai with ORDS 23.x.
2. Solution Architecture
Layer Technology Role Transport Oracle REST Data Services (ORDS) Listens for
/tools/list and /tools/call
Business logic PL/SQL package MCP_DATE_PKG
Implements MCP tools, Tests utPLSQL Unit-tests package & HTTP layer
3. Preparing the Environment
-- Enable ORDS for the target schema (once)
BEGIN
ORDS.ENABLE_SCHEMA(p_enabled => TRUE);
END;
/
-- Install utPLSQL once in a common user (see
github.com/utPLSQL/utPLSQL)
4. Implementing the Tool in PL/SQL
4.1 The Package
CREATE OR REPLACE PACKAGE mcp_date_pkg IS
FUNCTION list_tools
RETURN json_array_t;
FUNCTION get_current_date RETURN json_object_t;
END;
/
CREATE OR REPLACE PACKAGE BODY mcp_date_pkg IS
FUNCTION list_tools RETURN json_array_t IS
BEGIN
RETURN json_array_t(
json_object_t(
'name'
VALUE 'getCurrentDate',
'description' VALUE 'Returns the current UTC date‑time in
ISO‑8601',
'parameters'
VALUE json_object_t()
-- this tool needs no
params
));
END;
FUNCTION get_current_date RETURN json_object_t IS
BEGIN
RETURN json_object_t(
'currentDate' VALUE
to_char(sys_extract_utc(systimestamp),
'YYYY-MM-DD"T"HH24:MI:SS"Z"')
);
END;
END;
/
4.2 The ORDS Module
BEGIN
ORDS.DEFINE_MODULE(
p_module_name
=> 'mcp',
p_base_path
=> 'mcp/',
p_items_per_page
=> 0
);
/* Discovery endpoint:
GET /mcp/tools/list */
ORDS.DEFINE_TEMPLATE(p_module_name => 'mcp', p_pattern =>
'tools/list');
ORDS.DEFINE_HANDLER(
p_module_name => 'mcp',
p_pattern
=> 'tools/list',
p_method
=> 'GET',
p_source_type => ORDS.source_type_plsql,
p_source
=> 'BEGIN :body :=
mcp_date_pkg.list_tools.to_clob(); END;'
);
/* Call endpoint:
POST /mcp/tools/call */
ORDS.DEFINE_TEMPLATE(p_module_name => 'mcp', p_pattern =>
'tools/call');
ORDS.DEFINE_HANDLER(
p_module_name => 'mcp',
p_pattern
=> 'tools/call',
p_method
=> 'POST',
p_source_type => ORDS.source_type_plsql,
p_source
=> q'~
DECLARE
l_request json_object_t := json_object_t.parse(:body);
l_id
NUMBER
:= l_request.get_number('id');
l_method
VARCHAR2(128) := l_request.get_string('method');
l_output
json_object_t;
BEGIN
IF l_method = 'getCurrentDate' THEN
l_output := mcp_date_pkg.get_current_date;
ELSE
raise_application_error(-20000, 'Unknown method');
END IF;
:body := json_object_t(
'jsonrpc' VALUE '2.0',
'id'
VALUE l_id,
'result'
VALUE l_output
).to_clob();
END;~'
);
COMMIT;
END;
/
5. Smoke Testing with cURL
# 1) Discover available tools
curl https://db.example.com/ords/hr/mcp/tools/list
# -> [{"name":"getCurrentDate","description":"...","parameters":{}}]
# 2) Invoke the tool
curl -X POST https://db.example.com/ords/hr/mcp/tools/call \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"getCurrentDate","params":
{}}'
# -> {"jsonrpc":"2.0","id":1,"result":{"currentDate":"2025-0503T13:17:42Z"}}
6. Unit Testing with utPLSQL
CREATE OR REPLACE PACKAGE ut_mcp_date_pkg AUTHID DEFINER IS
--%suite(MCP‑server unit tests)
--%test(getCurrentDate is listed)
PROCEDURE t_list;
--%test(getCurrentDate returns valid ISO‑8601)
PROCEDURE t_date;
END;
/
CREATE OR REPLACE PACKAGE BODY ut_mcp_date_pkg IS
PROCEDURE t_list IS
l_tools json_array_t := mcp_date_pkg.list_tools;
BEGIN
ut.expect(l_tools.get_object(0).get_string('name')).to_equal('getCur
rentDate');
END;
PROCEDURE t_date IS
l_res json_object_t := mcp_date_pkg.get_current_date;
l_iso VARCHAR2(32)
:= l_res.get_string('currentDate');
BEGIN
ut.expect(REGEXP_LIKE(l_iso,
'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$')).to_be_true;
END;
END;
/
-- Run tests
BEGIN
ut.run();
END;
/
7. Optional Integration Test Inside Oracle
DECLARE
l_req
CLOB :=
'{"jsonrpc":"2.0","id":99,"method":"getCurrentDate","params":{}}';
l_resp CLOB;
BEGIN
l_resp := ords_utilities.invoke_rest_endpoint(
p_method => 'POST',
p_url
=>
'https://db.example.com/ords/hr/mcp/tools/call',
p_body
=> l_req);
dbms_output.put_line(l_resp);
END;
/
8. Connecting the Server to ChatGPT Desktop
8.1 Using the UI
1. Settings → Labs → Remote Tools (Beta)
2. Click Add Remote Tool.
3. Fill in the form:
Name Oracle‑Date
Base URL https://db.example.com/ords/hr/mcp/
Discovery path tools/list
Call path tools/call
Transport HTTP SSE
(Optional) Auth header Authorization:
Bearer YOUR_TOKEN
Restart ChatGPT Desktop; the tool appears in the
Remote Tools panel.
8.2 Using a JSON Config File (macOS)
Create
~/Library/Application Support/OpenAI/ChatGPT/remote_tools.json :
{
"mcpServers": {
"oracle-date": {
"transport": "sse",
"baseUrl":
"https://db.example.com/ords/hr/mcp/",
"discovery": "tools/list",
"call":
"tools/call",
"headers":
{ "Authorization": "Bearer YOUR_TOKEN" }
}
}
}
For Windows use %APPDATA%\OpenAI\ChatGPT\remote_tools.json .
Restart the app.
8.3 Sample Conversation
You:
How many days remain until New Year according to the DB?
GPT:
(calls Oracle‑Date.getCurrentDate → 2025‑05‑03T…)
GPT:
Today is 3 May 2025; there are 242 days until 31 December.
9. Next Steps & Best Practices
• Authentication – protect the endpoints with an API key or OAuth2, and let
ORDS add the Authorization header to PL/SQL via
ords.enable_schema(hide_enabled_schemas=>TRUE) .
• Streaming/SSE – if your future tools return long-running output, place ORDS
behind Nginx and proxy with proxy_http_version 1.1 and
chunked_transfer_encoding on; .
• Multiple tools – add more PL/SQL packages and expose them through the
same module; ChatGPT groups them by server.
• Observability – write each call to an audit table or DBMS_APPLICATION_INFO
for traceability.
• Error handling – the handler already throws -20000 for unknown methods;
ChatGPT propagates this as a tool error to the user.
10. Conclusion
With roughly 150 lines of PL/SQL and a few ORDS calls, we built a fully
standards-compliant remote MCP server:
• no extra micro-service layer,
• unit-tested inside the database,
• Instantly usable from ChatGPT Desktop and any other MCP-aware client.
This pattern lets your Oracle database expose rich, safe “tools” to LLMs while
keeping all business logic exactly where your data already lives.
Article created by GPT-3o :)
Report content on this page