@oneciel-ai/claude-any 0.1.87 → 0.1.89

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 +104 -35
  2. package/package.json +1 -1
package/claude_any.py CHANGED
@@ -105,7 +105,7 @@ OFFICIAL_CHANNEL_PLUGINS = {
105
105
  "fakechat": "plugin:fakechat@claude-plugins-official",
106
106
  }
107
107
  APP_NAME = "Claude Any"
108
- VERSION = "0.1.87"
108
+ VERSION = "0.1.89"
109
109
  CREDITS = "Credits: One Ciel LLC"
110
110
 
111
111
  LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
@@ -3834,6 +3834,12 @@ def write_json(handler: BaseHTTPRequestHandler, obj: Any, status: int = 200) ->
3834
3834
  handler.wfile.write(body)
3835
3835
 
3836
3836
 
3837
+ def write_empty_response(handler: BaseHTTPRequestHandler, status: int = 202) -> None:
3838
+ handler.send_response(status)
3839
+ handler.send_header("content-length", "0")
3840
+ handler.end_headers()
3841
+
3842
+
3837
3843
  def reject_external_router_request(handler: BaseHTTPRequestHandler, cfg: dict[str, Any] | None = None) -> bool:
3838
3844
  if router_request_allowed(handler, cfg):
3839
3845
  return False
@@ -4920,6 +4926,56 @@ def _write_sse_event(handler: BaseHTTPRequestHandler, event: str, data: Any, eve
4920
4926
  handler.wfile.flush()
4921
4927
 
4922
4928
 
4929
+ def _send_channel_mcp_sse_headers(handler: BaseHTTPRequestHandler) -> None:
4930
+ handler.send_response(200)
4931
+ handler.send_header("content-type", "text/event-stream")
4932
+ handler.send_header("cache-control", "no-cache, no-transform")
4933
+ handler.send_header("connection", "keep-alive")
4934
+ handler.send_header("x-accel-buffering", "no")
4935
+ handler.end_headers()
4936
+
4937
+
4938
+ def _channel_mcp_enqueue(session: str, payload: dict[str, Any]) -> bool:
4939
+ if not session:
4940
+ return False
4941
+ with _CHANNEL_MCP_LOCK:
4942
+ state = _CHANNEL_MCP_SESSIONS.get(session)
4943
+ if not state:
4944
+ return False
4945
+ outbox = state.setdefault("outbox", [])
4946
+ if isinstance(outbox, list):
4947
+ outbox.append(payload)
4948
+ else:
4949
+ state["outbox"] = [payload]
4950
+ with _CHAT_CONDITION:
4951
+ _CHAT_CONDITION.notify_all()
4952
+ return True
4953
+
4954
+
4955
+ def _channel_mcp_take_outbox(session: str) -> list[dict[str, Any]]:
4956
+ with _CHANNEL_MCP_LOCK:
4957
+ state = _CHANNEL_MCP_SESSIONS.get(session)
4958
+ if not state:
4959
+ return []
4960
+ outbox = state.get("outbox")
4961
+ if not isinstance(outbox, list) or not outbox:
4962
+ return []
4963
+ state["outbox"] = []
4964
+ return [item for item in outbox if isinstance(item, dict)]
4965
+
4966
+
4967
+ def _channel_mcp_initialize_response(request_id: Any, protocol: str) -> dict[str, Any]:
4968
+ return {
4969
+ "jsonrpc": "2.0",
4970
+ "id": request_id,
4971
+ "result": {
4972
+ "protocolVersion": protocol,
4973
+ "capabilities": _channel_mcp_capabilities(),
4974
+ "serverInfo": {"name": "claude-any-router", "version": VERSION},
4975
+ },
4976
+ }
4977
+
4978
+
4923
4979
  def _channel_mcp_write_cursor_locked(last_id: int) -> None:
4924
4980
  CONFIG_DIR.mkdir(parents=True, exist_ok=True)
4925
4981
  tmp_path = CHANNEL_MCP_CURSOR_PATH.with_suffix(".json.tmp")
@@ -4999,22 +5055,27 @@ def handle_channel_mcp_get(handler: BaseHTTPRequestHandler, path: str) -> bool:
4999
5055
  session = _channel_mcp_session_id()
5000
5056
  last_id = _channel_mcp_ensure_cursor_initialized()
5001
5057
  with _CHANNEL_MCP_LOCK:
5002
- _CHANNEL_MCP_SESSIONS[session] = {"created_at": time.time(), "last_id": last_id, "initialized": False}
5058
+ _CHANNEL_MCP_SESSIONS[session] = {"created_at": time.time(), "last_id": last_id, "initialized": False, "outbox": []}
5003
5059
  router_log("INFO", f"channel_mcp_session_started session={session} last_id={last_id}")
5004
- handler.send_response(200)
5005
- handler.send_header("content-type", "text/event-stream")
5006
- handler.send_header("cache-control", "no-cache")
5007
- handler.send_header("connection", "close")
5008
- handler.end_headers()
5060
+ started_at = time.time()
5061
+ close_reason = "finished"
5062
+ _send_channel_mcp_sse_headers(handler)
5009
5063
  _write_sse_event(handler, "endpoint", f"/ca/mcp/messages?session={urllib.parse.quote(session)}")
5010
5064
  try:
5011
5065
  while True:
5012
5066
  with _CHANNEL_MCP_LOCK:
5013
5067
  state = _CHANNEL_MCP_SESSIONS.get(session)
5014
5068
  if not state:
5069
+ close_reason = "session_missing"
5015
5070
  return True
5016
5071
  last_id = int(state.get("last_id") or 0)
5017
5072
  initialized = bool(state.get("initialized"))
5073
+ outbox = _channel_mcp_take_outbox(session)
5074
+ if outbox:
5075
+ for payload in outbox:
5076
+ _write_sse_event(handler, "message", payload)
5077
+ router_log("INFO", f"channel_mcp_rpc_flushed session={session} count={len(outbox)}")
5078
+ continue
5018
5079
  if not initialized:
5019
5080
  handler.wfile.write(b": waiting-for-initialize\n\n")
5020
5081
  handler.wfile.flush()
@@ -5036,10 +5097,16 @@ def handle_channel_mcp_get(handler: BaseHTTPRequestHandler, path: str) -> bool:
5036
5097
  handler.wfile.write(b": keepalive\n\n")
5037
5098
  handler.wfile.flush()
5038
5099
  with _CHAT_CONDITION:
5039
- _CHAT_CONDITION.wait(timeout=15.0)
5040
- except (BrokenPipeError, ConnectionError, ConnectionResetError):
5100
+ _CHAT_CONDITION.wait(timeout=5.0)
5101
+ except (BrokenPipeError, ConnectionError, ConnectionResetError) as exc:
5102
+ close_reason = type(exc).__name__
5103
+ return True
5104
+ except Exception as exc:
5105
+ close_reason = type(exc).__name__
5106
+ router_log("ERROR", f"channel_mcp_session_failed session={session} error={type(exc).__name__}: {exc}")
5041
5107
  return True
5042
5108
  finally:
5109
+ router_log("INFO", f"channel_mcp_session_closed session={session} reason={close_reason} age={time.time() - started_at:.1f}s")
5043
5110
  with _CHANNEL_MCP_LOCK:
5044
5111
  _CHANNEL_MCP_SESSIONS.pop(session, None)
5045
5112
 
@@ -5054,6 +5121,7 @@ def handle_channel_mcp_post(handler: BaseHTTPRequestHandler, path: str, body: di
5054
5121
  _CHANNEL_MCP_SESSIONS[session]["last_seen_at"] = time.time()
5055
5122
  method = str(body.get("method") or "")
5056
5123
  request_id = body.get("id")
5124
+ response: dict[str, Any] | None = None
5057
5125
  if method == "initialize":
5058
5126
  protocol = "2024-11-05"
5059
5127
  req_params = body.get("params") if isinstance(body.get("params"), dict) else {}
@@ -5062,32 +5130,29 @@ def handle_channel_mcp_post(handler: BaseHTTPRequestHandler, path: str, body: di
5062
5130
  with _CHANNEL_MCP_LOCK:
5063
5131
  if session and session in _CHANNEL_MCP_SESSIONS:
5064
5132
  _CHANNEL_MCP_SESSIONS[session]["initialized"] = True
5065
- with _CHAT_CONDITION:
5066
- _CHAT_CONDITION.notify_all()
5067
- write_json(
5068
- handler,
5069
- {
5070
- "jsonrpc": "2.0",
5071
- "id": request_id,
5072
- "result": {
5073
- "protocolVersion": protocol,
5074
- "capabilities": _channel_mcp_capabilities(),
5075
- "serverInfo": {"name": "claude-any-router", "version": VERSION},
5076
- },
5077
- },
5078
- )
5133
+ response = _channel_mcp_initialize_response(request_id, protocol)
5079
5134
  router_log("INFO", f"channel_mcp_initialized session={session or '-'} protocol={protocol}")
5080
- return True
5081
- if method == "tools/list":
5082
- write_json(handler, {"jsonrpc": "2.0", "id": request_id, "result": {"tools": []}})
5083
- return True
5084
- if method == "ping":
5085
- write_json(handler, {"jsonrpc": "2.0", "id": request_id, "result": {}})
5086
- return True
5087
- if request_id is None:
5088
- write_json(handler, {"ok": True})
5089
- return True
5090
- write_json(handler, {"jsonrpc": "2.0", "id": request_id, "result": {}})
5135
+ elif method == "tools/list":
5136
+ response = {"jsonrpc": "2.0", "id": request_id, "result": {"tools": []}}
5137
+ elif method == "ping":
5138
+ response = {"jsonrpc": "2.0", "id": request_id, "result": {}}
5139
+ elif request_id is not None:
5140
+ response = {"jsonrpc": "2.0", "id": request_id, "result": {}}
5141
+ if response is not None:
5142
+ if not _channel_mcp_enqueue(session, response):
5143
+ router_log("WARN", f"channel_mcp_rpc_enqueue_failed session={session or '-'} method={method}")
5144
+ write_json(
5145
+ handler,
5146
+ {
5147
+ "jsonrpc": "2.0",
5148
+ "id": request_id,
5149
+ "error": {"code": -32000, "message": "MCP SSE session is not connected"},
5150
+ },
5151
+ 404,
5152
+ )
5153
+ return True
5154
+ router_log("INFO", f"channel_mcp_rpc_queued session={session or '-'} method={method} request_id={request_id}")
5155
+ write_empty_response(handler, 202)
5091
5156
  return True
5092
5157
 
5093
5158
 
@@ -14387,9 +14452,11 @@ def run_mcp_stdio_proxy(server_name: str, server_config_path: Path) -> int:
14387
14452
  try:
14388
14453
  server = json.loads(server_config_path.read_text(encoding="utf-8"))
14389
14454
  except Exception as exc:
14455
+ router_log("ERROR", f"mcp_proxy_config_read_failed server={server_name} error={type(exc).__name__}: {exc}")
14390
14456
  print(f"claude-any mcp-proxy: cannot read server config: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
14391
14457
  return 2
14392
14458
  if not isinstance(server, dict) or not _mcp_server_is_stdio(server):
14459
+ router_log("ERROR", f"mcp_proxy_invalid_config server={server_name}")
14393
14460
  print("claude-any mcp-proxy: server config is not a stdio MCP server", file=sys.stderr, flush=True)
14394
14461
  return 2
14395
14462
  command = str(server.get("command") or "").strip()
@@ -14411,6 +14478,7 @@ def run_mcp_stdio_proxy(server_name: str, server_config_path: Path) -> int:
14411
14478
  bufsize=0,
14412
14479
  )
14413
14480
  except Exception as exc:
14481
+ router_log("ERROR", f"mcp_proxy_start_failed server={server_name} command={command} error={type(exc).__name__}: {exc}")
14414
14482
  print(f"claude-any mcp-proxy: failed to start {command}: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
14415
14483
  return 127
14416
14484
  router_log("INFO", f"mcp_proxy_started server={server_name} command={command}")
@@ -14427,7 +14495,8 @@ def run_mcp_stdio_proxy(server_name: str, server_config_path: Path) -> int:
14427
14495
  sys.stdout.buffer.write(chunk)
14428
14496
  sys.stdout.buffer.flush()
14429
14497
  rc = proc.wait()
14430
- router_log("INFO", f"mcp_proxy_exited server={server_name} rc={rc}")
14498
+ level = "INFO" if rc == 0 else "WARN"
14499
+ router_log(level, f"mcp_proxy_exited server={server_name} rc={rc}")
14431
14500
  return rc
14432
14501
  finally:
14433
14502
  if proc.poll() is None:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneciel-ai/claude-any",
3
- "version": "0.1.87",
3
+ "version": "0.1.89",
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",