feat(.kilo): 新增 AgileDB 数据库操作技能
新增 lzwcai-agile-db 技能文件,为 AI Agent 提供 AgileDB 数据库操作的场景化工作流指导。 该技能支持数据源浏览、表数据 CRUD、SQL 执行和 AI 生成表结构等功能,包含完整的工具列表和使用场景。
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
|
||||
| 变量名 | 必填 | 说明 |
|
||||
|--------|------|------|
|
||||
| `AGILE_DB_API_KEY` | 是 | 数据库管理平台的 API 密钥 |
|
||||
| `AGILE_DB_BASE_URL` | 否 | 数据库管理平台后端地址(默认 `http://localhost:8080`) |
|
||||
| `API_KEY` | 是 | 数据库管理平台的 API 密钥(格式: `Bearer <token>`) |
|
||||
| `backendBaseUrl` | 否 | 数据库管理平台后端地址(默认 `http://lzwcai-demp-corp-manager:8086`) |
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -19,8 +19,8 @@ pip install -e .
|
||||
|
||||
```bash
|
||||
# 设置环境变量
|
||||
export AGILE_DB_API_KEY="your-api-key"
|
||||
export AGILE_DB_BASE_URL="http://localhost:8080" # 可选
|
||||
export API_KEY="Bearer your-token"
|
||||
export backendBaseUrl="https://dempdemo.lzwcai.com" # 可选
|
||||
|
||||
# 运行 MCP Server
|
||||
lzwcai-mcp-agile-db
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
def main():
|
||||
print("Hello from lzwcai-mcp-agile-db!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,7 +0,0 @@
|
||||
[project]
|
||||
name = "lzwcai-mcp-agile-db"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
@@ -26,7 +26,7 @@ def register_tool(name: str):
|
||||
input_schema = {...}
|
||||
|
||||
async def execute(self, args):
|
||||
return await self.client.get("/api/...", params=args)
|
||||
return await self.client.get("/datasource/...", params=args)
|
||||
|
||||
Args:
|
||||
name: 工具名称(唯一标识)
|
||||
|
||||
@@ -22,7 +22,7 @@ class ListApiKeysTool(ToolDef):
|
||||
async def execute(self, args: dict) -> dict:
|
||||
args = dict(args)
|
||||
params = {k: v for k, v in args.items() if v is not None}
|
||||
return self.client.get("/api/datasource/api_key/list", params=params)
|
||||
return await self.client.get("/datasource/api_key/list", params=params)
|
||||
|
||||
|
||||
@register_tool("create_api_key")
|
||||
@@ -38,7 +38,7 @@ class CreateApiKeyTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.post("/api/datasource/api_key", json_data=args)
|
||||
return await self.client.post("/datasource/api_key", json_data=args)
|
||||
|
||||
|
||||
@register_tool("toggle_api_key_status")
|
||||
@@ -55,7 +55,7 @@ class ToggleApiKeyStatusTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.put("/api/datasource/api_key", json_data=args)
|
||||
return await self.client.put("/datasource/api_key", json_data=args)
|
||||
|
||||
|
||||
@register_tool("delete_api_key")
|
||||
@@ -71,7 +71,7 @@ class DeleteApiKeyTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.delete(f"/api/datasource/api_key/{args['id']}")
|
||||
return await self.client.delete(f"/datasource/api_key/{args['id']}")
|
||||
|
||||
|
||||
@register_tool("get_api_key_permissions")
|
||||
@@ -87,7 +87,7 @@ class GetApiKeyPermissionsTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.get(f"/api/datasource/api_key/permission/{args['apiKeyId']}")
|
||||
return await self.client.get(f"/datasource/api_key/permission/{args['apiKeyId']}")
|
||||
|
||||
|
||||
@register_tool("grant_api_key_permissions")
|
||||
@@ -118,4 +118,4 @@ class GrantApiKeyPermissionsTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.post("/api/datasource/api_key/permission/grant_batch", json_data=args)
|
||||
return await self.client.post("/datasource/api_key/permission/grant_batch", json_data=args)
|
||||
|
||||
@@ -38,8 +38,8 @@ class PreviewImportDataTool(ToolDef):
|
||||
"file": (file_name, io.BytesIO(file_content), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
|
||||
}
|
||||
|
||||
return self.client.upload(
|
||||
f"/api/datasource/connection/{connection_id}/import_document/preview",
|
||||
return await self.client.upload(
|
||||
f"/datasource/connection/{connection_id}/import_document/preview",
|
||||
files=files,
|
||||
params={"target": target},
|
||||
)
|
||||
@@ -65,8 +65,8 @@ class ConfirmImportDataTool(ToolDef):
|
||||
target = args.pop("target", "test")
|
||||
data = args.pop("data")
|
||||
|
||||
return self.client.post(
|
||||
f"/api/datasource/connection/{connection_id}/import_document/confirm",
|
||||
return await self.client.post(
|
||||
f"/datasource/connection/{connection_id}/import_document/confirm",
|
||||
json_data=data,
|
||||
params={"target": target},
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ class ListDatabasesTool(ToolDef):
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
params = {k: v for k, v in args.items() if v is not None}
|
||||
return self.client.get("/api/datasource/config/list", params=params)
|
||||
return await self.client.get("/datasource/config/list", params=params)
|
||||
|
||||
|
||||
@register_tool("list_tables")
|
||||
@@ -42,7 +42,7 @@ class ListTablesTool(ToolDef):
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
params = {k: v for k, v in args.items() if v is not None}
|
||||
return self.client.get("/api/datasource/table/list", params=params)
|
||||
return await self.client.get("/datasource/table/list", params=params)
|
||||
|
||||
|
||||
@register_tool("get_table_detail")
|
||||
@@ -59,7 +59,7 @@ class GetTableDetailTool(ToolDef):
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
table_id = args["tableId"]
|
||||
return self.client.get(f"/api/datasource/table/{table_id}/detail")
|
||||
return await self.client.get(f"/datasource/table/{table_id}/detail")
|
||||
|
||||
|
||||
@register_tool("create_table")
|
||||
@@ -98,7 +98,7 @@ class CreateTableTool(ToolDef):
|
||||
async def execute(self, args: dict) -> dict:
|
||||
args = dict(args)
|
||||
connection_id = args.pop("connectionId")
|
||||
return self.client.post(f"/api/datasource/connection/{connection_id}/create_table", json_data=args)
|
||||
return await self.client.post(f"/datasource/connection/{connection_id}/create_table", json_data=args)
|
||||
|
||||
|
||||
@register_tool("alter_table")
|
||||
@@ -131,7 +131,7 @@ class AlterTableTool(ToolDef):
|
||||
async def execute(self, args: dict) -> dict:
|
||||
args = dict(args)
|
||||
connection_id = args.pop("connectionId")
|
||||
return self.client.put(f"/api/datasource/connection/{connection_id}/alter_table", json_data=args)
|
||||
return await self.client.put(f"/datasource/connection/{connection_id}/alter_table", json_data=args)
|
||||
|
||||
|
||||
@register_tool("generate_table_by_description")
|
||||
@@ -148,4 +148,4 @@ class GenerateTableByDescriptionTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.post("/api/datasource/connection/generate_table", json_data=args)
|
||||
return await self.client.post("/datasource/connection/generate_table", json_data=args)
|
||||
|
||||
@@ -27,7 +27,7 @@ class ListDatasourcesTool(ToolDef):
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
params = {k: v for k, v in args.items() if v is not None}
|
||||
return self.client.get("/api/datasource/connection/list", params=params)
|
||||
return await self.client.get("/datasource/connection/list", params=params)
|
||||
|
||||
|
||||
@register_tool("get_datasource_detail")
|
||||
@@ -47,17 +47,17 @@ class GetDatasourceDetailTool(ToolDef):
|
||||
result = {}
|
||||
# 获取基本信息
|
||||
try:
|
||||
result["detail"] = self.client.get(f"/api/datasource/connection/{ds_id}")
|
||||
result["detail"] = await self.client.get(f"/datasource/connection/{ds_id}")
|
||||
except Exception as e:
|
||||
result["detail"] = {"error": str(e)}
|
||||
# 获取配置
|
||||
try:
|
||||
result["config"] = self.client.get(f"/api/datasource/config/{ds_id}")
|
||||
result["config"] = await self.client.get(f"/datasource/config/{ds_id}")
|
||||
except Exception as e:
|
||||
result["config"] = {"error": str(e)}
|
||||
# 获取实时结构
|
||||
try:
|
||||
result["structure"] = self.client.get(f"/api/datasource/connection/realtime/structure/{ds_id}")
|
||||
result["structure"] = await self.client.get(f"/datasource/connection/realtime/structure/{ds_id}")
|
||||
except Exception as e:
|
||||
result["structure"] = {"error": str(e)}
|
||||
return result
|
||||
@@ -100,12 +100,12 @@ class CreateDatasourceTool(ToolDef):
|
||||
"password": args.get("password"),
|
||||
"connectionType": args.get("connectionType", "user_password"),
|
||||
}
|
||||
test_result = self.client.post("/api/datasource/connection/test", json_data=test_data)
|
||||
test_result = await self.client.post("/datasource/connection/test", json_data=test_data)
|
||||
if test_result.get("code") != 200:
|
||||
return {"success": False, "error": f"连接测试失败: {test_result.get('msg', '未知错误')}"}
|
||||
|
||||
# 创建数据源
|
||||
return self.client.post("/api/datasource/connection", json_data=args)
|
||||
return await self.client.post("/datasource/connection", json_data=args)
|
||||
|
||||
|
||||
@register_tool("update_datasource")
|
||||
@@ -128,7 +128,7 @@ class UpdateDatasourceTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.put("/api/datasource/connection", json_data=args)
|
||||
return await self.client.put("/datasource/connection", json_data=args)
|
||||
|
||||
|
||||
@register_tool("toggle_datasource_status")
|
||||
@@ -145,7 +145,7 @@ class ToggleDatasourceStatusTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.put("/api/datasource/connection/changeStatus", json_data=args)
|
||||
return await self.client.put("/datasource/connection/changeStatus", json_data=args)
|
||||
|
||||
|
||||
@register_tool("delete_datasource")
|
||||
@@ -164,10 +164,10 @@ class DeleteDatasourceTool(ToolDef):
|
||||
ds_id = args["id"]
|
||||
# 先尝试停用(仅忽略已停用等预期错误)
|
||||
try:
|
||||
self.client.put("/api/datasource/connection/changeStatus", json_data={"id": ds_id, "status": 1})
|
||||
await self.client.put("/datasource/connection/changeStatus", json_data={"id": ds_id, "status": 1})
|
||||
except Exception as e:
|
||||
# 记录日志但继续删除
|
||||
logger.debug(f"停用数据源失败(可能已停用): {e}")
|
||||
|
||||
# 删除数据源
|
||||
return self.client.delete(f"/api/datasource/connection/{ds_id}")
|
||||
return await self.client.delete(f"/datasource/connection/{ds_id}")
|
||||
|
||||
@@ -20,7 +20,7 @@ class GetSkillByDatasourceTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.get(f"/api/datasource/skill/getByDatasource/{args['datasourceId']}")
|
||||
return await self.client.get(f"/datasource/skill/getByDatasource/{args['datasourceId']}")
|
||||
|
||||
|
||||
@register_tool("get_skill_tools")
|
||||
@@ -36,7 +36,7 @@ class GetSkillToolsTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.get(f"/api/datasource/skill/getBySkillId/{args['skillId']}")
|
||||
return await self.client.get(f"/datasource/skill/getBySkillId/{args['skillId']}")
|
||||
|
||||
|
||||
@register_tool("create_skill")
|
||||
@@ -54,7 +54,7 @@ class CreateSkillTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.post("/api/datasource/skill/createOrGet", json_data=args)
|
||||
return await self.client.post("/datasource/skill/createOrGet", json_data=args)
|
||||
|
||||
|
||||
@register_tool("create_sql_tool")
|
||||
@@ -97,7 +97,7 @@ class CreateSqlToolTool(ToolDef):
|
||||
for suggestion in args["suggestions"]:
|
||||
if "sqlParams" in suggestion and isinstance(suggestion["sqlParams"], dict):
|
||||
suggestion["sqlParams"] = json.dumps(suggestion["sqlParams"])
|
||||
return self.client.post("/api/datasource/skill/confirmTools", json_data=args)
|
||||
return await self.client.post("/datasource/skill/confirmTools", json_data=args)
|
||||
|
||||
|
||||
@register_tool("delete_skill_tool")
|
||||
@@ -113,7 +113,7 @@ class DeleteSkillToolTool(ToolDef):
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.delete(f"/api/datasource/skill/tskilltool/{args['skillToolId']}")
|
||||
return await self.client.delete(f"/datasource/skill/tskilltool/{args['skillToolId']}")
|
||||
|
||||
|
||||
@register_tool("update_skill_config")
|
||||
@@ -134,4 +134,4 @@ class UpdateSkillConfigTool(ToolDef):
|
||||
# 如果 configTemplate 是 dict,转为 JSON 字符串
|
||||
if "configTemplate" in args and isinstance(args["configTemplate"], dict):
|
||||
args["configTemplate"] = json.dumps(args["configTemplate"])
|
||||
return self.client.post("/api/datasource/skill/updateOrGet", json_data=args)
|
||||
return await self.client.post("/datasource/skill/updateOrGet", json_data=args)
|
||||
|
||||
@@ -13,13 +13,26 @@ class ExecuteSqlTool(ToolDef):
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"datasourceId": {"type": "string", "description": "数据源 ID"},
|
||||
"executableSql": {"type": "string", "description": "SQL 语句"},
|
||||
"sql": {"type": "string", "description": "SQL 语句"},
|
||||
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
|
||||
"sqlTemplate": {"type": "string", "description": "SQL 模板(可选)"},
|
||||
"businessName": {"type": "string", "description": "业务名称(可选)"},
|
||||
"parameters": {"type": "object", "description": "参数定义(可选)"},
|
||||
"params": {"type": "object", "description": "SQL 参数对象(可选)"},
|
||||
},
|
||||
"required": ["datasourceId", "executableSql"],
|
||||
"required": ["datasourceId", "sql"],
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.post("/api/datasource/sqlExecutionLog/testSqlWithSchema", json_data=args)
|
||||
# 映射参数名为后端 API 期望的格式
|
||||
body = {}
|
||||
if "datasourceId" in args:
|
||||
body["datasourceId"] = args["datasourceId"]
|
||||
if "sql" in args:
|
||||
body["executableSql"] = args["sql"]
|
||||
if "sqlTemplate" in args:
|
||||
body["sqlTemplate"] = args["sqlTemplate"]
|
||||
if "businessName" in args:
|
||||
body["businessName"] = args["businessName"]
|
||||
if "params" in args:
|
||||
body["parameters"] = args["params"]
|
||||
return await self.client.post("/datasource/sqlExecutionLog/testSqlWithSchema", json_data=body)
|
||||
|
||||
@@ -12,12 +12,18 @@ class ToggleTableSubscriptionTool(ToolDef):
|
||||
input_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"configId": {"type": "string", "description": "数据库配置 ID"},
|
||||
"tableName": {"type": "string", "description": "表名"},
|
||||
"isSubscribe": {"type": "boolean", "description": "true=订阅, false=取消订阅"},
|
||||
"tableId": {"type": "string", "description": "表 ID"},
|
||||
"datasourceId": {"type": "string", "description": "数据源 ID"},
|
||||
"subscribe": {"type": "boolean", "description": "true=订阅, false=取消订阅"},
|
||||
},
|
||||
"required": ["configId", "tableName", "isSubscribe"],
|
||||
"required": ["tableId", "datasourceId", "subscribe"],
|
||||
}
|
||||
|
||||
async def execute(self, args: dict) -> dict:
|
||||
return self.client.post("/api/datasource/subscription/toggle", json_data=args)
|
||||
# 映射参数名为后端 API 期望的格式
|
||||
body = {
|
||||
"tableId": args["tableId"],
|
||||
"datasourceId": args["datasourceId"],
|
||||
"subscribe": args["subscribe"],
|
||||
}
|
||||
return await self.client.post("/datasource/subscription/toggle", json_data=body)
|
||||
|
||||
@@ -26,7 +26,7 @@ class QueryTableDataTool(ToolDef):
|
||||
args = dict(args)
|
||||
table_id = args.pop("tableId")
|
||||
params = {k: v for k, v in args.items() if v is not None}
|
||||
return self.client.get(f"/api/datasource/connection/builtin/table/{table_id}", params=params)
|
||||
return await self.client.get(f"/datasource/connection/builtin/table/{table_id}", params=params)
|
||||
|
||||
|
||||
@register_tool("insert_table_row")
|
||||
@@ -49,7 +49,7 @@ class InsertTableRowTool(ToolDef):
|
||||
target = args.pop("target", "prod")
|
||||
data = args.pop("data", {})
|
||||
params = {"target": target} if target else {}
|
||||
return self.client.post(f"/api/datasource/connection/builtin/table/{table_id}/rows", json_data=data, params=params)
|
||||
return await self.client.post(f"/datasource/connection/builtin/table/{table_id}/rows", json_data=data, params=params)
|
||||
|
||||
|
||||
@register_tool("update_table_row")
|
||||
@@ -75,7 +75,7 @@ class UpdateTableRowTool(ToolDef):
|
||||
data = args.pop("data", {})
|
||||
params = {"target": target} if target else {}
|
||||
body = {"primaryKey": primary_key, "data": data}
|
||||
return self.client.put(f"/api/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params)
|
||||
return await self.client.put(f"/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params)
|
||||
|
||||
|
||||
@register_tool("delete_table_rows")
|
||||
@@ -103,7 +103,7 @@ class DeleteTableRowsTool(ToolDef):
|
||||
primary_keys = args.pop("primaryKeys")
|
||||
params = {"target": target} if target else {}
|
||||
body = {"primaryKeys": primary_keys}
|
||||
return self.client.delete(f"/api/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params)
|
||||
return await self.client.delete(f"/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params)
|
||||
|
||||
|
||||
@register_tool("export_table_excel")
|
||||
@@ -124,7 +124,7 @@ class ExportTableExcelTool(ToolDef):
|
||||
table_id = args.pop("tableId")
|
||||
target = args.pop("target", "prod")
|
||||
params = {"target": target} if target else {}
|
||||
result = self.client.get(f"/api/datasource/connection/builtin/table/{table_id}/export/excel", params=params)
|
||||
result = await self.client.get(f"/datasource/connection/builtin/table/{table_id}/export/excel", params=params)
|
||||
|
||||
# 处理二进制响应
|
||||
if result.get("raw"):
|
||||
|
||||
@@ -30,8 +30,8 @@ class AgileDBAPIClient:
|
||||
初始化 API 客户端
|
||||
|
||||
Args:
|
||||
base_url: API 基础 URL(默认从环境变量 AGILE_DB_BASE_URL 读取)
|
||||
api_key: API 密钥(默认从环境变量 AGILE_DB_API_KEY 读取)
|
||||
base_url: API 基础 URL(默认从环境变量 backendBaseUrl 读取)
|
||||
api_key: API 密钥(默认从环境变量 API_KEY 读取)
|
||||
default_timeout: 请求超时时间(秒),默认 30 秒
|
||||
"""
|
||||
if base_url is None:
|
||||
@@ -43,15 +43,15 @@ class AgileDBAPIClient:
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.api_key = api_key
|
||||
self.default_timeout = default_timeout
|
||||
self._client: Optional[httpx.Client] = None
|
||||
self._client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
logger.info(f"[客户端初始化] base_url={self.base_url}")
|
||||
|
||||
@property
|
||||
def client(self) -> httpx.Client:
|
||||
def client(self) -> httpx.AsyncClient:
|
||||
"""懒加载 HTTP 客户端"""
|
||||
if self._client is None:
|
||||
self._client = httpx.Client(timeout=self.default_timeout)
|
||||
self._client = httpx.AsyncClient(timeout=self.default_timeout)
|
||||
return self._client
|
||||
|
||||
def _get_headers(self, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
||||
@@ -64,10 +64,14 @@ class AgileDBAPIClient:
|
||||
return headers
|
||||
|
||||
def _build_url(self, path: str) -> str:
|
||||
"""构建完整 URL"""
|
||||
"""构建完整 URL,自动去掉路径中多余的 /api 前缀"""
|
||||
if path.startswith('http://') or path.startswith('https://'):
|
||||
return path
|
||||
return f"{self.base_url}{path}"
|
||||
# 去掉 /api 前缀,因为 base_url 已经包含完整地址
|
||||
clean_path = path
|
||||
if clean_path.startswith('/api'):
|
||||
clean_path = clean_path[4:]
|
||||
return f"{self.base_url}{clean_path}"
|
||||
|
||||
def _handle_response(self, response: httpx.Response, url: str) -> Dict[str, Any]:
|
||||
"""统一处理 API 响应"""
|
||||
@@ -93,12 +97,12 @@ class AgileDBAPIClient:
|
||||
|
||||
return data
|
||||
|
||||
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
async def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""发送 GET 请求"""
|
||||
url = self._build_url(path)
|
||||
try:
|
||||
logger.info(f"[API请求] GET {url}")
|
||||
response = self.client.get(url, headers=self._get_headers(), params=params)
|
||||
response = await self.client.get(url, headers=self._get_headers(), params=params)
|
||||
return self._handle_response(response, url)
|
||||
except httpx.TimeoutException:
|
||||
raise Exception(f"API 请求超时: {url}")
|
||||
@@ -107,13 +111,13 @@ class AgileDBAPIClient:
|
||||
except httpx.RequestError as e:
|
||||
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||
|
||||
def post(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
async def post(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""发送 POST 请求"""
|
||||
url = self._build_url(path)
|
||||
try:
|
||||
logger.info(f"[API请求] POST {url}")
|
||||
headers = self._get_headers({'Content-Type': 'application/json'})
|
||||
response = self.client.post(url, headers=headers, json=json_data, params=params)
|
||||
response = await self.client.post(url, headers=headers, json=json_data, params=params)
|
||||
return self._handle_response(response, url)
|
||||
except httpx.TimeoutException:
|
||||
raise Exception(f"API 请求超时: {url}")
|
||||
@@ -122,13 +126,13 @@ class AgileDBAPIClient:
|
||||
except httpx.RequestError as e:
|
||||
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||
|
||||
def put(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
async def put(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""发送 PUT 请求"""
|
||||
url = self._build_url(path)
|
||||
try:
|
||||
logger.info(f"[API请求] PUT {url}")
|
||||
headers = self._get_headers({'Content-Type': 'application/json'})
|
||||
response = self.client.put(url, headers=headers, json=json_data, params=params)
|
||||
response = await self.client.put(url, headers=headers, json=json_data, params=params)
|
||||
return self._handle_response(response, url)
|
||||
except httpx.TimeoutException:
|
||||
raise Exception(f"API 请求超时: {url}")
|
||||
@@ -137,7 +141,7 @@ class AgileDBAPIClient:
|
||||
except httpx.RequestError as e:
|
||||
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||
|
||||
def delete(self, path: str, params: Optional[Dict[str, Any]] = None, json_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
async def delete(self, path: str, params: Optional[Dict[str, Any]] = None, json_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""发送 DELETE 请求"""
|
||||
url = self._build_url(path)
|
||||
try:
|
||||
@@ -145,7 +149,7 @@ class AgileDBAPIClient:
|
||||
headers = self._get_headers()
|
||||
if json_data is not None:
|
||||
headers['Content-Type'] = 'application/json'
|
||||
response = self.client.delete(url, headers=headers, params=params, json=json_data)
|
||||
response = await self.client.delete(url, headers=headers, params=params, json=json_data)
|
||||
return self._handle_response(response, url)
|
||||
except httpx.TimeoutException:
|
||||
raise Exception(f"API 请求超时: {url}")
|
||||
@@ -154,14 +158,14 @@ class AgileDBAPIClient:
|
||||
except httpx.RequestError as e:
|
||||
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||
|
||||
def upload(self, path: str, files: Dict[str, Any], params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
async def upload(self, path: str, files: Dict[str, Any], params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""发送文件上传请求(multipart/form-data)"""
|
||||
url = self._build_url(path)
|
||||
try:
|
||||
logger.info(f"[API请求] UPLOAD {url}")
|
||||
# 文件上传不需要 Content-Type,httpx 会自动设置 multipart/form-data
|
||||
headers = self._get_headers()
|
||||
response = self.client.post(url, headers=headers, files=files, params=params)
|
||||
response = await self.client.post(url, headers=headers, files=files, params=params)
|
||||
return self._handle_response(response, url)
|
||||
except httpx.TimeoutException:
|
||||
raise Exception(f"API 请求超时: {url}")
|
||||
@@ -170,17 +174,17 @@ class AgileDBAPIClient:
|
||||
except httpx.RequestError as e:
|
||||
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||
|
||||
def close(self):
|
||||
async def close(self):
|
||||
"""关闭 HTTP 客户端"""
|
||||
if self._client is not None:
|
||||
self._client.close()
|
||||
await self._client.aclose()
|
||||
self._client = None
|
||||
|
||||
def __enter__(self):
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
await self.close()
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Optional
|
||||
|
||||
def get_api_key(default: Optional[str] = None) -> str:
|
||||
"""
|
||||
获取数据库管理平台 API 密钥
|
||||
获取 API 密钥
|
||||
|
||||
Args:
|
||||
default: 默认值(可选)
|
||||
@@ -15,25 +15,25 @@ def get_api_key(default: Optional[str] = None) -> str:
|
||||
str: API 密钥
|
||||
|
||||
Raises:
|
||||
ValueError: 当 AGILE_DB_API_KEY 未设置且无默认值时
|
||||
ValueError: 当 API_KEY 未设置且无默认值时
|
||||
"""
|
||||
value = os.environ.get("AGILE_DB_API_KEY", default or "")
|
||||
value = os.environ.get("API_KEY", default or "")
|
||||
if not value:
|
||||
raise ValueError("环境变量 AGILE_DB_API_KEY 未设置")
|
||||
raise ValueError("环境变量 API_KEY 未设置")
|
||||
return value
|
||||
|
||||
|
||||
def get_base_url(default: str = "http://localhost:8080") -> str:
|
||||
def get_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str:
|
||||
"""
|
||||
获取数据库管理平台后端地址
|
||||
获取后端服务地址
|
||||
|
||||
Args:
|
||||
default: 默认值(默认 http://localhost:8080)
|
||||
default: 默认值(默认 http://lzwcai-demp-corp-manager:8086)
|
||||
|
||||
Returns:
|
||||
str: 后端 API 基础 URL
|
||||
"""
|
||||
return os.environ.get("AGILE_DB_BASE_URL", default)
|
||||
return os.environ.get("backendBaseUrl", default)
|
||||
|
||||
|
||||
def get_env_config() -> dict:
|
||||
|
||||
@@ -7,6 +7,6 @@ from lzwcai_mcp_agile_db.server import main
|
||||
import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ["AGILE_DB_API_KEY"] = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjJiMDY0YzMzLTBiZWYtNDU0NC04NWY1LTRmNTFiOGMxMmI5NSJ9.Uv599TvlQvlTlwrnZGo3Tl2eLAvM0ldE9vpMI5jHxbTf4_tVSRA60rUNIV4LBiw6pt1r_xIi7aFcTRE2PeN5sg"
|
||||
os.environ["AGILE_DB_BASE_URL"] = "https://dempdemo.lzwcai.com"
|
||||
os.environ["API_KEY"] = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjJiMDY0YzMzLTBiZWYtNDU0NC04NWY1LTRmNTFiOGMxMmI5NSJ9.Uv599TvlQvlTlwrnZGo3Tl2eLAvM0ldE9vpMI5jHxbTf4_tVSRA60rUNIV4LBiw6pt1r_xIi7aFcTRE2PeN5sg"
|
||||
os.environ["backendBaseUrl"] = "https://dempdemo.lzwcai.com"
|
||||
main()
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "lzwcai-mcp-agile-db"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
description = "MCP server for database management platform with 33 tools for datasource, table, data, API key, and skill management"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
Reference in New Issue
Block a user