Skip to content

Commit c7c8460

Browse files
JayLi52lexburner
andauthored
feat(mcp): 支持 PostgreSQL 和 ClickHouse 数据库连接 (higress-group#579)
Co-authored-by: xujingfeng <xujingfeng95@gmail.com>
1 parent bc8dd09 commit c7c8460

File tree

3 files changed

+68
-18
lines changed

3 files changed

+68
-18
lines changed

frontend/src/pages/mcp/components/DatabaseConfig.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,23 @@ export const DB_FIXED_FIELDS = [
5858

5959
export const computeDSN = (values: any) => {
6060
const { db_type, db_user_name, db_password, db_server_host, db_database, db_server_port } = values;
61-
if (!db_type || !db_user_name || !db_password || !db_server_host || !db_database) return '';
62-
// eslint-disable-next-line max-len
61+
if (!db_type || !db_user_name || !db_password || !db_server_host || !db_server_port) return '';
62+
// PostgreSQL: postgres://username:password@host:port/dbname
63+
if (db_type === 'POSTGRESQL') {
64+
if (!db_database) return '';
65+
return `postgres://${db_user_name}:${db_password}@${db_server_host}:${db_server_port}/${db_database}`;
66+
}
67+
// ClickHouse: tcp://host:port?database=xxx&username=xxx&password=xxx
68+
if (db_type === 'CLICKHOUSE') {
69+
const query = new URLSearchParams({
70+
database: db_database || '',
71+
username: db_user_name,
72+
password: db_password,
73+
}).toString();
74+
return `tcp://${db_server_host}:${db_server_port}?${query}`;
75+
}
76+
// MySQL (默认): username:password@tcp(host:port)/dbname?...
77+
if (!db_database) return '';
6378
return `${db_user_name}:${db_password}@tcp(${db_server_host}:${db_server_port})/${db_database}?charset=utf8mb4&parseTime=True&loc=Local`;
6479
};
6580

frontend/src/pages/mcp/components/McpFormDrawer.tsx

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,35 @@ const McpFormDrawer: React.FC<McpFormDrawerProps> = ({ visible, mode, name, onCl
6565
form.resetFields();
6666
} else {
6767
if (record && record.dsn) {
68-
const match = record.dsn.match(REG_DSN_STRING.DEFAULT);
69-
if (match) {
70-
form.setFieldsValue({
71-
db_user_name: match[1],
72-
db_password: match[2],
73-
db_database: match[5],
74-
});
68+
const type = record?.dbType;
69+
let m: RegExpMatchArray | null = null;
70+
if (type === 'MYSQL') {
71+
m = record.dsn.match(REG_DSN_STRING.MYSQL);
72+
if (m) {
73+
form.setFieldsValue({
74+
db_user_name: m[1],
75+
db_password: m[2],
76+
db_database: m[5],
77+
});
78+
}
79+
} else if (type === 'POSTGRESQL') {
80+
m = record.dsn.match(REG_DSN_STRING.POSTGRESQL);
81+
if (m) {
82+
form.setFieldsValue({
83+
db_user_name: m[1],
84+
db_password: m[2],
85+
db_database: m[5],
86+
});
87+
}
88+
} else if (type === 'CLICKHOUSE') {
89+
m = record.dsn.match(REG_DSN_STRING.CLICKHOUSE);
90+
if (m) {
91+
form.setFieldsValue({
92+
db_user_name: m[4],
93+
db_password: m[5],
94+
db_database: m[3],
95+
});
96+
}
7597
}
7698
}
7799
form.setFieldsValue({
@@ -331,7 +353,20 @@ const McpFormDrawer: React.FC<McpFormDrawerProps> = ({ visible, mode, name, onCl
331353
{
332354
required: true,
333355
validator: (_, value) => {
334-
if (!value || !value.match(REG_DSN_STRING.DEFAULT)) {
356+
const values = form.getFieldsValue();
357+
const type = values?.db_type;
358+
if (!value) {
359+
return Promise.reject(new Error(t('mcp.form.databaseConfigInvalid')!));
360+
}
361+
let valid = false;
362+
if (type === 'MYSQL') {
363+
valid = REG_DSN_STRING.MYSQL.test(value);
364+
} else if (type === 'POSTGRESQL') {
365+
valid = REG_DSN_STRING.POSTGRESQL.test(value);
366+
} else if (type === 'CLICKHOUSE') {
367+
valid = REG_DSN_STRING.CLICKHOUSE.test(value);
368+
}
369+
if (!valid) {
335370
return Promise.reject(new Error(t('mcp.form.databaseConfigInvalid')!));
336371
}
337372
return Promise.resolve();

frontend/src/pages/mcp/constant.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@ export function getServiceTypeMap(directRouteText: string) {
1313
[SERVICE_TYPE.DIRECT_ROUTE]: directRouteText,
1414
};
1515
}
16-
// MYSQL/PostgreSQL/Sqlite/Clickhouse
16+
// MYSQL/PostgreSQL/Clickhouse
1717
export const DB_TYPE_OPTIONS = [
1818
{ label: 'MySQL', value: 'MYSQL' },
19-
{ label: 'PostgreSQL', value: 'PostgreSQL' },
20-
{ label: 'SQLite', value: 'Sqlite' },
21-
{ label: 'ClickHouse', value: 'Clickhouse' },
19+
{ label: 'PostgreSQL', value: 'POSTGRESQL' },
20+
{ label: 'ClickHouse', value: 'CLICKHOUSE' },
2221
];
2322

2423
export const DOMAIN_PROTOCOL_MAP = {
@@ -28,11 +27,12 @@ export const DOMAIN_PROTOCOL_MAP = {
2827

2928
// 数据库连接字符串正则 eg: mysql:user:pass@tcp(host:port)/database?charset=utf8mb4&parseTime=True&loc=Local
3029
export const REG_DSN_STRING = {
30+
// username:password@tcp(host:port)/dbname?param1=value1&param2=value2
3131
MYSQL: /^(\w+):([^@]+)@tcp\(([^:]+):(\d+)\)\/([^?]+)\?(.+)$/,
32-
POSTGRESQL: /^(\w+):([^@]+)@tcp\(([^:]+):(\d+)\)\/([^?]+)\?(.+)$/,
33-
SQLITE: /^(\w+):([^@]+)@tcp\(([^:]+):(\d+)\)\/([^?]+)\?(.+)$/,
34-
CLICKHOUSE: /^(\w+):([^@]+)@tcp\(([^:]+):(\d+)\)\/([^?]+)\?(.+)$/,
35-
DEFAULT: /^(\w+):([^@]+)@tcp\(([^:]+):(\d+)\)\/([^?]+)\?(.+)$/,
32+
// postgres://username:password@host:port/dbname
33+
POSTGRESQL: /^postgres:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/([^?]+)$/,
34+
// tcp://localhost:9000?database=default&username=default&password=
35+
CLICKHOUSE: /^tcp:\/\/([^:]+):(\d+)\?database=([^&]+)&username=([^&]*)&password=([^&]*)/,
3636
};
3737

3838

0 commit comments

Comments
 (0)