Content
# Skipper
Skipper is a lightweight Model Context Protocol (MCP) server for Oracle Database
diagnostics. It can query Oracle fixed views, run read-only SQL, search an Oracle
SGA for byte patterns, infer the layout of X$ structures, and trace Oracle C
functions with GDB.
The MCP server is written entirely with the Python standard library. Memory
scanning is performed by the bundled `focs_vm` utility, which is built from Rust
source.
> [!WARNING]
> Skipper is a privileged diagnostic tool. It can connect locally as SYSDBA,
> inspect process memory, and attach a debugger to Oracle processes. Install it
> only on an authorized diagnostic or laboratory system, restrict network access,
> and never expose its HTTP port directly to the Internet.
## Platform and dependencies
The deployment procedure below targets Oracle Linux 8.9 on AArch64, with Oracle
Database running as user `oracle` and group `oinstall`. It uses:
| Dependency | Required for | Notes |
| --- | --- | --- |
| Python 3.12 | MCP server and tests | No third-party Python packages, `pip`, or virtual environment are required. |
| A local Oracle Database installation | Oracle diagnostic tools | The service account must be able to use local OS authentication. |
| SQL*Plus | All `oracle_*` SQL tools | Normally found at `$ORACLE_HOME/bin/sqlplus`. |
| Rust toolchain and Cargo | Building `focs_vm` | Only required when building or updating the scanner. |
| GCC/linker | Building `focs_vm` | Installed from the Oracle Linux development packages. |
| Linux `/proc` access | Process and SGA discovery | Required by the memory-related tools. |
| `sudo` | Privileged memory scans and debugging | Required only when the service user cannot access the target process directly. |
| GDB | `oracle_debug_*` tools | Optional unless C-function tracing is needed. |
| GNU `strings` and `grep` | Long fixed-view fallback | `strings` is supplied by `binutils`; this fallback runs only for truncated view definitions. |
| Git and curl | Installation | Used to clone the repository and install Rust. |
Python 3.12 is used explicitly throughout this guide. On Oracle Linux 8,
`python3` may still resolve to Python 3.6, so do not assume it selects the desired
interpreter.
## Step-by-step installation
The commands in this section assume that the final installation directory is
`/home/oracle/skipper`.
### 1. Install system packages
Run the following as the `oracle` user with access to `sudo`:
```bash
sudo dnf install -y git curl gcc python3.12 binutils grep sudo
```
Install GDB as well if the `oracle_debug_*` tools will be used:
```bash
sudo dnf install -y gdb
```
Confirm the Python version:
```bash
/usr/bin/python3.12 --version
```
### 2. Clone the repository
```bash
cd /home/oracle
git clone https://github.com/ora600pl/skipper.git
cd /home/oracle/skipper
```
If GitHub SSH access is already configured, the SSH URL can be used instead:
```bash
git clone git@github.com:ora600pl/skipper.git
```
### 3. Install Rust
Install Rust for the `oracle` account, because that account builds and runs the
project:
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup-init.sh
sh /tmp/rustup-init.sh -y --profile minimal
source "$HOME/.cargo/env"
rustc --version
cargo --version
```
### 4. Build the memory scanner
Build a release binary using the dependency versions recorded in `Cargo.lock`:
```bash
cd /home/oracle/skipper
cargo build --locked --manifest-path tools/focs_vm/Cargo.toml --release
test -x tools/focs_vm/target/release/focs_vm
```
The resulting executable is:
```text
/home/oracle/skipper/tools/focs_vm/target/release/focs_vm
```
Skipper finds this path automatically. Set `FOCS_VM_BIN` only when the scanner is
installed elsewhere.
### 5. Run the test suite
The launcher adds `src/` to the Python module path, but the test runner needs an
explicit `PYTHONPATH`:
```bash
cd /home/oracle/skipper
PYTHONPATH=src /usr/bin/python3.12 -m unittest discover -s tests -v
/usr/bin/python3.12 -m unittest discover -s tools/rico2 -p 'test_*.py' -v
/usr/bin/python3.12 bin/skipper-mcp --version
/usr/bin/python3.12 bin/skipper-mcp --help
```
### 6. Provide the password dictionary manually
The production password dictionary is deliberately not included in the
repository. Provide it manually as this exact file:
```text
/home/oracle/skipper/dict.txt
```
The file must be named `dict.txt`, live in the Skipper repository root,
and contain one password candidate per UTF-8 line. It is ignored by Git so that
a locally maintained dictionary is not committed accidentally.
A small example dictionary is currently included at:
```text
/home/oracle/skipper/data/dict.example.txt
```
It contains only `test`, `hr`, `dupa`, `abc123`, `oracle`, `Oracle+`, and
`Oracle+1`. If `/home/oracle/skipper/dict.txt` is not supplied, the
`oracle_password_check` tool automatically uses **only this example file**. A
no-match result from that fallback therefore does not mean that a password is
strong.
For a minimal local dictionary based on the example, copy it manually and then
extend it:
```bash
cd /home/oracle/skipper
cp data/dict.example.txt dict.txt
chmod 0600 dict.txt
```
### 7. Configure the Oracle environment
The service does not read the `oracle` user's shell profile. Copy the supplied
environment file and edit it for the local instance:
```bash
sudo install -m 0644 deploy/skipper-mcp.env.example /etc/sysconfig/skipper-mcp
sudo vi /etc/sysconfig/skipper-mcp
```
Example:
```ini
ORACLE_SID=rick
ORACLE_BASE=/u01/app/oracle
ORACLE_HOME=/u01/app/oracle/product/19.19/dbhome_1
PATH=/u01/app/oracle/product/19.19/dbhome_1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
```
Verify local OS authentication as the same user that will run Skipper:
```bash
echo 'select instance_name, status from v$instance; exit' | "$ORACLE_HOME/bin/sqlplus" -s / as sysdba
```
The service account must belong to the Oracle OS groups that permit
`sqlplus / as sysdba`.
### 8. Grant narrowly scoped process-memory access
The supplied systemd unit sets `SKIPPER_FOCS_USE_SUDO=true`, causing memory scans
to run through non-interactive `sudo -n`. Grant access only to the scanner rather
than using `NOPASSWD: ALL`:
```bash
printf '%s\n' 'oracle ALL=(root) NOPASSWD: /home/oracle/skipper/tools/focs_vm/target/release/focs_vm' \
| sudo tee /etc/sudoers.d/skipper-mcp >/dev/null
sudo chmod 0440 /etc/sudoers.d/skipper-mcp
sudo visudo -cf /etc/sudoers.d/skipper-mcp
sudo -n /home/oracle/skipper/tools/focs_vm/target/release/focs_vm --help >/dev/null
```
To use the GDB tools through `sudo`, add `/usr/bin/gdb` to the same rule:
```sudoers
oracle ALL=(root) NOPASSWD: /home/oracle/skipper/tools/focs_vm/target/release/focs_vm, /usr/bin/gdb
```
Then add this line to `/etc/sysconfig/skipper-mcp`:
```ini
SKIPPER_GDB_USE_SUDO=true
```
Process tracing may also be restricted by Linux ptrace policy or service
hardening. Keep these permissions limited to a controlled host.
### 9. Install and start the systemd service
The included unit expects `/home/oracle/skipper`, user `oracle`, group `oinstall`,
Python 3.12, and TCP port 6751:
```bash
cd /home/oracle/skipper
sudo install -m 0644 deploy/skipper-mcp.service /etc/systemd/system/skipper-mcp.service
sudo systemctl daemon-reload
sudo systemctl enable --now skipper-mcp.service
sudo systemctl status skipper-mcp.service --no-pager -l
```
Check the health endpoint and recent logs:
```bash
curl --fail http://127.0.0.1:6751/health
sudo journalctl -u skipper-mcp.service -n 100 --no-pager
```
Expected health response:
```json
{"status":"ok","server":"skipper"}
```
The MCP endpoint is now available at `http://HOST:6751/mcp`.
## Running without systemd
For a foreground HTTP server:
```bash
cd /home/oracle/skipper
export ORACLE_SID=rick
export ORACLE_HOME=/u01/app/oracle/product/19.19/dbhome_1
export PATH="$ORACLE_HOME/bin:$PATH"
/usr/bin/python3.12 bin/skipper-mcp --bind 127.0.0.1 --port 6751
```
For MCP over standard input/output:
```bash
/usr/bin/python3.12 /home/oracle/skipper/bin/skipper-mcp --stdio
```
## Connecting an MCP client
For an HTTP-capable MCP client, use:
```json
{
"mcpServers": {
"skipper": {
"url": "http://192.168.181.136:6751/mcp"
}
}
}
```
For a client that launches a local stdio server, use the equivalent of:
```json
{
"mcpServers": {
"skipper": {
"command": "/usr/bin/python3.12",
"args": [
"/home/oracle/skipper/bin/skipper-mcp",
"--stdio"
],
"env": {
"ORACLE_SID": "rick",
"ORACLE_HOME": "/u01/app/oracle/product/19.19/dbhome_1"
}
}
}
}
```
Client configuration formats differ, so adapt the outer JSON structure to the
client while keeping the command, URL, and environment values.
### Verify the HTTP API manually
Initialize the MCP connection:
```bash
curl -sS http://127.0.0.1:6751/mcp \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"manual-test","version":"1.0"}}}'
```
List the registered tools:
```bash
curl -sS http://127.0.0.1:6751/mcp \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
```
Call a tool:
```bash
curl -sS http://127.0.0.1:6751/mcp \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"oracle_select","arguments":{"query":"select instance_name, status from v$instance"}}}'
```
## Using the tools
The MCP `tools/list` response is the authoritative description of every argument.
The most common workflows are summarized below.
### Inspect and edit Oracle blocks with RICO2
Call `rico2` with a RICO2 listfile and either one command or an ordered command
array. Every command in an array runs in the same isolated session, so location,
offset, edit mode, the current block buffer, and its undo snapshot are preserved:
```json
{
"listfile": "/home/oracle/rico2-listfile.txt",
"commands": [
"set dba 7,120",
"show",
"map",
"print kdbh",
"print *kdbr[0]",
"examine /rncc",
"verify"
]
}
```
The adapter accepts the complete interactive command set: `INFO`, `SHOW`,
`SET`, `DUMP`/`D`, `MAP`, `PRINT`/`P`, `EXAMINE`/`X`, `FIND`, `SELECT`,
`MODIFY`/`ASSIGN`, `SUM`/`CHECKSUM`, `VERIFY`, `COPY`, `UNDO`,
`REVERT`/`DUPA`, `SAVE`, `HELP`, and `EXIT`/`QUIT`. See
[`tools/rico2/README.md`](tools/rico2/README.md) for syntax and block structure
details.
`MODIFY` and `ASSIGN` require `"confirm_modifications": true`; this replaces the
interactive `Y/N` prompt. Commands that immediately write a data file (`SAVE`,
`COPY`, `REVERT`, and `DUPA`) additionally require
`"allow_disk_writes": true`. RICO2 still requires `SET MODE EDIT` where the
underlying command requires it. Both confirmations default to false.
```json
{
"listfile": "/home/oracle/rico2-listfile.txt",
"commands": [
"set dba 7,120",
"set offset 100",
"modify -h deadbeef",
"set mode edit",
"sum apply",
"verify",
"save"
],
"confirm_modifications": true,
"allow_disk_writes": true
}
```
RICO2 writes raw Oracle data-file blocks and does not coordinate with a running
database. Use write commands only on verified backups or isolated laboratory
copies. A fresh RICO2 session is created for every MCP call; put dependent
commands in one array.
### Run a read-only Oracle query
Call `oracle_select` with one `SELECT` statement or a query beginning with
`WITH`:
```json
{
"query": "select instance_name, status from v$instance"
}
```
The tool connects with `sqlplus / as sysdba` and returns pipe-delimited CSV.
Additional SQL statements and SQL*Plus commands are rejected. The default timeout
is 120 seconds and the default output limit is 1 MiB.
Whenever the query reads `USER$`, the result includes a security follow-up that
encourages the model to call `oracle_password_check` for each non-empty
`SPARE4` value containing an `S:` or `T:` verifier.
### Check an Oracle password verifier against the dictionary
Call `oracle_password_check` with the complete `USER$.SPARE4` value. It may
contain the Oracle 11g `S:` verifier, the Oracle 12c `T:` verifier, or both:
```json
{
"spare4": "S:9DEA3FA1EDC6087AD05C7E8AE3FD179D4E93CF9BBE12100B315AB84BC247"
}
```
The tool ports the algorithms from `oracle-Password`: salted SHA-1 for `S:` and
PBKDF2-HMAC-SHA512 plus SHA-512 for `T:`. It reports which dictionary file was
used, which verifier versions were checked, how many candidates were checked,
and whether a password was found. It prefers `/home/oracle/skipper/dict.txt`; if
that file is absent, it uses only `/home/oracle/skipper/data/dict.example.txt`.
This is an offline audit of password strength and does not attempt to log in to
the database.
### Find a fixed-view definition
Call `oracle_view_definition`:
```json
{
"view_name": "GV$SQL"
}
```
If SQL*Plus truncates a long definition, Skipper can search the SQL embedded in
`$ORACLE_HOME/bin/oracle` using `strings` and `grep`. Pass `"fallback": false` to
disable this behavior.
### Search an Oracle SGA
Call `focs_vm` with a process PID and a UTF-8 string:
```json
{
"pid": 2374,
"pattern": "select instance_name from v$instance"
}
```
For a byte pattern, use hexadecimal tokens:
```json
{
"pid": 2374,
"pattern": "73 65 6c 65 63 74",
"pattern_type": "hex",
"memory_size": "6G"
}
```
If `memory_size` is omitted, Skipper selects the largest SysV shared-memory
segment mapped by the process. A human-readable value such as `6G` is matched to
the nearest actual mapped size; a plain integer is treated as an exact byte count.
### Infer an X$ structure from a V$ or GV$ view
Call `oracle_structure_from_x`:
```json
{
"view_name": "GV$SQL",
"match_column": "SQL_FULLTEXT",
"match_value": "select instance_name from v$instance",
"pid": 2374,
"memory_size": "6G",
"output_path": "/tmp/gvsql-structure.json"
}
```
The tool resolves the backing X$ table, maps view columns, finds matching X$
rows, builds binary anchors, scans the SGA, clusters candidate offsets, and writes
a JSON report. If `pid` is omitted, it discovers PMON using `ORACLE_SID`. If
`output_path` is omitted, the report is written under `/tmp`.
### Trace an Oracle C function
The persistent debugger workflow separates process control from focused GDB
investigation:
1. Call `oracle_debug_start_session` to start SQL*Plus and obtain a
`debug_session_id`, Oracle SID, and server-process SPID.
2. Call `oracle_debug_attach` with that ID and a breakpoint location such as
`kksLoadChild` or `kksLoadChild+0x2a`. The server process remains stopped.
3. Call `oracle_debug_trigger` with SQL that should hit the breakpoint.
4. Use one or more investigation tools while GDB is stopped:
`oracle_debug_inspect`, `oracle_debug_backtrace`, `oracle_debug_evaluate`,
`oracle_debug_set_variable`, `oracle_debug_memory`, or
`oracle_debug_disassemble`.
5. Always call `oracle_debug_close` to detach GDB and terminate SQL*Plus.
Minimal argument examples:
```json
{"connection":"/ as sysdba"}
{"debug_session_id":"ID_FROM_STEP_1","breakpoint":"kksLoadChild+0x2a"}
{"debug_session_id":"ID_FROM_STEP_1","sql":"select 1 from dual"}
{"debug_session_id":"ID_FROM_STEP_1"}
{"debug_session_id":"ID_FROM_STEP_1","full":false,"max_frames":32}
{"debug_session_id":"ID_FROM_STEP_1","expression":"$x0","format":"hex"}
{"debug_session_id":"ID_FROM_STEP_1","function":"kksLoadChild"}
{"debug_session_id":"ID_FROM_STEP_1"}
```
The focused tools map common GDB operations to structured MCP arguments:
| MCP tool | GDB equivalent | Purpose |
| --- | --- | --- |
| `oracle_debug_backtrace` | `bt` or `bt full` | Return the call stack, optionally with locals and a frame limit. |
| `oracle_debug_evaluate` | `p`, `p/x`, and related formats | Evaluate registers, symbols, casts, dereferences, and pointer arithmetic. |
| `oracle_debug_set_variable` | `set $name = expression` | Preserve a register or calculated pointer in a GDB convenience variable. |
| `oracle_debug_memory` | `x/nfu address` | Read strings, integers, pointers, raw bytes, or instructions from a chosen address. |
| `oracle_debug_inspect` | `bt full`, `info args`, `info registers`, and ABI probes | Collect a broad diagnostic snapshot in one call. |
| `oracle_debug_disassemble` | `disassemble /r function` | Return assembly together with instruction bytes. |
For example, the following calls reproduce an NLS pointer investigation based on
AArch64 argument registers:
```json
{"debug_session_id":"ID_FROM_STEP_1","name":"new_nls","expression":"$x0"}
{"debug_session_id":"ID_FROM_STEP_1","name":"prev_nls","expression":"$x1"}
{"debug_session_id":"ID_FROM_STEP_1","address":"$new_nls+0xa0","count":1,"unit":"byte","format":"string"}
{"debug_session_id":"ID_FROM_STEP_1","address":"$prev_nls+0xa0","count":1,"unit":"byte","format":"string"}
```
These become `set $new_nls = $x0`, `set $prev_nls = $x1`,
`x/1bs $new_nls+0xa0`, and `x/1bs $prev_nls+0xa0`. Use the optional `frame`
argument with evaluate, set-variable, or memory calls to select a stack frame
before resolving its registers and symbols. Evaluation formats are `natural`,
`hex`, `signed`, `unsigned`, `octal`, `binary`, `address`, `char`, and `float`.
Memory units are `byte`, `halfword`, `word`, and `giant`; memory formats also
include `string` and `instruction`.
The interaction tools do not accept raw GDB commands. Expressions are limited to
a single non-command line, assignments and increment/decrement expressions are
rejected, and inferior function calls are disabled when GDB attaches. Process
control remains confined to the lifecycle tools.
`oracle_debug_attach` accepts three deliberately constrained breakpoint forms:
| Form | Example | GDB behavior |
| --- | --- | --- |
| C symbol | `kksLoadChild` | Break at the function entry point. |
| Symbol plus offset | `kksLoadChild+0x2a` or `kksLoadChild+42` | Break at the exact instruction address resolved from the loaded symbol. |
| Absolute hexadecimal address | `0x000000001234abcd` | Break at that exact virtual address in the attached process. |
Offsets are non-negative and may be decimal or hexadecimal. Use an offset that
points to an instruction boundary; for example, the address immediately after a
`call` instruction can be used to inspect its return value. GDB resolves symbolic
locations after attaching, so `symbol+offset` continues to work when the Oracle
image is relocated by ASLR. Absolute addresses are process-specific. Arbitrary
GDB expressions are intentionally rejected to keep MCP input from becoming a GDB
command channel.
## Configuration reference
Command-line options take precedence over environment defaults.
| Setting | Default | Purpose |
| --- | --- | --- |
| `-b`, `--bind`, `--host` / `SKIPPER_MCP_HOST` | `0.0.0.0` | HTTP listener address. |
| `-p`, `--port` / `SKIPPER_MCP_PORT` | `6751` | HTTP listener port. |
| `--path` / `SKIPPER_MCP_PATH` | `/mcp` | MCP HTTP endpoint. |
| `--stdio` | off | Use standard input/output instead of HTTP. |
| `SKIPPER_ALLOWED_ORIGINS` | empty | Comma-separated browser origins allowed to send HTTP requests. |
| `FOCS_VM_BIN` | bundled release path | Override the `focs_vm` executable. |
| `SKIPPER_FOCS_USE_SUDO` | false | Run `focs_vm` through `sudo -n`. |
| `SQLPLUS_BIN` | auto-detected | Override the SQL*Plus executable. |
| `ORACLE_BIN` | `$ORACLE_HOME/bin/oracle` | Override the Oracle executable used by GDB and the strings fallback. |
| `GDB_BIN` | `gdb` | Override the GDB executable. |
| `SKIPPER_GDB_USE_SUDO` | false | Run GDB through `sudo -n`. |
Skipper supports MCP protocol revisions `2025-11-25`, `2025-06-18`, and
`2024-11-05`.
## Network security
The built-in HTTP transport does not provide authentication or TLS. Bind to
`127.0.0.1` when only local access is required. For remote access, restrict port
6751 to trusted hosts with a firewall or place the service behind an authenticated
TLS proxy or SSH tunnel. Do not publish the endpoint on a public interface.
## Updating
```bash
cd /home/oracle/skipper
git pull --ff-only
source "$HOME/.cargo/env"
cargo build --locked --manifest-path tools/focs_vm/Cargo.toml --release
PYTHONPATH=src /usr/bin/python3.12 -m unittest discover -s tests -v
sudo systemctl restart skipper-mcp.service
curl --fail http://127.0.0.1:6751/health
```
## Development
Run all tests from the repository root:
```bash
PYTHONPATH=src /usr/bin/python3.12 -m unittest discover -s tests -v
```
Tool adapters live in `src/skipper_mcp/tools/`. A new adapter defines its handler
and a `register(registry)` function, and is then imported by
`src/skipper_mcp/tools/__init__.py`.
## License
See [LICENSE](LICENSE).
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available