@oneciel-ai/claude-any 0.1.86 → 0.1.88
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 +89 -10
- package/package.json +1 -1
package/claude_any.py
CHANGED
|
@@ -58,6 +58,7 @@ MODEL_LIST_CACHE_PATH = CONFIG_DIR / "model-list-cache.json"
|
|
|
58
58
|
WEB_TOOLS_MCP_CONFIG = CONFIG_DIR / "web-tools-mcp.json"
|
|
59
59
|
DUCKDUCKGO_MCP_CONFIG = CONFIG_DIR / "duckduckgo-mcp.json"
|
|
60
60
|
CHANNEL_MCP_CONFIG = CONFIG_DIR / "channel-mcp.json"
|
|
61
|
+
CHANNEL_MCP_CURSOR_PATH = CONFIG_DIR / "channel-mcp-cursor.json"
|
|
61
62
|
MCP_PROXY_CONFIG = CONFIG_DIR / "mcp-proxy.json"
|
|
62
63
|
ROUTER_HOST = os.environ.get("CLAUDE_ANY_ROUTER_CLIENT_HOST", "127.0.0.1").strip() or "127.0.0.1"
|
|
63
64
|
ROUTER_PORT = 8799
|
|
@@ -104,7 +105,7 @@ OFFICIAL_CHANNEL_PLUGINS = {
|
|
|
104
105
|
"fakechat": "plugin:fakechat@claude-plugins-official",
|
|
105
106
|
}
|
|
106
107
|
APP_NAME = "Claude Any"
|
|
107
|
-
VERSION = "0.1.
|
|
108
|
+
VERSION = "0.1.88"
|
|
108
109
|
CREDITS = "Credits: One Ciel LLC"
|
|
109
110
|
|
|
110
111
|
LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
|
|
@@ -124,6 +125,8 @@ _CHANNEL_SSE_LOCK = threading.Lock()
|
|
|
124
125
|
_CHANNEL_SSE_CONNECTIONS: dict[str, dict[str, Any]] = {}
|
|
125
126
|
_CHANNEL_MCP_LOCK = threading.Lock()
|
|
126
127
|
_CHANNEL_MCP_SESSIONS: dict[str, dict[str, Any]] = {}
|
|
128
|
+
_CHANNEL_MCP_CURSOR_LOCK = threading.Lock()
|
|
129
|
+
_CHANNEL_MCP_CURSOR_LAST_ID: int | None = None
|
|
127
130
|
_NATIVE_CHANNEL_NOTIFICATION_METHOD = "notifications/claude/channel"
|
|
128
131
|
_MCP_NOTIFICATION_DEDUP_TTL_SECONDS = 3.0
|
|
129
132
|
_MCP_NOTIFICATION_DEDUP_LOCK = threading.Lock()
|
|
@@ -4917,6 +4920,61 @@ def _write_sse_event(handler: BaseHTTPRequestHandler, event: str, data: Any, eve
|
|
|
4917
4920
|
handler.wfile.flush()
|
|
4918
4921
|
|
|
4919
4922
|
|
|
4923
|
+
def _send_channel_mcp_sse_headers(handler: BaseHTTPRequestHandler) -> None:
|
|
4924
|
+
handler.send_response(200)
|
|
4925
|
+
handler.send_header("content-type", "text/event-stream")
|
|
4926
|
+
handler.send_header("cache-control", "no-cache, no-transform")
|
|
4927
|
+
handler.send_header("connection", "keep-alive")
|
|
4928
|
+
handler.send_header("x-accel-buffering", "no")
|
|
4929
|
+
handler.end_headers()
|
|
4930
|
+
|
|
4931
|
+
|
|
4932
|
+
def _channel_mcp_write_cursor_locked(last_id: int) -> None:
|
|
4933
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
4934
|
+
tmp_path = CHANNEL_MCP_CURSOR_PATH.with_suffix(".json.tmp")
|
|
4935
|
+
tmp_path.write_text(json.dumps({"last_id": max(0, int(last_id))}, separators=(",", ":")) + "\n", encoding="utf-8")
|
|
4936
|
+
tmp_path.replace(CHANNEL_MCP_CURSOR_PATH)
|
|
4937
|
+
|
|
4938
|
+
|
|
4939
|
+
def _channel_mcp_read_cursor_locked() -> int:
|
|
4940
|
+
global _CHANNEL_MCP_CURSOR_LAST_ID
|
|
4941
|
+
if _CHANNEL_MCP_CURSOR_LAST_ID is not None:
|
|
4942
|
+
return _CHANNEL_MCP_CURSOR_LAST_ID
|
|
4943
|
+
if CHANNEL_MCP_CURSOR_PATH.exists():
|
|
4944
|
+
try:
|
|
4945
|
+
data = json.loads(CHANNEL_MCP_CURSOR_PATH.read_text(encoding="utf-8"))
|
|
4946
|
+
_CHANNEL_MCP_CURSOR_LAST_ID = max(0, int(data.get("last_id") or 0))
|
|
4947
|
+
return _CHANNEL_MCP_CURSOR_LAST_ID
|
|
4948
|
+
except Exception as exc:
|
|
4949
|
+
router_log("WARN", f"channel_mcp_cursor_read_failed error={type(exc).__name__}: {exc}")
|
|
4950
|
+
_CHANNEL_MCP_CURSOR_LAST_ID = max(0, _chat_init_next_id() - 1)
|
|
4951
|
+
try:
|
|
4952
|
+
_channel_mcp_write_cursor_locked(_CHANNEL_MCP_CURSOR_LAST_ID)
|
|
4953
|
+
except Exception as exc:
|
|
4954
|
+
router_log("WARN", f"channel_mcp_cursor_write_failed error={type(exc).__name__}: {exc}")
|
|
4955
|
+
return _CHANNEL_MCP_CURSOR_LAST_ID
|
|
4956
|
+
|
|
4957
|
+
|
|
4958
|
+
def _channel_mcp_ensure_cursor_initialized() -> int:
|
|
4959
|
+
with _CHANNEL_MCP_CURSOR_LOCK:
|
|
4960
|
+
return _channel_mcp_read_cursor_locked()
|
|
4961
|
+
|
|
4962
|
+
|
|
4963
|
+
def _channel_mcp_update_cursor(last_id: int) -> None:
|
|
4964
|
+
global _CHANNEL_MCP_CURSOR_LAST_ID
|
|
4965
|
+
if last_id < 0:
|
|
4966
|
+
return
|
|
4967
|
+
with _CHANNEL_MCP_CURSOR_LOCK:
|
|
4968
|
+
current = _channel_mcp_read_cursor_locked()
|
|
4969
|
+
if last_id <= current:
|
|
4970
|
+
return
|
|
4971
|
+
_CHANNEL_MCP_CURSOR_LAST_ID = int(last_id)
|
|
4972
|
+
try:
|
|
4973
|
+
_channel_mcp_write_cursor_locked(_CHANNEL_MCP_CURSOR_LAST_ID)
|
|
4974
|
+
except Exception as exc:
|
|
4975
|
+
router_log("WARN", f"channel_mcp_cursor_write_failed error={type(exc).__name__}: {exc}")
|
|
4976
|
+
|
|
4977
|
+
|
|
4920
4978
|
def _channel_mcp_notifications_for_messages(
|
|
4921
4979
|
messages: list[dict[str, Any]],
|
|
4922
4980
|
session: str = "",
|
|
@@ -4948,28 +5006,36 @@ def handle_channel_mcp_get(handler: BaseHTTPRequestHandler, path: str) -> bool:
|
|
|
4948
5006
|
if path != "/ca/mcp/sse":
|
|
4949
5007
|
return False
|
|
4950
5008
|
session = _channel_mcp_session_id()
|
|
4951
|
-
last_id =
|
|
5009
|
+
last_id = _channel_mcp_ensure_cursor_initialized()
|
|
4952
5010
|
with _CHANNEL_MCP_LOCK:
|
|
4953
5011
|
_CHANNEL_MCP_SESSIONS[session] = {"created_at": time.time(), "last_id": last_id, "initialized": False}
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
handler
|
|
4958
|
-
handler.end_headers()
|
|
5012
|
+
router_log("INFO", f"channel_mcp_session_started session={session} last_id={last_id}")
|
|
5013
|
+
started_at = time.time()
|
|
5014
|
+
close_reason = "finished"
|
|
5015
|
+
_send_channel_mcp_sse_headers(handler)
|
|
4959
5016
|
_write_sse_event(handler, "endpoint", f"/ca/mcp/messages?session={urllib.parse.quote(session)}")
|
|
4960
5017
|
try:
|
|
4961
5018
|
while True:
|
|
4962
5019
|
with _CHANNEL_MCP_LOCK:
|
|
4963
5020
|
state = _CHANNEL_MCP_SESSIONS.get(session)
|
|
4964
5021
|
if not state:
|
|
5022
|
+
close_reason = "session_missing"
|
|
4965
5023
|
return True
|
|
4966
5024
|
last_id = int(state.get("last_id") or 0)
|
|
5025
|
+
initialized = bool(state.get("initialized"))
|
|
5026
|
+
if not initialized:
|
|
5027
|
+
handler.wfile.write(b": waiting-for-initialize\n\n")
|
|
5028
|
+
handler.wfile.flush()
|
|
5029
|
+
with _CHAT_CONDITION:
|
|
5030
|
+
_CHAT_CONDITION.wait(timeout=1.0)
|
|
5031
|
+
continue
|
|
4967
5032
|
messages = read_chat_messages(last_id, None, None, 100)
|
|
4968
5033
|
if messages:
|
|
4969
5034
|
delivered_last_id, events = _channel_mcp_notifications_for_messages(messages, session)
|
|
4970
5035
|
last_id = max(last_id, delivered_last_id)
|
|
4971
5036
|
for event_id, notification in events:
|
|
4972
5037
|
_write_sse_event(handler, "message", notification, event_id)
|
|
5038
|
+
_channel_mcp_update_cursor(last_id)
|
|
4973
5039
|
with _CHANNEL_MCP_LOCK:
|
|
4974
5040
|
state = _CHANNEL_MCP_SESSIONS.get(session)
|
|
4975
5041
|
if state:
|
|
@@ -4978,10 +5044,16 @@ def handle_channel_mcp_get(handler: BaseHTTPRequestHandler, path: str) -> bool:
|
|
|
4978
5044
|
handler.wfile.write(b": keepalive\n\n")
|
|
4979
5045
|
handler.wfile.flush()
|
|
4980
5046
|
with _CHAT_CONDITION:
|
|
4981
|
-
_CHAT_CONDITION.wait(timeout=
|
|
4982
|
-
except (BrokenPipeError, ConnectionError, ConnectionResetError):
|
|
5047
|
+
_CHAT_CONDITION.wait(timeout=5.0)
|
|
5048
|
+
except (BrokenPipeError, ConnectionError, ConnectionResetError) as exc:
|
|
5049
|
+
close_reason = type(exc).__name__
|
|
5050
|
+
return True
|
|
5051
|
+
except Exception as exc:
|
|
5052
|
+
close_reason = type(exc).__name__
|
|
5053
|
+
router_log("ERROR", f"channel_mcp_session_failed session={session} error={type(exc).__name__}: {exc}")
|
|
4983
5054
|
return True
|
|
4984
5055
|
finally:
|
|
5056
|
+
router_log("INFO", f"channel_mcp_session_closed session={session} reason={close_reason} age={time.time() - started_at:.1f}s")
|
|
4985
5057
|
with _CHANNEL_MCP_LOCK:
|
|
4986
5058
|
_CHANNEL_MCP_SESSIONS.pop(session, None)
|
|
4987
5059
|
|
|
@@ -5004,6 +5076,8 @@ def handle_channel_mcp_post(handler: BaseHTTPRequestHandler, path: str, body: di
|
|
|
5004
5076
|
with _CHANNEL_MCP_LOCK:
|
|
5005
5077
|
if session and session in _CHANNEL_MCP_SESSIONS:
|
|
5006
5078
|
_CHANNEL_MCP_SESSIONS[session]["initialized"] = True
|
|
5079
|
+
with _CHAT_CONDITION:
|
|
5080
|
+
_CHAT_CONDITION.notify_all()
|
|
5007
5081
|
write_json(
|
|
5008
5082
|
handler,
|
|
5009
5083
|
{
|
|
@@ -13767,6 +13841,7 @@ def write_channel_mcp_config() -> Path:
|
|
|
13767
13841
|
os.chmod(CHANNEL_MCP_CONFIG, 0o600)
|
|
13768
13842
|
except Exception:
|
|
13769
13843
|
pass
|
|
13844
|
+
_channel_mcp_ensure_cursor_initialized()
|
|
13770
13845
|
return CHANNEL_MCP_CONFIG
|
|
13771
13846
|
|
|
13772
13847
|
|
|
@@ -14326,9 +14401,11 @@ def run_mcp_stdio_proxy(server_name: str, server_config_path: Path) -> int:
|
|
|
14326
14401
|
try:
|
|
14327
14402
|
server = json.loads(server_config_path.read_text(encoding="utf-8"))
|
|
14328
14403
|
except Exception as exc:
|
|
14404
|
+
router_log("ERROR", f"mcp_proxy_config_read_failed server={server_name} error={type(exc).__name__}: {exc}")
|
|
14329
14405
|
print(f"claude-any mcp-proxy: cannot read server config: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
|
|
14330
14406
|
return 2
|
|
14331
14407
|
if not isinstance(server, dict) or not _mcp_server_is_stdio(server):
|
|
14408
|
+
router_log("ERROR", f"mcp_proxy_invalid_config server={server_name}")
|
|
14332
14409
|
print("claude-any mcp-proxy: server config is not a stdio MCP server", file=sys.stderr, flush=True)
|
|
14333
14410
|
return 2
|
|
14334
14411
|
command = str(server.get("command") or "").strip()
|
|
@@ -14350,6 +14427,7 @@ def run_mcp_stdio_proxy(server_name: str, server_config_path: Path) -> int:
|
|
|
14350
14427
|
bufsize=0,
|
|
14351
14428
|
)
|
|
14352
14429
|
except Exception as exc:
|
|
14430
|
+
router_log("ERROR", f"mcp_proxy_start_failed server={server_name} command={command} error={type(exc).__name__}: {exc}")
|
|
14353
14431
|
print(f"claude-any mcp-proxy: failed to start {command}: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
|
|
14354
14432
|
return 127
|
|
14355
14433
|
router_log("INFO", f"mcp_proxy_started server={server_name} command={command}")
|
|
@@ -14366,7 +14444,8 @@ def run_mcp_stdio_proxy(server_name: str, server_config_path: Path) -> int:
|
|
|
14366
14444
|
sys.stdout.buffer.write(chunk)
|
|
14367
14445
|
sys.stdout.buffer.flush()
|
|
14368
14446
|
rc = proc.wait()
|
|
14369
|
-
|
|
14447
|
+
level = "INFO" if rc == 0 else "WARN"
|
|
14448
|
+
router_log(level, f"mcp_proxy_exited server={server_name} rc={rc}")
|
|
14370
14449
|
return rc
|
|
14371
14450
|
finally:
|
|
14372
14451
|
if proc.poll() is None:
|
package/package.json
CHANGED