@oneciel-ai/claude-any 0.1.77 → 0.1.79
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.
- package/claude_any.py +155 -16
- package/package.json +1 -1
package/claude_any.py
CHANGED
|
@@ -104,7 +104,7 @@ OFFICIAL_CHANNEL_PLUGINS = {
|
|
|
104
104
|
"fakechat": "plugin:fakechat@claude-plugins-official",
|
|
105
105
|
}
|
|
106
106
|
APP_NAME = "Claude Any"
|
|
107
|
-
VERSION = "0.1.
|
|
107
|
+
VERSION = "0.1.79"
|
|
108
108
|
CREDITS = "Credits: One Ciel LLC"
|
|
109
109
|
|
|
110
110
|
LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
|
|
@@ -8316,6 +8316,21 @@ class RouterHandler(BaseHTTPRequestHandler):
|
|
|
8316
8316
|
except Exception:
|
|
8317
8317
|
pass
|
|
8318
8318
|
|
|
8319
|
+
def do_HEAD(self) -> None:
|
|
8320
|
+
cfg = load_config()
|
|
8321
|
+
if reject_external_router_request(self, cfg):
|
|
8322
|
+
return
|
|
8323
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
8324
|
+
path = parsed.path
|
|
8325
|
+
if path in ("/", "/health", "/healthz"):
|
|
8326
|
+
self.send_response(200)
|
|
8327
|
+
self.send_header("content-type", "text/plain; charset=utf-8")
|
|
8328
|
+
self.end_headers()
|
|
8329
|
+
return
|
|
8330
|
+
self.send_response(404)
|
|
8331
|
+
self.send_header("content-type", "application/json")
|
|
8332
|
+
self.end_headers()
|
|
8333
|
+
|
|
8319
8334
|
def do_GET(self) -> None:
|
|
8320
8335
|
parsed = urllib.parse.urlparse(self.path)
|
|
8321
8336
|
path = parsed.path
|
|
@@ -9091,8 +9106,8 @@ def _mcp_server_is_stdio(server: dict[str, Any]) -> bool:
|
|
|
9091
9106
|
command = str(server.get("command") or "").strip()
|
|
9092
9107
|
if not command:
|
|
9093
9108
|
return False
|
|
9094
|
-
|
|
9095
|
-
return "mcp-proxy" not in
|
|
9109
|
+
args = [str(item) for item in server.get("args", []) if item is not None] if isinstance(server.get("args", []), list) else []
|
|
9110
|
+
return "mcp-proxy" not in args
|
|
9096
9111
|
|
|
9097
9112
|
|
|
9098
9113
|
def _mcp_config_passthrough_values(passthrough: list[str]) -> list[str]:
|
|
@@ -13744,14 +13759,7 @@ def _mcp_proxy_notification_payload(server_name: str, message: dict[str, Any]) -
|
|
|
13744
13759
|
}
|
|
13745
13760
|
|
|
13746
13761
|
|
|
13747
|
-
def
|
|
13748
|
-
try:
|
|
13749
|
-
text = line.decode("utf-8", errors="replace").strip()
|
|
13750
|
-
if not text or not text.startswith("{"):
|
|
13751
|
-
return
|
|
13752
|
-
payload = json.loads(text)
|
|
13753
|
-
except Exception:
|
|
13754
|
-
return
|
|
13762
|
+
def _mcp_proxy_observe_json_message(server_name: str, payload: Any) -> None:
|
|
13755
13763
|
if not isinstance(payload, dict):
|
|
13756
13764
|
return
|
|
13757
13765
|
chat_payload = _mcp_proxy_notification_payload(server_name, payload)
|
|
@@ -13767,10 +13775,131 @@ def _mcp_proxy_observe_stdout_line(server_name: str, line: bytes) -> None:
|
|
|
13767
13775
|
router_log("WARN", f"mcp_proxy_notification_failed server={server_name} error={type(exc).__name__}: {exc}")
|
|
13768
13776
|
|
|
13769
13777
|
|
|
13778
|
+
def _mcp_proxy_observe_stdout_line(server_name: str, line: bytes) -> None:
|
|
13779
|
+
try:
|
|
13780
|
+
text = line.decode("utf-8", errors="replace").strip()
|
|
13781
|
+
if not text or not text.startswith("{"):
|
|
13782
|
+
return
|
|
13783
|
+
payload = json.loads(text)
|
|
13784
|
+
except Exception:
|
|
13785
|
+
return
|
|
13786
|
+
_mcp_proxy_observe_json_message(server_name, payload)
|
|
13787
|
+
|
|
13788
|
+
|
|
13789
|
+
def _mcp_proxy_header_end(buffer: bytes) -> tuple[int, int] | None:
|
|
13790
|
+
crlf = buffer.find(b"\r\n\r\n")
|
|
13791
|
+
lf = buffer.find(b"\n\n")
|
|
13792
|
+
candidates: list[tuple[int, int]] = []
|
|
13793
|
+
if crlf >= 0:
|
|
13794
|
+
candidates.append((crlf, 4))
|
|
13795
|
+
if lf >= 0:
|
|
13796
|
+
candidates.append((lf, 2))
|
|
13797
|
+
return min(candidates, key=lambda item: item[0]) if candidates else None
|
|
13798
|
+
|
|
13799
|
+
|
|
13800
|
+
def _mcp_proxy_frame_header(buffer: bytes) -> tuple[int, int, int] | None:
|
|
13801
|
+
header = _mcp_proxy_header_end(buffer)
|
|
13802
|
+
if not header:
|
|
13803
|
+
return None
|
|
13804
|
+
header_end, delimiter_len = header
|
|
13805
|
+
length = _mcp_proxy_content_length(buffer[:header_end])
|
|
13806
|
+
if length is None:
|
|
13807
|
+
return None
|
|
13808
|
+
return header_end, delimiter_len, length
|
|
13809
|
+
|
|
13810
|
+
|
|
13811
|
+
def _mcp_proxy_content_length(header_bytes: bytes) -> int | None:
|
|
13812
|
+
try:
|
|
13813
|
+
header_text = header_bytes.decode("ascii", errors="replace")
|
|
13814
|
+
except Exception:
|
|
13815
|
+
return None
|
|
13816
|
+
for line in re.split(r"\r?\n", header_text):
|
|
13817
|
+
name, sep, value = line.partition(":")
|
|
13818
|
+
if sep and name.strip().lower() == "content-length":
|
|
13819
|
+
try:
|
|
13820
|
+
length = int(value.strip())
|
|
13821
|
+
except Exception:
|
|
13822
|
+
return None
|
|
13823
|
+
return length if length >= 0 else None
|
|
13824
|
+
return None
|
|
13825
|
+
|
|
13826
|
+
|
|
13827
|
+
class _McpStdoutObserver:
|
|
13828
|
+
def __init__(self, server_name: str) -> None:
|
|
13829
|
+
self.server_name = server_name
|
|
13830
|
+
self.buffer = bytearray()
|
|
13831
|
+
|
|
13832
|
+
def feed(self, chunk: bytes) -> None:
|
|
13833
|
+
if not chunk:
|
|
13834
|
+
return
|
|
13835
|
+
self.buffer.extend(chunk)
|
|
13836
|
+
self._drain()
|
|
13837
|
+
|
|
13838
|
+
def _drop_until_candidate(self) -> bool:
|
|
13839
|
+
data = bytes(self.buffer)
|
|
13840
|
+
if not data:
|
|
13841
|
+
return False
|
|
13842
|
+
stripped = data.lstrip()
|
|
13843
|
+
if len(stripped) != len(data):
|
|
13844
|
+
del self.buffer[: len(data) - len(stripped)]
|
|
13845
|
+
data = stripped
|
|
13846
|
+
if _mcp_proxy_frame_header(data) or data.startswith(b"{"):
|
|
13847
|
+
return True
|
|
13848
|
+
lowered = data.lower()
|
|
13849
|
+
content_idx = lowered.find(b"content-length:")
|
|
13850
|
+
json_idx = data.find(b"{")
|
|
13851
|
+
candidates = [idx for idx in (content_idx, json_idx) if idx >= 0]
|
|
13852
|
+
newline_idx = data.find(b"\n")
|
|
13853
|
+
if candidates:
|
|
13854
|
+
keep_from = min(candidates)
|
|
13855
|
+
if newline_idx >= 0 and newline_idx < keep_from:
|
|
13856
|
+
del self.buffer[: newline_idx + 1]
|
|
13857
|
+
elif keep_from > 0:
|
|
13858
|
+
del self.buffer[:keep_from]
|
|
13859
|
+
return True
|
|
13860
|
+
if newline_idx >= 0:
|
|
13861
|
+
del self.buffer[: newline_idx + 1]
|
|
13862
|
+
return True
|
|
13863
|
+
if len(self.buffer) > 1024 * 1024:
|
|
13864
|
+
del self.buffer[:-4096]
|
|
13865
|
+
return False
|
|
13866
|
+
|
|
13867
|
+
def _drain(self) -> None:
|
|
13868
|
+
while self.buffer:
|
|
13869
|
+
if not self._drop_until_candidate():
|
|
13870
|
+
return
|
|
13871
|
+
data = bytes(self.buffer)
|
|
13872
|
+
frame = _mcp_proxy_frame_header(data)
|
|
13873
|
+
if frame:
|
|
13874
|
+
header_end, delimiter_len, length = frame
|
|
13875
|
+
body_start = header_end + delimiter_len
|
|
13876
|
+
body_end = body_start + length
|
|
13877
|
+
if len(data) < body_end:
|
|
13878
|
+
return
|
|
13879
|
+
body = data[body_start:body_end]
|
|
13880
|
+
del self.buffer[:body_end]
|
|
13881
|
+
try:
|
|
13882
|
+
payload = json.loads(body.decode("utf-8", errors="replace"))
|
|
13883
|
+
except Exception:
|
|
13884
|
+
continue
|
|
13885
|
+
_mcp_proxy_observe_json_message(self.server_name, payload)
|
|
13886
|
+
continue
|
|
13887
|
+
if data.startswith(b"{"):
|
|
13888
|
+
newline_idx = data.find(b"\n")
|
|
13889
|
+
if newline_idx < 0:
|
|
13890
|
+
return
|
|
13891
|
+
line = data[:newline_idx]
|
|
13892
|
+
del self.buffer[: newline_idx + 1]
|
|
13893
|
+
_mcp_proxy_observe_stdout_line(self.server_name, line)
|
|
13894
|
+
continue
|
|
13895
|
+
return
|
|
13896
|
+
|
|
13897
|
+
|
|
13770
13898
|
def _mcp_proxy_forward_stdin(proc: subprocess.Popen[bytes]) -> None:
|
|
13771
13899
|
try:
|
|
13900
|
+
stdin_fd = sys.stdin.fileno()
|
|
13772
13901
|
while True:
|
|
13773
|
-
chunk =
|
|
13902
|
+
chunk = os.read(stdin_fd, 65536)
|
|
13774
13903
|
if not chunk:
|
|
13775
13904
|
break
|
|
13776
13905
|
if proc.stdin:
|
|
@@ -13825,19 +13954,27 @@ def run_mcp_stdio_proxy(server_name: str, server_config_path: Path) -> int:
|
|
|
13825
13954
|
stderr=subprocess.PIPE,
|
|
13826
13955
|
cwd=cwd,
|
|
13827
13956
|
env=env,
|
|
13957
|
+
bufsize=0,
|
|
13828
13958
|
)
|
|
13829
13959
|
except Exception as exc:
|
|
13830
13960
|
print(f"claude-any mcp-proxy: failed to start {command}: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
|
|
13831
13961
|
return 127
|
|
13962
|
+
router_log("INFO", f"mcp_proxy_started server={server_name} command={command}")
|
|
13832
13963
|
threading.Thread(target=_mcp_proxy_forward_stdin, args=(proc,), daemon=True, name=f"mcp-proxy-stdin-{server_name}").start()
|
|
13833
13964
|
threading.Thread(target=_mcp_proxy_forward_stderr, args=(proc,), daemon=True, name=f"mcp-proxy-stderr-{server_name}").start()
|
|
13834
13965
|
try:
|
|
13966
|
+
observer = _McpStdoutObserver(server_name)
|
|
13835
13967
|
if proc.stdout:
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13968
|
+
while True:
|
|
13969
|
+
chunk = proc.stdout.read(65536)
|
|
13970
|
+
if not chunk:
|
|
13971
|
+
break
|
|
13972
|
+
observer.feed(chunk)
|
|
13973
|
+
sys.stdout.buffer.write(chunk)
|
|
13839
13974
|
sys.stdout.buffer.flush()
|
|
13840
|
-
|
|
13975
|
+
rc = proc.wait()
|
|
13976
|
+
router_log("INFO", f"mcp_proxy_exited server={server_name} rc={rc}")
|
|
13977
|
+
return rc
|
|
13841
13978
|
finally:
|
|
13842
13979
|
if proc.poll() is None:
|
|
13843
13980
|
try:
|
|
@@ -14988,6 +15125,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
14988
15125
|
|
|
14989
15126
|
|
|
14990
15127
|
def main() -> None:
|
|
15128
|
+
if len(sys.argv) >= 2 and sys.argv[1] == "mcp-proxy":
|
|
15129
|
+
raise SystemExit(cmd_mcp_proxy(sys.argv[2:]))
|
|
14991
15130
|
if len(sys.argv) >= 2 and sys.argv[1] == "cli":
|
|
14992
15131
|
raise SystemExit(run_cli(sys.argv[2:]))
|
|
14993
15132
|
if len(sys.argv) >= 2 and sys.argv[1] == "launch":
|
package/package.json
CHANGED