How to Connect Oracle AI Private Agent Factory to Oracle EBS Using SQLcl MCP Server over SSE
Over the past few weeks, I have been hands-on with Oracle AI Private Agent Factory (PAF) on OCI, and one of the most interesting challenges I faced was connecting PAF to a classic Oracle EBS (E-Business Suite) database that sits on a private subnet. There was no documentation that covered this end-to-end, and I had to piece things together through trial, error, and a fair amount of troubleshooting.
In this blog, I am going to walk you through everything I did — from installing SQLcl on Oracle Linux 9.7, understanding why a bridge is needed between SQLcl MCP and PAF, dealing with SELinux, cross-VCN networking on OCI, and finally getting PAF to talk to your EBS database through the MCP server. If you are doing something similar, this is the guide I wish I had.
Understanding the Architecture
Before we dive into commands, let me explain what we are building and why each component exists. This is the flow:
PAF (VCN 1, Private Subnet) → SQLcl MCP VM (VCN 2, Private Subnet) → EBS Database (VCN 2, Private Subnet)
Oracle AI Private Agent Factory is an agentic AI platform that orchestrates AI agents to perform tasks — including querying databases. It communicates with external tools using the Model Context Protocol (MCP), but it expects MCP servers to be reachable over HTTP/SSE (Server-Sent Events).
SQLcl is Oracle's command-line interface for Oracle Database. From version 25.2 onwards, it ships with a built-in MCP server that you can activate with a single flag — sql -mcp. However, SQLcl's MCP server speaks stdio (standard input/output), not HTTP. This is the core challenge.
💡 PAF speaks HTTP/SSE. SQLcl MCP speaks stdio. You need a bridge between them — that bridge is mcp-proxy running on the same VM as SQLcl.
The mcp-proxy tool (Python) wraps the SQLcl stdio process and exposes it as an HTTP/SSE endpoint on port 8080. Once that endpoint is live, PAF can discover and call all five SQLcl MCP tools as if they were a native HTTP service.
The following are the OS details in my OCI environment,
OS - Oracle Linux 9.7 (OL9) OR Oracle Linux
Architecture - x86_64
RAM - Minimum 4 GB
Disk - Boot volume at least 100 GB
Network - Private subnet
User - oracle user with sudo access
Software Prerequisites
● Java 17 or higher (JRE/JDK) — SQLcl is Java-based
● SQLcl 25.2 or higher — includes the built-in MCP server
● Python 3.11 or higher — required for mcp-proxy
● mcp-proxy Python package — the stdio-to-SSE bridge
Section 1: Installing Java 17 on Oracle Linux 9
The first thing you need is Java 17 or higher. Do not touch the system Python or any system-managed Java — install a clean version alongside what is already on the system.
# Install OpenJDK 17
sudo dnf install java-17-openjdk -y
# Set JAVA_HOME
echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
# Verify
java -version
💡 On OL9, OpenJDK 17 is available directly from the default dnf repositories — no third-party repo needed.
Section 2: Installing SQLcl
Download SQLcl 25.2 or later from Oracle's official download page (requires an Oracle account). Once you have the zip file, extract it to a standard location.
# Create the install directory
mkdir -p /home/oracle/sqlcl
# Unzip (replace with your actual downloaded filename)
unzip sqlcl-25.x.x.zip -d /home/oracle/sqlcl/
# The binary is called 'sql' — not 'sqlcl'
# This is a common gotcha!
export PATH=/home/oracle/sqlcl/bin:$PATH
# Verify
sql -version
💡 The binary is named 'sql', not 'sqlcl'. This trips up almost everyone the first time. All commands going forward use 'sql', including sql -mcp.
Section 3: Saving the EBS Database Connection
The SQLcl MCP server relies on connections saved in its credential store at ~/.dbtools. You must save your EBS connection with the -savepwd flag before the MCP server can auto-connect to it.
# Launch SQLcl
/home/oracle/sqlcl/bin/sql /nolog
# Inside SQLcl — save EBS connection with password
SQL> conn -save ebs_mcp -savepwd apps/yourpassword@//ebs-db-hostname:1521/EBSDB
# Verify it saved correctly
SQL> connmgr list
SQL> exit
If your EBS database uses a tnsnames.ora entry, set TNS_ADMIN first:
export TNS_ADMIN=/home/oracle/network/admin
sql /nolog
SQL> conn -save ebs_mcp -savepwd apps/yourpassword@EBSDB
Section 4: Installing Python 3.11 and mcp-proxy
Your Oracle Linux 9 ships with Python 3.9, which is tied to system tools like dnf. Do not upgrade or replace it. Instead, install Python 3.11 alongside it — they coexist cleanly on OL9.
# Install Python 3.11
sudo dnf install python3.11 python3.11-pip -y
# Verify
python3.11 --version
# Install mcp-proxy using Python 3.11
python3.11 -m pip install --user mcp-proxy
# Add local bin to PATH
echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
# Verify
mcp-proxy --version
💡 mcp-proxy requires Python 3.10 or higher. If you try to install it with the system Python 3.9, it will fail with 'No matching distribution found'. Always use python3.11 -m pip install for this package.
Why mcp-proxy and What It Does
Let me explain this clearly because I got confused about this myself. There are several bridge tools in the MCP ecosystem — mcp-remote, mcp-proxy, supergateway — and they all sound similar but do different things.
For our use case — wrapping SQLcl MCP (stdio) and exposing it as HTTP/SSE for PAF — mcp-proxy (Python) is the correct tool. It spawns sql -mcp as a child process, handles stdio communication internally, and exposes a clean SSE endpoint on port 8080 that PAF can register and call.
Section 5: Creating the Wrapper Script and systemd Service
The Wrapper Script
Create a shell script that sets all required environment variables and launches mcp-proxy with the correct arguments. Pay attention to using absolute paths — systemd does not load your .bashrc, so relative paths and ~ expansions will not work.
cat > /usr/local/bin/sqlmcpserver.sh << 'EOF'
#!/bin/bash
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
export TNS_ADMIN=/home/oracle/network/admin
export PATH=/home/oracle/.local/bin:/usr/lib/jvm/java-17-openjdk/bin:/home/oracle/sqlcl/bin:$PATH
exec /home/oracle/.local/bin/mcp-proxy \
--port=8080 \
--host=0.0.0.0 \
-- /home/oracle/sqlcl/bin/sql -mcp
EOF
chmod +x /usr/local/bin/sqlmcpserver.sh
💡 Keep your scripts in /usr/local/bin rather than /home/oracle. Oracle Linux 9 runs SELinux in Enforcing mode by default, and scripts in home directories carry the user_home_t context which systemd refuses to execute (you get status=203/EXEC). Scripts in /usr/local/bin automatically get the bin_t context that systemd accepts.
The systemd Service
Create a systemd unit file to manage the service lifecycle — auto-start on boot, auto-restart on crash, and proper logging via journald.
sudo vi /etc/systemd/system/sqlcl-mcp.service
[Unit]
Description=SQLcl MCP HTTP/SSE Server for PAF
After=network.target
[Service]
Type=simple
User=oracle
ExecStart=/usr/local/bin/sqlmcpserver.sh
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable sqlcl-mcp
sudo systemctl start sqlcl-mcp
sudo systemctl status sqlcl-mcp
A healthy service output looks like this:
Active: active (running) since Wed 2026-07-01 06:20:07 GMT
Main PID: 318054 (mcp-proxy)
INFO: Uvicorn running on http://0.0.0.0:8080
INFO: Application startup complete.
💡 Watch for 'http://0.0.0.0:8080' specifically. If you see '127.0.0.1:8080', add --host=0.0.0.0 to your mcp-proxy command — otherwise PAF cannot reach it from outside the VM.
Section 6: SELinux — The Silent Blocker
This deserves its own section because it cost me a significant amount of troubleshooting time. Oracle Linux 9 runs SELinux in Enforcing mode by default. If your script is in /home/oracle, systemd will refuse to execute it with status=203/EXEC — even if the file has execute permissions and a valid shebang.
The quick way to diagnose this:
getenforce
# Enforcing
ls -lZ /home/oracle/sqlmcpserver.sh
# -rwxr-xr-x. oracle oinstall unconfined_u:object_r:user_home_t:s0
The context user_home_t is the problem. The solution is simply to move your script to /usr/local/bin where it automatically gets the bin_t context that systemd trusts. Alternatively, if you must keep it in the home directory, you can relabel it:
sudo dnf install policycoreutils-python-utils -y
sudo semanage fcontext -a -t bin_t "/home/oracle/sqlmcpserver.sh"
sudo restorecon -v /home/oracle/sqlmcpserver.sh
💡 Never disable SELinux permanently to work around this. It is there for a reason — especially on a VM that holds database credentials. Fix the context, not the policy.
OS Firewall on SQLcl VM
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports
Verify Connectivity End-to-End
# From SQLcl VM — confirm EBS DB is reachable
nc -zv <ebs-db-private-ip> 1521
# From PAF VM — confirm SQLcl MCP SSE endpoint is reachable
curl -N http://<sqlcl-vm-private-ip>:8080/sse
Section 7: Registering SQLcl MCP in Oracle AI Private Agent Factory
Once the SSE endpoint is confirmed reachable from the PAF VM, registering it in PAF is straightforward.
● Open PAF UI → Settings → MCP Servers → Add
● Name: SQLcl-EBS-MCP
● Endpoint URL: http://<sqlcl-vm-private-ip>:8080/sse
● Transport: SSE
● Authentication: None (for private subnet deployments)
● Click Connect — status should change to Connected
Once connected, PAF will automatically discover all five SQLcl MCP tools:
Thanks & Regards,
Chandan Tanwani
No comments:
Post a Comment