JobForecaster-Agent / mcp_server.py
lixiaowww
Initial commit: forecaster agent with zero-cost GitHub Pages deploy.
2fc729c
Raw
History Blame Contribute Delete
3.16 kB
#!/usr/bin/env python3
"""Read-only MCP server for forecaster-agent (Phase 3).
Exposes calibration scoreboard, OOD assessment, job search, and open predictions
via the Harness `services/` read model. No write tools; no LLM calls at runtime.
Run (stdio — for Cursor / Claude Desktop):
python mcp_server.py
Cursor config example: see docs/MCP.md
"""
from __future__ import annotations
import sys
from pathlib import Path
# Bootstrap project root before any local imports
_ROOT = Path(__file__).resolve().parent
if str(_ROOT) not in sys.path:
sys.path.insert(0, str(_ROOT))
sys.path = [p for p in sys.path if p != "/home/sean"]
from mcp.server.fastmcp import FastMCP
from services.mcp_handlers import (
INDUSTRIES,
handle_get_calibration_scoreboard,
handle_get_ood_assessment,
handle_list_open_predictions,
handle_search_jobs,
)
mcp = FastMCP(
"forecaster-agent",
instructions=(
"Read-only access to the forecaster-agent job radar and calibration data. "
"Use get_ood_assessment when extrapolating beyond historical tech transitions. "
"All outputs are speculative — not financial or career advice."
),
)
@mcp.tool(
name="get_calibration_scoreboard",
description=(
"Return Brier calibration metrics: total/open/resolved counts, mean Brier, "
"and 10-decile reliability curve. Lower Brier is better (0=perfect)."
),
)
def get_calibration_scoreboard() -> dict:
return handle_get_calibration_scoreboard()
@mcp.tool(
name="get_ood_assessment",
description=(
"Assess whether the current AI×economy scenario is out-of-distribution vs "
"15+ historical tech transitions. Returns Mahalanobis distance, nearest "
"historical regime, conditional rules, and prompt_context. "
"Optional scenario_json: JSON object overriding evolution scenario variables "
"(augmentation_ratio, demand_elasticity, oring_leverage, skill_distance, "
"diffusion_years, absorbing_sector, productivity_capture, task_frontier_open)."
),
)
def get_ood_assessment(
scenario_json: str | None = None,
n_bootstrap: int | None = None,
) -> dict:
return handle_get_ood_assessment(scenario_json=scenario_json, n_bootstrap=n_bootstrap)
@mcp.tool(
name="search_jobs",
description=(
"Hybrid RAG job search: ranked occupations by impact score and semantic "
f"similarity to query. industry filter: {', '.join(INDUSTRIES)}."
),
)
def search_jobs_tool(
query: str = "",
industry: str = "All",
limit: int = 10,
scenario_json: str | None = None,
) -> dict:
return handle_search_jobs(
query=query, industry=industry, limit=limit, scenario_json=scenario_json
)
@mcp.tool(
name="list_open_predictions",
description=(
"List open (unresolved) falsifiable AI×economy predictions from the registry, "
"sorted by confidence descending."
),
)
def list_open_predictions_tool(limit: int = 20) -> dict:
return handle_list_open_predictions(limit=limit)
def main() -> None:
mcp.run(transport="stdio")
if __name__ == "__main__":
main()