@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.
Files changed (2) hide show
  1. package/claude_any.py +155 -16
  2. 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.77"
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
- joined = " ".join([command, *[str(item) for item in server.get("args", []) if item is not None]])
9095
- return "mcp-proxy" not in joined
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 _mcp_proxy_observe_stdout_line(server_name: str, line: bytes) -> None:
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 = sys.stdin.buffer.read(65536)
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
- for line in iter(proc.stdout.readline, b""):
13837
- _mcp_proxy_observe_stdout_line(server_name, line)
13838
- sys.stdout.buffer.write(line)
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
- return proc.wait()
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneciel-ai/claude-any",
3
- "version": "0.1.77",
3
+ "version": "0.1.79",
4
4
  "description": "Claude Code provider selector for Anthropic, Ollama, Ollama Cloud, vLLM, NVIDIA hosted, and self-hosted NIM.",
5
5
  "license": "MIT",
6
6
  "author": "One Ciel LLC",