@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.
- package/claude_any.py +104 -35
- 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.
|
|
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
|
-
|
|
5005
|
-
|
|
5006
|
-
handler
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
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
|
-
|
|
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