fix(mcp): add direct Redis command execution

This commit is contained in:
t8y2 committed 2026-06-26 22:27:31 +08:00
1 parent 76e2d33cd3
commit 23b44cc2b9
15 files changed
+774 -26

No files matched your search

+60
View File
@@ -6,6 +6,7 @@ import { z } from "zod";
import {
buildSchemaContext,
createBackend,
evaluateRedisCommandSafety,
evaluateMongoAggregateSafety,
evaluateSqlSafety,
formatCell,
@@ -20,6 +21,7 @@ import {
type Backend,
type ConnectionConfig,
type QueryResult,
type RedisCommandResult,
} from "@dbx-app/node-core";
const require = createRequire(import.meta.url);
@@ -45,6 +47,26 @@ function formatQueryToolResult(result: QueryResult, title?: string) {
return text(`${prefix}${mdTable(result.columns, rows)}\n\n${result.row_count} row(s)`);
}
function redisDbFromValue(value?: string): number | undefined {
const trimmed = value?.trim();
if (!trimmed) return undefined;
const db = Number(trimmed);
return Number.isInteger(db) && db >= 0 ? db : undefined;
}
function defaultRedisDb(config: ConnectionConfig, scope: McpScope, db?: number): number {
return db ?? redisDbFromValue(scope.database) ?? redisDbFromValue(config.database) ?? 0;
}
function formatRedisCommandValue(value: unknown): string {
if (typeof value === "string") return value;
return JSON.stringify(value, null, 2) ?? String(value);
}
function formatRedisCommandToolResult(result: RedisCommandResult) {
return text(`Command: ${result.command}\nSafety: ${result.safety}\n\n${formatRedisCommandValue(result.value)}`);
}
export const DBX_CONNECTION_TYPE_DESCRIPTION =
"Database type: postgres, mysql, sqlite, rqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, etcd, doris, starrocks, manticoresearch, milvus, qdrant, weaviate, chromadb, redshift, dameng, kingbase, highgo, vastbase, goldendb, databend, gaussdb, kwdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, questdb, gbase, h2, snowflake, trino, prestosql, hive, db2, informix, influxdb, iris, neo4j, cassandra, bigquery, kylin, sundb, tdengine, iotdb, xugu, zookeeper, jdbc, access, mq";
const FILE_CAPABLE_CONNECTION_TYPES = new Set(["sqlite", "duckdb", "access", "h2"]);
@@ -162,6 +184,12 @@ export function createDbxMcpServer(backend: Backend, options: { isWebMode?: bool
const { config, error } = await resolveConnection(backend, scope, connection_name);
if (error) return error;
const scopedConfig = config!;
if (scopedConfig.db_type === "redis") {
return toolError(
"REDIS_COMMAND_REQUIRED",
"Redis connections do not accept SQL through dbx_execute_query. Use dbx_execute_redis_command with a Redis command such as GET key or INFO.",
);
}
if (scopedConfig.db_type !== "mongodb") {
const safety = evaluateSqlSafety(sql, { ...sqlSafetyFromEnv(), allowMultipleStatements: true });
if (!safety.allowed) return toolError("SQL_BLOCKED", safety.reason ?? "SQL blocked.");
@@ -183,6 +211,38 @@ export function createDbxMcpServer(backend: Backend, options: { isWebMode?: bool
},
);
server.tool(
"dbx_execute_redis_command",
"Execute a Redis command on a Redis connection",
{
connection_name: z.string().optional().describe("Name of the DBX Redis connection"),
db: z.number().int().min(0).optional().describe("Redis logical database number (default: scoped/default database or 0)"),
command: z.string().describe("Redis command to execute, for example: GET mykey, INFO, or DBSIZE"),
},
async ({ connection_name, db, command }) => {
const { config, error } = await resolveConnection(backend, scope, connection_name);
if (error) return error;
const scopedConfig = config!;
if (scopedConfig.db_type !== "redis") {
return toolError("INVALID_CONNECTION_TYPE", `Connection "${scopedConfig.name}" is ${scopedConfig.db_type}, not Redis.`);
}
if (!backend.executeRedisCommand) {
return toolError("UNSUPPORTED_BACKEND", "This DBX backend does not support Redis command execution.");
}
const safety = evaluateRedisCommandSafety(command, sqlSafetyFromEnv());
if (!safety.allowed) return toolError("REDIS_COMMAND_BLOCKED", safety.reason ?? "Redis command blocked.");
try {
const result = await backend.executeRedisCommand(scopedConfig, defaultRedisDb(scopedConfig, scope, db), command, {
skipSafetyCheck: safety.skipSafetyCheck,
});
return formatRedisCommandToolResult(result);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
return toolError("REDIS_COMMAND_ERROR", msg);
}
},
);
server.tool(
"dbx_get_schema_context",
"Get compact table and column context for writing SQL",