token-runway 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,6 @@
1
+ """Top-level package for Token Runway."""
2
+
3
+ from token_runway.cli import __version__, main
4
+ from token_runway.server import TokenRunwayServer
5
+
6
+ __all__ = ["__version__", "main", "TokenRunwayServer"]
@@ -0,0 +1,7 @@
1
+ """Module entrypoint for python -m token_runway."""
2
+
3
+ import sys
4
+ from token_runway.cli import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main())
token_runway/cli.py ADDED
@@ -0,0 +1,179 @@
1
+ """Command-line interface for Token Runway."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import shutil
7
+ import sys
8
+ from typing import List, Optional
9
+
10
+ from token_runway.injector import get_ide_config_targets, inject_into_file
11
+ from token_runway.server import TokenRunwayServer
12
+ from token_runway.sources import fetch_all_runways
13
+ from token_runway.sources.claude import find_claude_token
14
+
15
+ __version__ = "0.1.0"
16
+
17
+
18
+ def format_status_badge(status: str) -> str:
19
+ """Simple ANSI terminal color badge."""
20
+ if status == "critical":
21
+ return "\033[91m● CRITICAL\033[0m"
22
+ if status == "warning":
23
+ return "\033[93m▲ WARNING\033[0m"
24
+ if status == "ok":
25
+ return "\033[92m✔ OK\033[0m"
26
+ if status == "unlimited":
27
+ return "\033[96m∞ UNLIMITED\033[0m"
28
+ if status == "not_on_plan":
29
+ return "\033[90m- NOT ON PLAN\033[0m"
30
+ return status.upper()
31
+
32
+
33
+ def cmd_check() -> int:
34
+ """Print a terminal table of active token quotas across all providers."""
35
+ print("\n\033[1m=== Token Runway: Live Quota Telemetry ===\033[0m\n")
36
+ readings = fetch_all_runways()
37
+
38
+ for key, r in readings.items():
39
+ title = f"\033[1;34m{r.display_name}\033[0m"
40
+ if r.plan:
41
+ title += f" \033[90m(Plan: {r.plan})\033[0m"
42
+ print(title)
43
+
44
+ if not r.available:
45
+ print(f" \033[90mUnavailable: {r.error_message}\033[0m\n")
46
+ continue
47
+
48
+ if not r.windows:
49
+ print(" \033[90mNo active quota windows reported.\033[0m\n")
50
+ continue
51
+
52
+ for w in r.windows:
53
+ badge = format_status_badge(w.status)
54
+ line = f" • {w.name:<26} {badge:<22}"
55
+
56
+ if w.used_pct is not None:
57
+ line += f" Used: {w.used_pct:>3}%"
58
+ if w.remaining_pct is not None:
59
+ line += f" (Remaining: {w.remaining_pct:>5}%)"
60
+ if w.remaining_count is not None:
61
+ if w.total_entitlement is not None:
62
+ line += f" [{w.remaining_count}/{w.total_entitlement}]"
63
+ else:
64
+ line += f" [{w.remaining_count}]"
65
+ if w.resets_in_minutes is not None:
66
+ if w.resets_in_minutes < 60:
67
+ line += f" (Resets in {w.resets_in_minutes}m)"
68
+ elif w.resets_in_minutes < 1440:
69
+ line += f" (Resets in {w.resets_in_minutes // 60}h {w.resets_in_minutes % 60}m)"
70
+ else:
71
+ line += f" (Resets in {w.resets_in_minutes // 1440}d {(w.resets_in_minutes % 1440) // 60}h)"
72
+
73
+ print(line)
74
+ print()
75
+
76
+ return 0
77
+
78
+
79
+ def cmd_doctor() -> int:
80
+ """Diagnose local CLI tools, credentials, and network connectivity."""
81
+ print("\n\033[1m=== Token Runway: Doctor ===\033[0m\n")
82
+
83
+ # 1. Claude
84
+ token = find_claude_token()
85
+ if token:
86
+ print(" \033[92m✔ Claude Code:\033[0m Credentials found")
87
+ else:
88
+ print(" \033[93m▲ Claude Code:\033[0m No credentials found in ~/.claude/.credentials.json")
89
+
90
+ # 2. Antigravity CLI (`agy`)
91
+ if shutil.which("agy"):
92
+ print(" \033[92m✔ Antigravity CLI (`agy`):\033[0m Found in PATH")
93
+ else:
94
+ print(" \033[90m- Antigravity CLI (`agy`):\033[0m Not installed or not in PATH")
95
+
96
+ # 3. GitHub CLI (`gh`)
97
+ if shutil.which("gh"):
98
+ print(" \033[92m✔ GitHub CLI (`gh`):\033[0m Found in PATH")
99
+ else:
100
+ print(" \033[90m- GitHub CLI (`gh`):\033[0m Not installed or not in PATH")
101
+
102
+ # 4. Google Colab CLI (`colab`)
103
+ if shutil.which("colab"):
104
+ print(" \033[92m✔ Google Colab CLI (`colab`):\033[0m Found in PATH")
105
+ else:
106
+ print(" \033[90m- Google Colab CLI (`colab`):\033[0m Not installed or not in PATH")
107
+
108
+ print("\nChecking live endpoints...")
109
+ readings = fetch_all_runways()
110
+ for name, r in readings.items():
111
+ if r.available:
112
+ print(f" \033[92m✔ {r.display_name}:\033[0m Connected ({len(r.windows)} quota windows active)")
113
+ else:
114
+ print(f" \033[93m▲ {r.display_name}:\033[0m {r.error_message}")
115
+
116
+ print("\nDoctor check completed.\n")
117
+ return 0
118
+
119
+
120
+ def cmd_inject(mode: str = "auto") -> int:
121
+ """Inject Token Runway into detected AI IDE configuration files."""
122
+ print("\n\033[1m=== Token Runway: IDE Injector ===\033[0m\n")
123
+ targets = get_ide_config_targets()
124
+ injected_count = 0
125
+
126
+ for ide_name, path in targets:
127
+ try:
128
+ inject_into_file(path, mode=mode)
129
+ print(f" \033[92m✔ {ide_name}:\033[0m Injected configuration into {path}")
130
+ injected_count += 1
131
+ except Exception as e:
132
+ print(f" \033[91m✖ {ide_name}:\033[0m Failed to update {path}: {e}")
133
+
134
+ print(f"\nInjected token-runway entry into {injected_count} IDE configuration(s).\n")
135
+ return 0
136
+
137
+
138
+ def main(argv: Optional[List[str]] = None) -> int:
139
+ parser = argparse.ArgumentParser(
140
+ prog="token-runway",
141
+ description="Model Context Protocol (MCP) server for real-time token runway & quota awareness.",
142
+ )
143
+ parser.add_argument("-V", "--version", action="version", version=f"token-runway {__version__}")
144
+
145
+ subparsers = parser.add_subparsers(dest="command")
146
+
147
+ # Subcommands
148
+ subparsers.add_parser("run", help="Run stdio MCP server (default when no command given).")
149
+ subparsers.add_parser("check", help="Print terminal table of active quotas.")
150
+ subparsers.add_parser("doctor", help="Check local tools, credentials, and API connectivity.")
151
+
152
+ inject_parser = subparsers.add_parser("inject", help="Auto-inject MCP server into Claude Desktop, Cursor, etc.")
153
+ inject_parser.add_argument(
154
+ "--mode",
155
+ choices=["auto", "uvx", "pip"],
156
+ default="auto",
157
+ help="Command launch mode in IDE configs (default: auto).",
158
+ )
159
+
160
+ args = parser.parse_args(argv)
161
+
162
+ if args.command == "check":
163
+ return cmd_check()
164
+ elif args.command == "doctor":
165
+ return cmd_doctor()
166
+ elif args.command == "inject":
167
+ return cmd_inject(mode=args.mode)
168
+ else:
169
+ # Default behavior: run MCP stdio server
170
+ server = TokenRunwayServer()
171
+ try:
172
+ server.run_stdio()
173
+ return 0
174
+ except (KeyboardInterrupt, BrokenPipeError):
175
+ return 0
176
+
177
+
178
+ if __name__ == "__main__":
179
+ sys.exit(main())
@@ -0,0 +1,9 @@
1
+ """Config injector utilities for Token Runway."""
2
+
3
+ from token_runway.injector.config_injector import (
4
+ build_mcp_entry,
5
+ get_ide_config_targets,
6
+ inject_into_file,
7
+ )
8
+
9
+ __all__ = ["build_mcp_entry", "get_ide_config_targets", "inject_into_file"]
@@ -0,0 +1,80 @@
1
+ """Multi-IDE configuration detector and one-click injector for Token Runway."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import shutil
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Tuple
11
+
12
+
13
+ def get_ide_config_targets() -> List[Tuple[str, Path]]:
14
+ """Return a list of known (IDE_Name, config_path) tuples for the current OS."""
15
+ targets = []
16
+ home = Path.home()
17
+
18
+ # 1. Claude Desktop
19
+ if sys.platform == "darwin":
20
+ targets.append(("Claude Desktop", home / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"))
21
+ elif sys.platform == "win32":
22
+ appdata = os.environ.get("APPDATA")
23
+ if appdata:
24
+ targets.append(("Claude Desktop", Path(appdata) / "Claude" / "claude_desktop_config.json"))
25
+ else:
26
+ targets.append(("Claude Desktop", home / ".config" / "Claude" / "claude_desktop_config.json"))
27
+
28
+ # 2. Cursor
29
+ targets.append(("Cursor", home / ".cursor" / "mcp.json"))
30
+
31
+ # 3. Claude Code
32
+ targets.append(("Claude Code", home / ".claude" / "mcp.json"))
33
+
34
+ # 4. Antigravity
35
+ targets.append(("Antigravity", home / ".gemini" / "antigravity-cli" / "mcp_config.json"))
36
+
37
+ # 5. Windsurf
38
+ targets.append(("Windsurf", home / ".codeium" / "windsurf" / "mcp_config.json"))
39
+
40
+ return targets
41
+
42
+
43
+ def build_mcp_entry(mode: str = "auto") -> Dict[str, Any]:
44
+ """Generate the MCP server entry dictionary."""
45
+ if mode == "uvx" or (mode == "auto" and shutil.which("uvx")):
46
+ return {
47
+ "command": "uvx",
48
+ "args": ["token-runway"],
49
+ "env": {"PYTHONUNBUFFERED": "1"},
50
+ }
51
+ else:
52
+ python_exe = sys.executable
53
+ return {
54
+ "command": python_exe,
55
+ "args": ["-m", "token_runway"],
56
+ "env": {"PYTHONUNBUFFERED": "1"},
57
+ }
58
+
59
+
60
+ def inject_into_file(path: Path, entry_name: str = "token-runway", mode: str = "auto") -> bool:
61
+ """Inject or update the token-runway MCP server definition into target config."""
62
+ path.parent.mkdir(parents=True, exist_ok=True)
63
+
64
+ data: Dict[str, Any] = {}
65
+ if path.is_file():
66
+ try:
67
+ data = json.loads(path.read_text(encoding="utf-8"))
68
+ except Exception:
69
+ data = {}
70
+
71
+ # Root key is typically "mcpServers"
72
+ if "mcpServers" not in data or not isinstance(data["mcpServers"], dict):
73
+ data["mcpServers"] = {}
74
+
75
+ data["mcpServers"][entry_name] = build_mcp_entry(mode)
76
+
77
+ tmp_path = path.with_suffix(".tmp")
78
+ tmp_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
79
+ tmp_path.replace(path)
80
+ return True
@@ -0,0 +1,5 @@
1
+ """MCP server implementation for Token Runway."""
2
+
3
+ from token_runway.server.server import TokenRunwayServer
4
+
5
+ __all__ = ["TokenRunwayServer"]
@@ -0,0 +1,84 @@
1
+ """Meta-cognitive reasoning engine for assessing action feasibility against active quotas."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from token_runway.sources.base import ProviderReading
8
+
9
+
10
+ def evaluate_action_feasibility(
11
+ readings: Dict[str, ProviderReading],
12
+ action_type: str = "general",
13
+ intensity: str = "medium", # "low", "medium", "high"
14
+ target_provider: Optional[str] = None,
15
+ ) -> Dict[str, Any]:
16
+ """Evaluate whether an agent should proceed with a resource-intensive action."""
17
+ action_type = action_type.lower()
18
+ intensity = intensity.lower()
19
+
20
+ # Identify primary active provider constraints
21
+ providers_to_check = [target_provider] if target_provider and target_provider in readings else list(readings.keys())
22
+
23
+ highest_used_pct = 0
24
+ most_constrained_provider = None
25
+ most_constrained_window = None
26
+ mins_to_reset = None
27
+
28
+ for p_name in providers_to_check:
29
+ reading = readings.get(p_name)
30
+ if not reading or not reading.available:
31
+ continue
32
+ for w in reading.windows:
33
+ if w.used_pct is not None and w.used_pct > highest_used_pct:
34
+ highest_used_pct = w.used_pct
35
+ most_constrained_provider = reading.display_name
36
+ most_constrained_window = w.name
37
+ mins_to_reset = w.resets_in_minutes
38
+
39
+ recommendation = "proceed"
40
+ strategies: List[str] = []
41
+ reasoning: str = ""
42
+
43
+ if highest_used_pct >= 95:
44
+ recommendation = "pause_or_downgrade"
45
+ reasoning = (
46
+ f"Quota is critically constrained ({highest_used_pct}% used on {most_constrained_provider} {most_constrained_window})."
47
+ )
48
+ if mins_to_reset is not None and mins_to_reset <= 30:
49
+ strategies.append(
50
+ f"Window resets in ~{mins_to_reset} minutes. Defer heavy work and perform lightweight local tasks."
51
+ )
52
+ strategies.append("Prohibit parallel subagent fan-outs; execute one step at a time.")
53
+ strategies.append("Read narrow line ranges (under 100 lines) instead of loading full files into context.")
54
+ strategies.append("Route subagent prompts to lightweight models (Flash/Haiku) if model selection is supported.")
55
+ elif highest_used_pct >= 80:
56
+ if intensity == "high":
57
+ recommendation = "downgrade_strategy"
58
+ reasoning = (
59
+ f"Quota headroom is tight ({highest_used_pct}% used on {most_constrained_provider}). High-intensity operations risk lockouts."
60
+ )
61
+ strategies.append("Downgrade multi-agent broad searches to targeted ripgrep / grep commands.")
62
+ strategies.append("Summarize long context before launching further turns.")
63
+ else:
64
+ recommendation = "proceed_with_caution"
65
+ reasoning = f"Headroom is moderate ({highest_used_pct}% used). Standard actions permitted."
66
+ strategies.append("Monitor token usage after this turn.")
67
+ else:
68
+ recommendation = "proceed"
69
+ reasoning = f"Healthy headroom ({highest_used_pct}% maximum utilization across active providers)."
70
+ strategies.append("Full operational capacity available for multi-agent workflows and deep research.")
71
+
72
+ return {
73
+ "verdict": recommendation,
74
+ "max_utilization_pct": highest_used_pct,
75
+ "bottleneck": {
76
+ "provider": most_constrained_provider,
77
+ "window": most_constrained_window,
78
+ "resets_in_minutes": mins_to_reset,
79
+ }
80
+ if most_constrained_provider
81
+ else None,
82
+ "analysis": reasoning,
83
+ "preservation_strategies": strategies,
84
+ }
@@ -0,0 +1,38 @@
1
+ """JSON-RPC 2.0 protocol definitions and message envelopes for MCP."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from typing import Any, Dict, Optional, Union
8
+
9
+ PARSE_ERROR = -32700
10
+ INVALID_REQUEST = -32600
11
+ METHOD_NOT_FOUND = -32601
12
+ INVALID_PARAMS = -32602
13
+ INTERNAL_ERROR = -32603
14
+
15
+ MCP_PROTOCOL_VERSION = "2024-11-05"
16
+
17
+
18
+ def make_response(req_id: Union[int, str, None], result: Any) -> Dict[str, Any]:
19
+ """Construct a JSON-RPC 2.0 success response envelope."""
20
+ return {"jsonrpc": "2.0", "id": req_id, "result": result}
21
+
22
+
23
+ def make_error(
24
+ req_id: Union[int, str, None], code: int, message: str, data: Optional[Any] = None
25
+ ) -> Dict[str, Any]:
26
+ """Construct a JSON-RPC 2.0 error response envelope."""
27
+ err: Dict[str, Any] = {"code": code, "message": message}
28
+ if data is not None:
29
+ err["data"] = data
30
+ return {"jsonrpc": "2.0", "id": req_id, "error": err}
31
+
32
+
33
+ def write_message(msg: Dict[str, Any], out_stream=None) -> None:
34
+ """Serialize and write a JSON-RPC message line followed by a newline."""
35
+ stream = out_stream or sys.stdout
36
+ line = json.dumps(msg, ensure_ascii=False)
37
+ stream.write(line + "\n")
38
+ stream.flush()
@@ -0,0 +1,255 @@
1
+ """Stdio-based Model Context Protocol (MCP) server for Token Runway."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ import time
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from token_runway.server.advice import evaluate_action_feasibility
11
+ from token_runway.server.protocol import (
12
+ INTERNAL_ERROR,
13
+ INVALID_PARAMS,
14
+ INVALID_REQUEST,
15
+ MCP_PROTOCOL_VERSION,
16
+ METHOD_NOT_FOUND,
17
+ make_error,
18
+ make_response,
19
+ write_message,
20
+ )
21
+ from token_runway.sources import fetch_all_runways
22
+ from token_runway.sources.base import ProviderReading
23
+
24
+
25
+ class TokenRunwayServer:
26
+ """Zero-dependency stdio MCP server exposing token quotas and advice."""
27
+
28
+ def __init__(self, cache_ttl: float = 30.0):
29
+ self.cache_ttl = cache_ttl
30
+ self._cached_readings: Optional[Dict[str, ProviderReading]] = None
31
+ self._cache_time: float = 0.0
32
+
33
+ def get_readings(self, force_refresh: bool = False) -> Dict[str, ProviderReading]:
34
+ now = time.monotonic()
35
+ if force_refresh or self._cached_readings is None or (now - self._cache_time) > self.cache_ttl:
36
+ self._cached_readings = fetch_all_runways()
37
+ self._cache_time = now
38
+ return self._cached_readings
39
+
40
+ def handle_request(self, req: Dict[str, Any]) -> Optional[Dict[str, Any]]:
41
+ if not isinstance(req, dict) or req.get("jsonrpc") != "2.0":
42
+ return make_error(req.get("id") if isinstance(req, dict) else None, INVALID_REQUEST, "Invalid JSON-RPC request")
43
+
44
+ method = req.get("method")
45
+ req_id = req.get("id")
46
+ params = req.get("params") or {}
47
+
48
+ # Notifications (no id)
49
+ if req_id is None:
50
+ if method == "notifications/initialized":
51
+ return None
52
+ return None
53
+
54
+ # RPC Methods
55
+ if method == "initialize":
56
+ return make_response(
57
+ req_id,
58
+ {
59
+ "protocolVersion": MCP_PROTOCOL_VERSION,
60
+ "capabilities": {
61
+ "tools": {},
62
+ "resources": {},
63
+ "prompts": {},
64
+ },
65
+ "serverInfo": {
66
+ "name": "token-runway",
67
+ "version": "0.1.0",
68
+ },
69
+ },
70
+ )
71
+
72
+ if method == "ping":
73
+ return make_response(req_id, {})
74
+
75
+ if method == "tools/list":
76
+ return make_response(req_id, {"tools": self._list_tools()})
77
+
78
+ if method == "tools/call":
79
+ return self._call_tool(req_id, params)
80
+
81
+ if method == "resources/list":
82
+ return make_response(req_id, {"resources": self._list_resources()})
83
+
84
+ if method == "resources/read":
85
+ return self._read_resource(req_id, params)
86
+
87
+ if method == "prompts/list":
88
+ return make_response(req_id, {"prompts": self._list_prompts()})
89
+
90
+ if method == "prompts/get":
91
+ return self._get_prompt(req_id, params)
92
+
93
+ return make_error(req_id, METHOD_NOT_FOUND, f"Method not found: {method}")
94
+
95
+ def _list_tools(self) -> List[Dict[str, Any]]:
96
+ return [
97
+ {
98
+ "name": "check_runway",
99
+ "description": (
100
+ "Inspect current token quotas, utilization percentages, remaining capacity, "
101
+ "and reset countdowns across Claude, Gemini, Antigravity, GitHub Copilot, and Colab."
102
+ ),
103
+ "inputSchema": {
104
+ "type": "object",
105
+ "properties": {
106
+ "refresh": {
107
+ "type": "boolean",
108
+ "description": "Bypass cache and force an immediate re-fetch from all provider CLIs/APIs.",
109
+ "default": False,
110
+ },
111
+ "provider": {
112
+ "type": "string",
113
+ "description": "Optional specific provider to filter ('claude', 'gemini', 'antigravity', 'copilot', 'colab').",
114
+ },
115
+ },
116
+ },
117
+ },
118
+ {
119
+ "name": "assess_action",
120
+ "description": (
121
+ "Meta-cognitive evaluation of whether a proposed action (e.g. running multiple parallel subagents, "
122
+ "reading massive files, or continuous test loops) is justified given current remaining quotas and reset timers."
123
+ ),
124
+ "inputSchema": {
125
+ "type": "object",
126
+ "properties": {
127
+ "action_type": {
128
+ "type": "string",
129
+ "description": "Type of action planned ('subagent_fanout', 'large_file_dump', 'deep_research', 'targeted_edit', 'continuous_loop').",
130
+ "default": "general",
131
+ },
132
+ "intensity": {
133
+ "type": "string",
134
+ "enum": ["low", "medium", "high"],
135
+ "description": "Estimated token burn intensity of the action.",
136
+ "default": "medium",
137
+ },
138
+ "provider": {
139
+ "type": "string",
140
+ "description": "Target provider whose limits you want to evaluate against (optional).",
141
+ },
142
+ },
143
+ },
144
+ },
145
+ ]
146
+
147
+ def _call_tool(self, req_id: Any, params: Dict[str, Any]) -> Dict[str, Any]:
148
+ name = params.get("name")
149
+ args = params.get("arguments") or {}
150
+
151
+ if name == "check_runway":
152
+ force = bool(args.get("refresh", False))
153
+ provider_filter = args.get("provider")
154
+ readings = self.get_readings(force_refresh=force)
155
+
156
+ if provider_filter and provider_filter.lower() in readings:
157
+ data = {provider_filter.lower(): readings[provider_filter.lower()].to_dict()}
158
+ else:
159
+ data = {k: v.to_dict() for k, v in readings.items()}
160
+
161
+ text = json.dumps(data, indent=2)
162
+ return make_response(req_id, {"content": [{"type": "text", "text": text}]})
163
+
164
+ if name == "assess_action":
165
+ action_type = args.get("action_type", "general")
166
+ intensity = args.get("intensity", "medium")
167
+ provider = args.get("provider")
168
+ readings = self.get_readings()
169
+ assessment = evaluate_action_feasibility(readings, action_type, intensity, provider)
170
+ text = json.dumps(assessment, indent=2)
171
+ return make_response(req_id, {"content": [{"type": "text", "text": text}]})
172
+
173
+ return make_error(req_id, INVALID_PARAMS, f"Unknown tool name: {name}")
174
+
175
+ def _list_resources(self) -> List[Dict[str, Any]]:
176
+ return [
177
+ {
178
+ "uri": "runway://status",
179
+ "name": "Live Token Runway Telemetry",
180
+ "description": "Real-time snapshot of remaining limits and countdowns across all providers.",
181
+ "mimeType": "application/json",
182
+ }
183
+ ]
184
+
185
+ def _read_resource(self, req_id: Any, params: Dict[str, Any]) -> Dict[str, Any]:
186
+ uri = params.get("uri")
187
+ if uri == "runway://status":
188
+ readings = self.get_readings()
189
+ data = {k: v.to_dict() for k, v in readings.items()}
190
+ return make_response(
191
+ req_id,
192
+ {
193
+ "contents": [
194
+ {
195
+ "uri": uri,
196
+ "mimeType": "application/json",
197
+ "text": json.dumps(data, indent=2),
198
+ }
199
+ ]
200
+ },
201
+ )
202
+ return make_error(req_id, INVALID_PARAMS, f"Unknown resource URI: {uri}")
203
+
204
+ def _list_prompts(self) -> List[Dict[str, Any]]:
205
+ return [
206
+ {
207
+ "name": "runway_governance",
208
+ "description": "Self-governance guidelines for operating under token rate-limits and quotas.",
209
+ }
210
+ ]
211
+
212
+ def _get_prompt(self, req_id: Any, params: Dict[str, Any]) -> Dict[str, Any]:
213
+ prompt_name = params.get("name")
214
+ if prompt_name == "runway_governance":
215
+ prompt_text = (
216
+ "You are an AI agent equipped with real-time awareness of your token quotas via Token Runway.\n\n"
217
+ "Before initiating resource-heavy operations (e.g. fanning out multiple subagents, dumping whole files >500 lines, "
218
+ "or running unbounded search loops), call `assess_action` or `check_runway`.\n\n"
219
+ "- If utilization is >=95%: Switch to quota-preserving strategies (surgical line edits, targeted ripgrep, sequential steps).\n"
220
+ "- If reset is within 20 minutes and quota is high: Defer heavy work and perform lightweight verification.\n"
221
+ "- If utilization is <80%: You have full operational headroom."
222
+ )
223
+ return make_response(
224
+ req_id,
225
+ {
226
+ "description": "Runway governance instructions",
227
+ "messages": [
228
+ {
229
+ "role": "user",
230
+ "content": {"type": "text", "text": prompt_text},
231
+ }
232
+ ],
233
+ },
234
+ )
235
+ return make_error(req_id, INVALID_PARAMS, f"Unknown prompt: {prompt_name}")
236
+
237
+ def run_stdio(self) -> None:
238
+ """Run the MCP server event loop over standard input and standard output."""
239
+ for line in sys.stdin:
240
+ line = line.strip()
241
+ if not line:
242
+ continue
243
+ try:
244
+ msg = json.loads(line)
245
+ except Exception as e:
246
+ write_message(make_error(None, -32700, f"Parse error: {e}"))
247
+ continue
248
+
249
+ try:
250
+ resp = self.handle_request(msg)
251
+ if resp is not None:
252
+ write_message(resp)
253
+ except Exception as e:
254
+ req_id = msg.get("id") if isinstance(msg, dict) else None
255
+ write_message(make_error(req_id, INTERNAL_ERROR, f"Internal server error: {e}"))