From eccea564ddbae2eb84c8d2ca80508ee96e0f7775 Mon Sep 17 00:00:00 2001 From: Marcelo Ochoa Date: Wed, 19 Mar 2025 11:17:35 -0300 Subject: [PATCH] feat: add stats endpoint for SQL object and update README with Docker AI usage --- src/oracle/README.md | 60 ++++++++++++++++++++++++++++++- src/oracle/gordon-mcp.yml | 9 +++++ src/oracle/index.ts | 75 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 src/oracle/gordon-mcp.yml diff --git a/src/oracle/README.md b/src/oracle/README.md index d4fc4a23..eb7aa9ff 100644 --- a/src/oracle/README.md +++ b/src/oracle/README.md @@ -65,7 +65,11 @@ To use this server with the Claude Desktop app, add the following configuration "-y", "@modelcontextprotocol/server-oracle", "host.docker.internal:1521/freepdb1" - ] + ], + "env": { + "ORACLE_USER": "scott", + "ORACLE_PASSWORD": "tiger" + } } } } @@ -93,6 +97,60 @@ See in action using Claude Desktop App ![Oracle MCP Server demo](./demo-prompts.gif) +### Using Docker AI + +[Ask Gordon](https://docs.docker.com/desktop/features/gordon/) is an AI assistant designed to streamline your Docker workflow by providing contextual assistance tailored to your local environment. Currently in Beta and available in Docker Desktop version 4.38.0 or later, Ask Gordon offers intelligent support for various Docker-related tasks. + +```sh +% cd src/oracle +% docker ai 'stats for table countries' + + • Calling stats ✔️ + + Here are the statistics for the COUNTRIES table: + + ### Table Statistics: + + • Owner: HR + • Table Name: COUNTRIES + • Number of Rows: 25 + • Average Row Length: 16 bytes + • Last Analyzed: 2025-03-10 22:00:38 + + ### Index Statistics: + + • Index Name: COUNTRY_C_ID_PK + • B-Level: 0 + • Leaf Blocks: 1 + • Distinct Keys: 25 + • Number of Rows: 25 + • Clustering Factor: 0 + • Last Analyzed: 2025-03-10 22:00:38 + + ### Column Statistics: + + 1. COUNTRY_ID: + + • Number of Distinct Values: 25 + • Density: 0.04 + • Histogram: NONE + • Last Analyzed: 2025-03-10 22:00:38 + + 2. COUNTRY_NAME: + + • Number of Distinct Values: 25 + • Density: 0.04 + • Histogram: NONE + • Last Analyzed: 2025-03-10 22:00:38 + + 3. REGION_ID: + + • Number of Distinct Values: 5 + • Density: 0.02 + • Histogram: FREQUENCY + • Last Analyzed: 2025-03-10 22:00:38 +``` + ## Building Docker: diff --git a/src/oracle/gordon-mcp.yml b/src/oracle/gordon-mcp.yml new file mode 100644 index 00000000..5c52513d --- /dev/null +++ b/src/oracle/gordon-mcp.yml @@ -0,0 +1,9 @@ +services: + time: + image: mcp/time + oracle: + image: mcp/oracle + command: ["host.docker.internal:1521/freepdb1"] + environment: + - ORACLE_USER=hr + - ORACLE_PASSWORD=hr_2025 diff --git a/src/oracle/index.ts b/src/oracle/index.ts index 95dd6e83..9acea3b2 100644 --- a/src/oracle/index.ts +++ b/src/oracle/index.ts @@ -179,6 +179,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { }, }, }, + { + name: "stats", + description: "Stats for SQL object", + inputSchema: { + type: "object", + properties: { + name: { type: "string" }, + }, + }, + }, ], }; }); @@ -236,6 +246,71 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } } } + if (request.params.name === "stats") { + const tableName = request.params.arguments?.name as string; + let connection; + try { + connection = await pool.getConnection(); + const result = await connection.execute<{ STATS_JSON: string }>(`SELECT JSON_OBJECT( + 'table_stats' VALUE ( + SELECT JSON_OBJECT( + 'owner' VALUE owner, + 'table_name' VALUE table_name, + 'num_rows' VALUE num_rows, + 'blocks' VALUE blocks, + 'empty_blocks' VALUE empty_blocks, + 'avg_row_len' VALUE avg_row_len, + 'last_analyzed' VALUE TO_CHAR(last_analyzed, 'YYYY-MM-DD HH24:MI:SS') + ) + FROM dba_tab_statistics + WHERE owner = UPPER(USER) AND table_name = UPPER(:tableName) + ), + 'index_stats' VALUE ( + SELECT JSON_ARRAYAGG( + JSON_OBJECT( + 'index_name' VALUE index_name, + 'blevel' VALUE blevel, + 'leaf_blocks' VALUE leaf_blocks, + 'distinct_keys' VALUE distinct_keys, + 'num_rows' VALUE num_rows, + 'clustering_factor' VALUE clustering_factor, + 'last_analyzed' VALUE TO_CHAR(last_analyzed, 'YYYY-MM-DD HH24:MI:SS') + ) + ) + FROM dba_ind_statistics + WHERE table_owner = UPPER(USER) AND table_name = UPPER(:tableName) + ), + 'column_stats' VALUE ( + SELECT JSON_ARRAYAGG( + JSON_OBJECT( + 'column_name' VALUE column_name, + 'num_distinct' VALUE num_distinct, + 'density' VALUE density, + 'histogram' VALUE histogram, + 'last_analyzed' VALUE TO_CHAR(last_analyzed, 'YYYY-MM-DD HH24:MI:SS') + ) + ) + FROM dba_tab_col_statistics + WHERE owner = UPPER(USER) AND table_name = UPPER(:tableName) + ) +) AS stats_json +FROM dual`, [tableName,tableName,tableName], { outFormat: oracledb.OUT_FORMAT_OBJECT }); + return { + content: [{ type: "text", text: result.rows?.[0]?.STATS_JSON }], + isError: false, + }; + } catch (error) { + throw error; + } finally { + if (connection) { + try { + await connection.close(); + } catch (err) { + console.warn("Could not close connection:", err); + } + } + } + } throw new Error(`Unknown tool: ${request.params.name}`); });