archery-sql-mcp 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {archery_sql_mcp-0.2.2 → archery_sql_mcp-0.3.0}/PKG-INFO +19 -3
- {archery_sql_mcp-0.2.2 → archery_sql_mcp-0.3.0}/README.md +18 -2
- {archery_sql_mcp-0.2.2 → archery_sql_mcp-0.3.0}/pyproject.toml +1 -1
- {archery_sql_mcp-0.2.2 → archery_sql_mcp-0.3.0}/src/archery_sql_mcp/__init__.py +164 -2
- {archery_sql_mcp-0.2.2 → archery_sql_mcp-0.3.0}/.gitignore +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: archery-sql-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Archery SQL 查询 MCP 工具:自动登录 + 会话自愈 + 只读查询,供任意 MCP 客户端与 CLI 使用
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -62,9 +62,25 @@ archery-sql-mcp query "SELECT count(*) FROM open_api_request_logs WHERE created_
|
|
|
62
62
|
|
|
63
63
|
仅允许 `SELECT` / `WITH` 开头的语句(可在配置 `allowed_prefixes` 扩展)。
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## 一键注册到各 agent 客户端
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
```bash
|
|
68
|
+
# 注册到全部支持的 agent 的用户级配置(zcode / claude-code / claude-desktop / codex / cursor)
|
|
69
|
+
uvx archery-sql-mcp install-mcp
|
|
70
|
+
|
|
71
|
+
# 仅注册指定 agent,写入当前目录的项目级配置(进 git 共享,command 自动用可移植的 uvx)
|
|
72
|
+
uvx archery-sql-mcp install-mcp --agents claude-code,codex --scope project
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
写入规则:合并式更新(保留配置文件中的既有内容)、幂等(重复执行不产生重复条目)、
|
|
76
|
+
用户级默认写入本机 uvx 绝对路径、项目级默认写入可移植的命令名 `uvx`(加 `--portable` 强制全局可移植)。
|
|
77
|
+
各 agent 写入位置:ZCode `~/.zcode/cli/config.json`(mcp.servers)· Claude Code `~/.claude.json` / 项目 `.mcp.json` ·
|
|
78
|
+
Claude Desktop `%APPDATA%\Claude\claude_desktop_config.json` · Codex `~/.codex/config.toml`(mcp_servers 段)·
|
|
79
|
+
Cursor `~/.cursor/mcp.json` / 项目 `.cursor/mcp.json`。写入后重启对应客户端会话生效。
|
|
80
|
+
|
|
81
|
+
## 接入 MCP 客户端(手工方式)
|
|
82
|
+
|
|
83
|
+
ZCode(`~/.zcode/cli/config.json`)、Claude Desktop、Cursor(`~/.cursor/mcp.json`)同构:
|
|
68
84
|
|
|
69
85
|
```jsonc
|
|
70
86
|
{
|
|
@@ -52,9 +52,25 @@ archery-sql-mcp query "SELECT count(*) FROM open_api_request_logs WHERE created_
|
|
|
52
52
|
|
|
53
53
|
仅允许 `SELECT` / `WITH` 开头的语句(可在配置 `allowed_prefixes` 扩展)。
|
|
54
54
|
|
|
55
|
-
##
|
|
55
|
+
## 一键注册到各 agent 客户端
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
```bash
|
|
58
|
+
# 注册到全部支持的 agent 的用户级配置(zcode / claude-code / claude-desktop / codex / cursor)
|
|
59
|
+
uvx archery-sql-mcp install-mcp
|
|
60
|
+
|
|
61
|
+
# 仅注册指定 agent,写入当前目录的项目级配置(进 git 共享,command 自动用可移植的 uvx)
|
|
62
|
+
uvx archery-sql-mcp install-mcp --agents claude-code,codex --scope project
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
写入规则:合并式更新(保留配置文件中的既有内容)、幂等(重复执行不产生重复条目)、
|
|
66
|
+
用户级默认写入本机 uvx 绝对路径、项目级默认写入可移植的命令名 `uvx`(加 `--portable` 强制全局可移植)。
|
|
67
|
+
各 agent 写入位置:ZCode `~/.zcode/cli/config.json`(mcp.servers)· Claude Code `~/.claude.json` / 项目 `.mcp.json` ·
|
|
68
|
+
Claude Desktop `%APPDATA%\Claude\claude_desktop_config.json` · Codex `~/.codex/config.toml`(mcp_servers 段)·
|
|
69
|
+
Cursor `~/.cursor/mcp.json` / 项目 `.cursor/mcp.json`。写入后重启对应客户端会话生效。
|
|
70
|
+
|
|
71
|
+
## 接入 MCP 客户端(手工方式)
|
|
72
|
+
|
|
73
|
+
ZCode(`~/.zcode/cli/config.json`)、Claude Desktop、Cursor(`~/.cursor/mcp.json`)同构:
|
|
58
74
|
|
|
59
75
|
```jsonc
|
|
60
76
|
{
|
|
@@ -21,6 +21,7 @@ import getpass
|
|
|
21
21
|
import json
|
|
22
22
|
import os
|
|
23
23
|
import re
|
|
24
|
+
import shutil
|
|
24
25
|
import sys
|
|
25
26
|
import time
|
|
26
27
|
from pathlib import Path
|
|
@@ -28,7 +29,7 @@ from typing import Any, Optional
|
|
|
28
29
|
|
|
29
30
|
import httpx
|
|
30
31
|
|
|
31
|
-
VERSION = "0.
|
|
32
|
+
VERSION = "0.3.0"
|
|
32
33
|
|
|
33
34
|
# ── 配置文件与会话缓存位置(可用 ARCHERY_CONFIG_DIR 整体改根目录) ──────────
|
|
34
35
|
_CONFIG_DIR = Path(os.environ.get("ARCHERY_CONFIG_DIR", str(Path.home() / ".archery-mcp")))
|
|
@@ -521,6 +522,150 @@ def cmd_instances(_args: argparse.Namespace) -> int:
|
|
|
521
522
|
return 1
|
|
522
523
|
|
|
523
524
|
|
|
525
|
+
# ── 一键注册 MCP 到各 agent 客户端 ────────────────────────────────────
|
|
526
|
+
|
|
527
|
+
_AGENT_LABELS = {
|
|
528
|
+
"zcode": "ZCode",
|
|
529
|
+
"claude-code": "Claude Code",
|
|
530
|
+
"claude-desktop": "Claude Desktop",
|
|
531
|
+
"codex": "Codex CLI",
|
|
532
|
+
"cursor": "Cursor",
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def _json_upsert(path: Path, mutate) -> None:
|
|
537
|
+
"""读改写 JSON 配置(不存在则从 {} 开始),保留原有其它键。"""
|
|
538
|
+
data: dict[str, Any] = {}
|
|
539
|
+
if path.exists():
|
|
540
|
+
try:
|
|
541
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
542
|
+
except json.JSONDecodeError as e:
|
|
543
|
+
raise ArcheryError(f"{path} JSON 解析失败,请手工修复后重试: {e}")
|
|
544
|
+
mutate(data)
|
|
545
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
546
|
+
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _toml_upsert(path: Path, section: str, body_lines: list[str]) -> None:
|
|
550
|
+
"""向 TOML 插入/替换一个段(零依赖文本处理)。body 行需自带 `key = value`。"""
|
|
551
|
+
text = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
552
|
+
header = f"[{section}]"
|
|
553
|
+
body = "\n".join(body_lines)
|
|
554
|
+
pattern = re.compile(
|
|
555
|
+
r"^\[" + re.escape(section) + r"\][^\n]*\n(?:(?!\n?\[)[^\n]*\n?)*", re.M
|
|
556
|
+
)
|
|
557
|
+
if pattern.search(text):
|
|
558
|
+
# 用 lambda 替换,避免 Windows 路径中的 \U 等被 re 当作模板转义
|
|
559
|
+
text = pattern.sub(lambda _m: header + "\n" + body + "\n", text)
|
|
560
|
+
else:
|
|
561
|
+
text = text.rstrip("\n") + ("\n\n" if text.strip() else "") + header + "\n" + body + "\n"
|
|
562
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
563
|
+
path.write_text(text, encoding="utf-8")
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _entry_json(command: str, timeout_ms: bool) -> dict[str, Any]:
|
|
567
|
+
e = {"type": "stdio", "command": command, "args": ["archery-sql-mcp"]}
|
|
568
|
+
if timeout_ms: # 仅 zcode 使用该字段
|
|
569
|
+
e["timeoutMs"] = 120000
|
|
570
|
+
return e
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def _toml_escape(s: str) -> str:
|
|
574
|
+
"""TOML 单引号字面量字符串(Windows 路径无需转义反斜杠)。"""
|
|
575
|
+
return "'" + s.replace("'", "''") + "'"
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def install_mcp(
|
|
579
|
+
agents: list[str],
|
|
580
|
+
scope: str,
|
|
581
|
+
portable: Optional[bool],
|
|
582
|
+
home: Optional[Path] = None,
|
|
583
|
+
cwd: Optional[Path] = None,
|
|
584
|
+
) -> list[str]:
|
|
585
|
+
"""把 archery MCP server 注册进各 agent 配置。返回结果描述行列表。"""
|
|
586
|
+
home = home or Path.home()
|
|
587
|
+
cwd = cwd or Path.cwd()
|
|
588
|
+
uvx = shutil.which("uvx")
|
|
589
|
+
results: list[str] = []
|
|
590
|
+
unknown = [a for a in agents if a not in _AGENT_LABELS]
|
|
591
|
+
if unknown:
|
|
592
|
+
raise ArcheryError(f"未知 agent: {unknown},可用: {list(_AGENT_LABELS)}")
|
|
593
|
+
|
|
594
|
+
for agent in agents:
|
|
595
|
+
# 项目级默认 portable(通常进 git 共享),用户级默认本机绝对路径
|
|
596
|
+
is_portable = portable if portable is not None else (scope == "project")
|
|
597
|
+
if is_portable:
|
|
598
|
+
command = "uvx"
|
|
599
|
+
elif uvx:
|
|
600
|
+
command = uvx
|
|
601
|
+
else:
|
|
602
|
+
results.append(f"[{agent}] 跳过:PATH 中未找到 uvx(可加 --portable 用命令名 uvx)")
|
|
603
|
+
continue
|
|
604
|
+
|
|
605
|
+
paths: list[tuple[str, Path]] = []
|
|
606
|
+
if agent == "zcode":
|
|
607
|
+
if scope in ("user", "both"):
|
|
608
|
+
paths.append(("user", home / ".zcode" / "cli" / "config.json"))
|
|
609
|
+
if scope in ("project", "both"):
|
|
610
|
+
paths.append(("project", cwd / ".zcode" / "config.json"))
|
|
611
|
+
elif agent == "claude-code":
|
|
612
|
+
if scope in ("user", "both"):
|
|
613
|
+
paths.append(("user", home / ".claude.json"))
|
|
614
|
+
if scope in ("project", "both"):
|
|
615
|
+
paths.append(("project", cwd / ".mcp.json"))
|
|
616
|
+
elif agent == "claude-desktop":
|
|
617
|
+
if scope == "project":
|
|
618
|
+
results.append("[claude-desktop] 跳过:桌面应用无项目级配置")
|
|
619
|
+
continue
|
|
620
|
+
appdata = Path(os.environ.get("APPDATA", str(home / "AppData" / "Roaming")))
|
|
621
|
+
paths.append(("user", appdata / "Claude" / "claude_desktop_config.json"))
|
|
622
|
+
elif agent == "codex":
|
|
623
|
+
if scope in ("user", "both"):
|
|
624
|
+
paths.append(("user", home / ".codex" / "config.toml"))
|
|
625
|
+
if scope in ("project", "both"):
|
|
626
|
+
paths.append(("project", cwd / ".codex" / "config.toml"))
|
|
627
|
+
elif agent == "cursor":
|
|
628
|
+
if scope in ("user", "both"):
|
|
629
|
+
paths.append(("user", home / ".cursor" / "mcp.json"))
|
|
630
|
+
if scope in ("project", "both"):
|
|
631
|
+
paths.append(("project", cwd / ".cursor" / "mcp.json"))
|
|
632
|
+
|
|
633
|
+
for level, path in paths:
|
|
634
|
+
try:
|
|
635
|
+
if agent == "zcode":
|
|
636
|
+
_json_upsert(path, lambda d: d.setdefault("mcp", {}).setdefault(
|
|
637
|
+
"servers", {}).__setitem__(
|
|
638
|
+
"archery", _entry_json(command, timeout_ms=True)))
|
|
639
|
+
elif agent == "codex":
|
|
640
|
+
_toml_upsert(path, "mcp_servers.archery", [
|
|
641
|
+
f"command = {_toml_escape(command)}",
|
|
642
|
+
'args = ["archery-sql-mcp"]',
|
|
643
|
+
])
|
|
644
|
+
else: # claude-code / claude-desktop / cursor:顶层 mcpServers
|
|
645
|
+
_json_upsert(path, lambda d: d.setdefault(
|
|
646
|
+
"mcpServers", {}).__setitem__(
|
|
647
|
+
"archery", _entry_json(command, timeout_ms=False)))
|
|
648
|
+
results.append(f"[{agent}/{level}] 已写入 {path} (command={command})")
|
|
649
|
+
except ArcheryError as e:
|
|
650
|
+
results.append(f"[{agent}/{level}] 失败: {e}")
|
|
651
|
+
return results
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def cmd_install_mcp(args: argparse.Namespace) -> int:
|
|
655
|
+
_ensure_utf8_stdout()
|
|
656
|
+
agents = list(_AGENT_LABELS) if args.agents == "all" else [
|
|
657
|
+
a.strip() for a in args.agents.split(",") if a.strip()
|
|
658
|
+
]
|
|
659
|
+
try:
|
|
660
|
+
for line in install_mcp(agents, args.scope, args.portable):
|
|
661
|
+
print(line)
|
|
662
|
+
print("完成。重启对应 agent 的会话后生效;配置中的 server 名为 archery。")
|
|
663
|
+
return 0
|
|
664
|
+
except ArcheryError as e:
|
|
665
|
+
print(f"错误: {e}")
|
|
666
|
+
return 1
|
|
667
|
+
|
|
668
|
+
|
|
524
669
|
def run_mcp() -> None:
|
|
525
670
|
"""MCP stdio server 模式(惰性 import,CLI 场景无需安装 mcp 包)。"""
|
|
526
671
|
mcp = _new_mcp_server("archery")
|
|
@@ -624,6 +769,23 @@ def main() -> None:
|
|
|
624
769
|
sub.add_parser("check", help="全链路自检:登录 + SELECT 1")
|
|
625
770
|
sub.add_parser("instances", help="列出有读权限的数据库实例")
|
|
626
771
|
|
|
772
|
+
p_install = sub.add_parser(
|
|
773
|
+
"install-mcp",
|
|
774
|
+
help="一键注册 archery MCP 到各 agent 客户端(zcode/claude-code/claude-desktop/codex/cursor)",
|
|
775
|
+
)
|
|
776
|
+
p_install.add_argument(
|
|
777
|
+
"--scope", choices=["user", "project", "both"], default="user",
|
|
778
|
+
help="user=各 agent 用户级配置;project=当前目录项目级配置(默认 user)",
|
|
779
|
+
)
|
|
780
|
+
p_install.add_argument(
|
|
781
|
+
"--agents", default="all",
|
|
782
|
+
help="逗号分隔:zcode,claude-code,claude-desktop,codex,cursor 或 all(默认 all)",
|
|
783
|
+
)
|
|
784
|
+
p_install.add_argument(
|
|
785
|
+
"--portable", action="store_true", default=None,
|
|
786
|
+
help="command 用命令名 uvx 而非本机绝对路径(项目级配置进 git 共享时推荐;默认项目级自动 portable)",
|
|
787
|
+
)
|
|
788
|
+
|
|
627
789
|
p_query = sub.add_parser("query", help="执行只读 SQL 查询")
|
|
628
790
|
p_query.add_argument("sql", help="SELECT / WITH 开头的 SQL 语句")
|
|
629
791
|
p_query.add_argument("--db", help="覆盖默认库名")
|
|
@@ -635,7 +797,7 @@ def main() -> None:
|
|
|
635
797
|
run_mcp() # 无参数 → MCP stdio 模式
|
|
636
798
|
return
|
|
637
799
|
handlers = {"init": cmd_init, "check": cmd_check, "query": cmd_query,
|
|
638
|
-
"instances": cmd_instances}
|
|
800
|
+
"instances": cmd_instances, "install-mcp": cmd_install_mcp}
|
|
639
801
|
sys.exit(handlers[args.command](args))
|
|
640
802
|
|
|
641
803
|
|
|
File without changes
|