Skip to content

Commit bc53454

Browse files
committed
Stabilize Claude MCP and lighten runner
1 parent ec68026 commit bc53454

7 files changed

Lines changed: 30 additions & 12 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ SAGE is available through both Python and npm entry points. The npm package dele
3535
| MCP Registry | [`io.github.PsYcGoD/sage`](https://registry.modelcontextprotocol.io/) | Official registry entry |
3636
| Glama | [`PsYcGoD/sage`](https://glama.ai/mcp/servers/PsYcGoD/sage) | Listed MCP server |
3737

38-
MCP stdio servers exit after 10 seconds of inactivity by default. Set `SAGE_MCP_IDLE_TIMEOUT_SECONDS` to a higher value if a client needs a longer idle window.
38+
MCP stdio servers exit after 5 minutes of inactivity by default. The ML daemon still sleeps after short idle windows; MCP stays alive longer because clients such as Claude Code keep stdio servers open between tool calls. Set `SAGE_MCP_IDLE_TIMEOUT_SECONDS` for a stricter or longer local policy.
3939

4040
### Proof at Full Context
4141

js/src/mcp/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '@modelcontextprotocol/sdk/types.js';
88
import { TOOLS, handleToolCall } from './tools.js';
99

10-
const DEFAULT_IDLE_TIMEOUT_MS = 10_000;
10+
const DEFAULT_IDLE_TIMEOUT_MS = 300_000;
1111
const MIN_IDLE_TIMEOUT_MS = 10_000;
1212

1313
function getIdleTimeoutMs(): number {

src/sage/mcp/server.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
PROTOCOL_VERSION = "2024-11-05"
4141
SERVER_INFO = {"name": "sage", "version": "2.0.4"}
4242
COMMAND_TOOL_NAMES = {"sage_run_command"}
43-
DEFAULT_IDLE_TIMEOUT_SECONDS = 10
43+
DEFAULT_IDLE_TIMEOUT_SECONDS = 300
4444
MIN_IDLE_TIMEOUT_SECONDS = 10
4545

4646

@@ -49,8 +49,10 @@ def _mcp_idle_timeout() -> int:
4949
5050
MCP clients usually restart stdio servers on demand, so idle MCP servers
5151
should not sit around forever when an AI client crashes or forgets to close
52-
stdin. The default matches SAGE's low-footprint daemon policy and can be
53-
raised by setting SAGE_MCP_IDLE_TIMEOUT_SECONDS.
52+
stdin. MCP clients such as Claude Code often keep a stdio server open
53+
between tool calls, so the default must be long enough not to look like a
54+
flaky server while still cleaning truly abandoned processes. Set
55+
SAGE_MCP_IDLE_TIMEOUT_SECONDS for stricter or longer local policy.
5456
"""
5557
raw = os.getenv("SAGE_MCP_IDLE_TIMEOUT_SECONDS", "").strip()
5658
if not raw:

src/sage/ml/family_model.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
from datetime import datetime, timezone
1313
from pathlib import Path
1414
from typing import Any
15+
import warnings
1516

1617
import joblib
1718
import pandas as pd
1819
from sklearn.ensemble import ExtraTreesClassifier, GradientBoostingClassifier, RandomForestClassifier, VotingClassifier
20+
from sklearn.exceptions import InconsistentVersionWarning
1921
from sklearn.metrics import accuracy_score
2022
from sklearn.pipeline import Pipeline
2123
from sklearn.preprocessing import StandardScaler
@@ -242,7 +244,9 @@ def _load_model(self, path: Path) -> dict[str, Any] | None:
242244
return None
243245

244246
try:
245-
package = joblib.load(path)
247+
with warnings.catch_warnings():
248+
warnings.simplefilter("ignore", InconsistentVersionWarning)
249+
package = joblib.load(path)
246250
if package.get("version") != MODEL_VERSION:
247251
return None
248252
self._cache[cache_key] = package

src/sage/ml/model.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
from datetime import datetime, timezone
77
from pathlib import Path
88
from typing import Any
9+
import warnings
910

1011
import joblib
1112
import pandas as pd
1213
from sklearn.ensemble import ExtraTreesClassifier, GradientBoostingClassifier, HistGradientBoostingClassifier, RandomForestClassifier, VotingClassifier
14+
from sklearn.exceptions import InconsistentVersionWarning
1315
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score
1416
from sklearn.model_selection import train_test_split
1517
from sklearn.pipeline import Pipeline
@@ -188,7 +190,9 @@ def load(self) -> dict[str, Any] | None:
188190
if not self.model_path.exists():
189191
return None
190192
try:
191-
package = joblib.load(self.model_path)
193+
with warnings.catch_warnings():
194+
warnings.simplefilter("ignore", InconsistentVersionWarning)
195+
package = joblib.load(self.model_path)
192196
except Exception:
193197
return None
194198
if package.get("version") != MODEL_VERSION:

src/sage/runner.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,15 @@ def enqueue_stream(stream, stream_name: str) -> None:
326326
)
327327

328328
agent_results = []
329-
if os.environ.get("SAGE_DISABLE_AGENTS") != "1":
329+
agents_enabled = (
330+
os.environ.get("SAGE_ENABLE_AGENTS") == "1"
331+
or (
332+
returncode != 0
333+
and os.environ.get("SAGE_DISABLE_FAILURE_AGENTS") != "1"
334+
and os.environ.get("SAGE_DISABLE_AGENTS") != "1"
335+
)
336+
)
337+
if agents_enabled:
330338
try:
331339
from .agents import execute_agents_for_run
332340

@@ -429,12 +437,12 @@ def _run_agents_background():
429437
if result['token_savings'] > 0:
430438
print(f"[sage] context: saved {result['token_savings']} tokens ({result['compression_ratio']} compression)")
431439

432-
if os.environ.get("SAGE_DISABLE_AGENTS") != "1":
440+
if agents_enabled:
433441
from .agents.registry import select_agents_for_command
434442
specs = select_agents_for_command(command_text)
435443
if specs:
436444
agent_names = ", ".join(s.name for s in specs)
437-
print(f"[sage] agents: {len(specs)} completed ({agent_names})")
445+
print(f"[sage] agents: {len(specs)} queued ({agent_names})")
438446

439447
if not suppress_summary:
440448
print("[sage] summary:")

tests/test_mcp_sage_tools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def test_mcp_run_command_is_opt_in(monkeypatch):
3434

3535
def test_mcp_idle_timeout_is_configurable_and_clamped(monkeypatch):
3636
monkeypatch.delenv("SAGE_MCP_IDLE_TIMEOUT_SECONDS", raising=False)
37-
assert _mcp_idle_timeout() == 10
37+
assert _mcp_idle_timeout() == 300
3838

3939
monkeypatch.setenv("SAGE_MCP_IDLE_TIMEOUT_SECONDS", "2")
4040
assert _mcp_idle_timeout() == 10
@@ -43,7 +43,7 @@ def test_mcp_idle_timeout_is_configurable_and_clamped(monkeypatch):
4343
assert _mcp_idle_timeout() == 45
4444

4545
monkeypatch.setenv("SAGE_MCP_IDLE_TIMEOUT_SECONDS", "bad")
46-
assert _mcp_idle_timeout() == 10
46+
assert _mcp_idle_timeout() == 300
4747

4848

4949
def test_external_mcp_missing_command_fails_fast():

0 commit comments

Comments
 (0)