feat: add Oracle MCP documentation and integration tests for wallet and OCI connections

This commit is contained in:
RUN
2026-01-22 08:39:03 +01:00
parent f26750e834
commit 0797142103
4 changed files with 159 additions and 29 deletions

View File

@@ -689,8 +689,7 @@ See [Usage Examples](../reference/cli.md#examples).
* **Permissions:**
* **Database-level permissions** are required to execute Cypher queries.
* **Tools:**
* `ORACLE_WALLET`: The path to Oracle DB Wallet file for the databases that support this authentication type
* `USE_OCI`: true or false, The flag if the Oracle Database is deployed in cloud deployment
## Oracle
@@ -698,6 +697,11 @@ See [Usage Examples](../reference/cli.md#examples).
* **Environment Variables:**
* `ORACLE_HOST`: The hostname or IP address of the Oracle server.
* `ORACLE_PORT`: The port number for the Oracle server (Default: 1521).
* `ORACLE_CONNECTION_STRING`: The
* `ORACLE_USER`: The
* `ORACLE_PASSWORD`: The
* `ORACLE_WALLET`: The path to Oracle DB Wallet file for the databases that support this authentication type
* `ORACLE_USE_OCI`: true or false, The flag if the Oracle Database is deployed in cloud deployment
* **Permissions:**
* Database-level permissions (e.g., `SELECT`, `INSERT`) are required to
@@ -710,6 +714,11 @@ See [Usage Examples](../reference/cli.md#examples).
* `list_top_sql_by_resource`: Lists top SQL statements by resource usage.
* `list_tablespace_usage`: Lists tablespace usage.
* `list_invalid_objects`: Lists invalid objects.
* `list_active_sessions`: Lists active database sessions.
* `get_query_plan`: Gets the execution plan for a SQL statement.
* `list_top_sql_by_resource`: Lists top SQL statements by resource usage.
* `list_tablespace_usage`: Lists tablespace usage.
* `list_invalid_objects`: Lists invalid objects.
## Google Cloud Healthcare API
* `--prebuilt` value: `cloud-healthcare`

View File

@@ -15,27 +15,26 @@
sources:
oracle-source:
kind: "oracle"
connectionString: $ORACLE_CONNECTION_STRING
walletLocation: $ORACLE_WALLET
user: $ORACLE_USER
password: $ORACLE_PASSWORD
useOCI: $USE_OCI:false
tools:
execute_sql:
kind: oracle-execute-sql
source: oracle-source
description: Use this tool to execute a single SQL query or DML statement.
connectionString: ${ORACLE_CONNECTION_STRING}
walletLocation: ${ORACLE_WALLET:}
user: ${ORACLE_USER}
password: ${ORACLE_PASSWORD}
useOCI: ${ORACLE_USE_OCI:false}
tools:
list_tables:
kind: oracle-sql
source: oracle-source
description: "Lists all user tables in the connected schema, including segment size, row count, and last analyzed date. Filters by a comma-separated list of names. If names are omitted, lists all tables in the current user's schema. SELECT table_name from user_tables;"
description: "Lists all user tables in the connected schema, including segment size, row count, and last analyzed date. Filters by a comma-separated list of names. If names are omitted, lists all tables in the current user's schema."
statement: SELECT table_name from user_tables;
list_active_sessions:
kind: oracle-sql
source: oracle-source
description: "List the top N (default 50) currently running database sessions (STATUS='ACTIVE'), showing SID, OS User, Program, and the current SQL statement text.
SELECT
description: "List the top N (default 50) currently running database sessions (STATUS='ACTIVE'), showing SID, OS User, Program, and the current SQL statement text."
statement: |
SELECT
s.sid,
s.serial#,
s.username,
@@ -53,18 +52,19 @@ tools:
AND s.sql_id = sql.sql_id (+)
AND s.audsid != userenv('sessionid') -- Exclude current session
ORDER BY s.last_call_et DESC
FETCH FIRST COALESCE(10) ROWS ONLY;"
FETCH FIRST COALESCE(10) ROWS ONLY;
get_query_plan:
kind: oracle-execute-sql
kind: oracle-sql
source: oracle-source
description: "Generate a full execution plan for a single SQL statement. This can be used to analyze query performance without execution. Requires the SQL statement as input. following is an example EXPLAIN PLAN FOR {{&query}};
SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());"
description: "Generate a full execution plan for a single SQL statement. This can be used to analyze query performance without execution. Requires the SQL statement as input. following is an example EXPLAIN PLAN FOR {{&query}};"
statement: SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY());
list_top_sql_by_resource:
kind: oracle-sql
source: oracle-source
description: "List the top N SQL statements from the library cache based on a chosen resource metric (CPU, I/O, or Elapsed Time), following is an example of the sql
description: "List the top N SQL statements from the library cache based on a chosen resource metric (CPU, I/O, or Elapsed Time), following is an example of the sql"
statement: |
SELECT
sql_id,
executions,
@@ -74,12 +74,13 @@ tools:
elapsed_time / 1000000 AS elapsed_seconds
FROM
v$sql
FETCH FIRST 5 ROWS ONLY;"
FETCH FIRST 5 ROWS ONLY;
list_tablespace_usage:
kind: oracle-sql
source: oracle-source
description: "List tablespace names, total size, free space, and used percentage to monitor storage utilization.
description: "List tablespace names, total size, free space, and used percentage to monitor storage utilization."
statement: |
SELECT
t.tablespace_name,
TO_CHAR(t.total_bytes / 1024 / 1024, '99,999.00') AS total_mb,
@@ -93,7 +94,7 @@ tools:
GROUP BY
t.tablespace_name, t.total_bytes
ORDER BY
used_pct DESC"
used_pct DESC;
list_invalid_objects:
kind: oracle-sql
@@ -111,7 +112,7 @@ tools:
status = 'INVALID'
AND owner NOT IN ('SYS', 'SYSTEM') -- Exclude system schemas for clarity
ORDER BY
owner, object_type, object_name;
owner, object_type, object_name;
toolsets:
oracle_database_tools:
@@ -121,4 +122,4 @@ toolsets:
- get_query_plan
- list_top_sql_by_resource
- list_tablespace_usage
- list_invalid_objects
- list_invalid_objects

View File

@@ -9,6 +9,7 @@ import (
"os"
"regexp"
"strings"
"strconv"
"testing"
"time"
@@ -96,8 +97,8 @@ func TestOracleTools(t *testing.T) {
defer teardownTable1(t)
// set up data for auth tool
createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := getOracleAuthToolInfo(tableNameAuth)
teardownTable2 := setupOracleTable(t, ctx, db, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams)
createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := getOracleAuthToolInfo(t, tableNameAuth)
teardownTable2 := setupOracleTable(t, ctx, db, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams...)
defer teardownTable2(t)
// Write config into a file and pass it to command
@@ -150,6 +151,125 @@ func TestOracleTools(t *testing.T) {
})
}
func TestOracleConnectionPureGoWithWallet(t *testing.T) {
t.Parallel()
// This test expects the connection to fail because the wallet file won't exist.
// It verifies that the walletLocation parameter is correctly passed to the pure Go driver.
// Save original env vars and restore them at the end
cleanup := setOracleEnv(t,
oracleHost, oracleUser, oraclePassword, oracleService, oraclePort, // Use existing base connection details
"", // connectionString
"", // tnsAlias
"", // tnsAdmin
"/tmp/nonexistent_wallet", // walletLocation
false, // useOCI
)
defer cleanup()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cfg := getOracleConfigFromEnv(t)
_, err := cfg.Initialize(ctx, nil) // Pass nil for tracer as it's not critical for this test
if err == nil {
t.Fatalf("Expected connection to fail with non-existent wallet, but it succeeded")
}
// Check for error message indicating wallet usage or connection failure related to wallet
// The exact error message might vary depending on the go-ora version and OS.
// We are looking for an error that suggests the wallet path was attempted.
expectedErrorSubstring := "wallet"
if !strings.Contains(strings.ToLower(err.Error()), expectedErrorSubstring) {
t.Errorf("Expected error message to contain '%s' (case-insensitive) but got: %v", expectedErrorSubstring, err)
}
t.Logf("Connection failed as expected (Pure Go with Wallet): %v", err)
}
func TestOracleConnectionOCI(t *testing.T) {
t.Parallel()
// This test verifies that the useOCI=true parameter is correctly passed to the OCI driver.
// It will likely fail if Oracle Instant Client is not installed or configured.
// Save original env vars and restore them at the end
cleanup := setOracleEnv(t,
oracleHost, oracleUser, oraclePassword, oracleService, oraclePort, // Use existing base connection details
"", // connectionString
"", // tnsAlias
"", // tnsAdmin
"", // walletLocation
true, // useOCI
)
defer cleanup()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cfg := getOracleConfigFromEnv(t)
_, err := cfg.Initialize(ctx, nil)
if err == nil {
t.Fatalf("Expected connection to fail (OCI driver without Instant Client), but it succeeded")
}
// Check for error message indicating OCI driver usage or connection failure related to OCI.
// Common errors include "OCI environment not initialized", "driver: bad connection", etc.
expectedErrorSubstrings := []string{"oci", "driver", "connection"}
foundExpectedError := false
for _, sub := range expectedErrorSubstrings {
if strings.Contains(strings.ToLower(err.Error()), sub) {
foundExpectedError = true
break
}
}
if !foundExpectedError {
t.Errorf("Expected error message to contain one of %v (case-insensitive) but got: %v", expectedErrorSubstrings, err)
}
t.Logf("Connection failed as expected (OCI Driver): %v", err)
}
func TestOracleConnectionOCIWithWallet(t *testing.T) {
t.Parallel()
// This test verifies that useOCI=true and tnsAdmin parameters are correctly passed for OCI wallet.
// It will likely fail due to missing tnsnames.ora and wallet files.
// Save original env vars and restore them at the end
cleanup := setOracleEnv(t,
"", oracleUser, oraclePassword, "", "", // Unset host/port/service for TNS alias, but keep user/pass
"", // connectionString
"MY_TNS_ALIAS", // tnsAlias
"/tmp/nonexistent_tns_admin", // tnsAdmin
"", // walletLocation
true, // useOCI
)
defer cleanup()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cfg := getOracleConfigFromEnv(t)
_, err := cfg.Initialize(ctx, nil)
if err == nil {
t.Fatalf("Expected connection to fail (OCI driver with TNS Admin/Wallet), but it succeeded")
}
// Check for error message indicating TNS Admin/Wallet usage or connection failure.
expectedErrorSubstrings := []string{"tns", "wallet", "oci", "driver", "connection"}
foundExpectedError := false
for _, sub := range expectedErrorSubstrings {
if strings.Contains(strings.ToLower(err.Error()), sub) {
foundExpectedError = true
break
}
}
if !foundExpectedError {
t.Errorf("Expected error message to contain one of %v (case-insensitive) but got: %v", expectedErrorSubstrings, err)
}
t.Logf("Connection failed as expected (OCI Driver with TNS Admin/Wallet): %v", err)
}
func setupOracleTable(t *testing.T, ctx context.Context, pool *sql.DB, createStatement, insertStatement, tableName string, params []any) func(*testing.T) {
err := pool.PingContext(ctx)
if err != nil {
@@ -205,7 +325,7 @@ func getOracleParamToolInfo(tableName string) (string, string, string, string, s
}
// getOracleAuthToolInfo returns statements and params for my-auth-tool for Oracle SQL
func getOracleAuthToolInfo(tableName string) (string, string, string, []any) {
func getOracleAuthToolInfo(t *testing.T, tableName string) (string, string, string, []any) {
createStatement := fmt.Sprintf(`CREATE TABLE %s ("id" NUMBER GENERATED AS IDENTITY PRIMARY KEY, "name" VARCHAR2(255), "email" VARCHAR2(255))`, tableName)
// MODIFIED: Use a PL/SQL block for multiple inserts