File size: 7,128 Bytes
6cefeec e57d3fe 6cefeec 2e3ca88 6cefeec e57d3fe 6cefeec e57d3fe 6cefeec e1c195f 30bf877 6cefeec 32e3729 e1c195f 6cefeec 30bf877 e57d3fe 6cefeec 2e3ca88 e57d3fe 30bf877 e57d3fe 6cefeec e57d3fe 6cefeec 30bf877 32e3729 30bf877 2e3ca88 32e3729 30bf877 32e3729 30bf877 6cefeec c631558 6cefeec e1c195f 6cefeec e1c195f 6cefeec e57d3fe 6cefeec e57d3fe 6cefeec e57d3fe 6cefeec e57d3fe 6cefeec e57d3fe 6cefeec 18eb9c2 6cefeec | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | from __future__ import annotations
# ruff: noqa: E402
import json
import os
import sys
import traceback
from pathlib import Path
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import PlainTextResponse
def _discover_workspace_root() -> Path:
env_root = os.getenv("CODE_TOOLS_ROOT")
if env_root:
return Path(env_root).expanduser().resolve()
script_root = Path(__file__).resolve().parents[1]
if (script_root / ".prefab").exists():
return script_root
return (Path.home() / "source/code_tools").resolve()
WORKSPACE_ROOT = _discover_workspace_root()
PREFAB_ROOT = WORKSPACE_ROOT / ".prefab"
PREFAB_SRC = Path(os.getenv("PREFAB_SRC", str(Path.home() / "source/prefab/src")))
SCRIPTS_DIR = Path(__file__).resolve().parent
CARDS_DIR = PREFAB_ROOT / "agent-cards"
CONFIG_PATH = PREFAB_ROOT / "fastagent.config.yaml"
RAW_CARD_FILE = CARDS_DIR / "hub_search_raw.md"
EXPANDED_CARDS_DIR = CARDS_DIR
RAW_AGENT = "hub_search_raw"
HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", "9999"))
PATH = os.getenv("MCP_PATH", "/mcp")
CORS_MIDDLEWARE = [
Middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["mcp-session-id"],
)
]
os.chdir(WORKSPACE_ROOT)
if PREFAB_SRC.exists() and str(PREFAB_SRC) not in sys.path:
sys.path.insert(0, str(PREFAB_SRC))
if str(SCRIPTS_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPTS_DIR))
from fast_agent import FastAgent
from fast_agent.mcp.auth.context import request_bearer_token
from fast_agent.mcp.auth.middleware import HFAuthHeaderMiddleware
from fast_agent.mcp.auth.presence import PresenceTokenVerifier
from fastmcp import FastMCP
from fastmcp.server.auth.auth import RemoteAuthProvider
from fastmcp.server.dependencies import get_access_token
from fastmcp.tools import ToolResult
from mcp.types import TextContent
from pydantic import AnyHttpUrl
from card_includes import materialize_expanded_card
from prefab_hub_ui import build_runtime_wire, error_wire, parse_runtime_payload
class _RootResourceRemoteAuthProvider(RemoteAuthProvider):
"""Advertise the Space root as the protected resource."""
def _get_resource_url(self, path: str | None = None) -> AnyHttpUrl | None:
del path
return self.base_url
def _get_oauth_config() -> tuple[str | None, list[str], str]:
oauth_provider = os.environ.get("FAST_AGENT_SERVE_OAUTH", "").lower()
if oauth_provider in ("hf", "huggingface"):
oauth_provider = "huggingface"
elif not oauth_provider:
oauth_provider = None
oauth_scopes_str = os.environ.get("FAST_AGENT_OAUTH_SCOPES", "")
oauth_scopes = [s.strip() for s in oauth_scopes_str.split(",") if s.strip()] or ["access"]
resource_url = os.environ.get(
"FAST_AGENT_OAUTH_RESOURCE_URL",
f"http://localhost:{PORT}",
)
return oauth_provider, oauth_scopes, resource_url
EXPANDED_RAW_CARD_FILE = materialize_expanded_card(
RAW_CARD_FILE,
workspace_root=WORKSPACE_ROOT,
out_dir=EXPANDED_CARDS_DIR,
)
fast = FastAgent(
"hub-search-prefab",
config_path=str(CONFIG_PATH),
parse_cli_args=False,
)
fast.load_agents(EXPANDED_RAW_CARD_FILE)
_oauth_provider, _oauth_scopes, _oauth_resource_url = _get_oauth_config()
_auth_provider = None
_middleware = list(CORS_MIDDLEWARE)
if _oauth_provider == "huggingface":
_auth_provider = _RootResourceRemoteAuthProvider(
token_verifier=PresenceTokenVerifier(
provider="huggingface",
scopes=_oauth_scopes,
),
authorization_servers=[AnyHttpUrl("https://huggingface.co")],
base_url=AnyHttpUrl(_oauth_resource_url),
scopes_supported=_oauth_scopes,
resource_name="gen-ui",
resource_documentation=AnyHttpUrl("https://huggingface.co/spaces/evalstate/gen-ui"),
)
_middleware.append(Middleware(HFAuthHeaderMiddleware))
mcp = FastMCP(
"hub-search-prefab",
auth=_auth_provider,
)
@mcp.custom_route("/", methods=["GET"])
async def root_info(request) -> PlainTextResponse:
return PlainTextResponse("gen-ui MCP server. Use /mcp for MCP and /.well-known/oauth-protected-resource for auth discovery.")
async def _run_raw(query: str) -> str:
return await _run_agent(RAW_AGENT, query)
def _get_request_bearer_token() -> str | None:
access_token = get_access_token()
if access_token is None:
return None
return access_token.token
async def _run_agent(agent_name: str, query: str) -> str:
saved_token = request_bearer_token.set(_get_request_bearer_token())
try:
async with fast.run() as agents:
return await getattr(agents, agent_name).send(query)
finally:
request_bearer_token.reset(saved_token)
def _wire_tool_result(wire: dict[str, object]) -> ToolResult:
return ToolResult(
content=[TextContent(type="text", text="[Rendered Prefab UI]")],
structured_content=wire,
)
def _render_query_wire(query: str, raw_text: str) -> dict[str, object]:
payload = parse_runtime_payload(raw_text)
return build_runtime_wire(query, payload)
async def _build_query_wire(query: str) -> dict[str, object]:
raw = await _run_raw(query)
return _render_query_wire(query, raw)
def _missing_query_json() -> str:
return json.dumps(
{
"result": None,
"meta": {
"ok": False,
"error": "Missing required argument: query",
},
}
)
@mcp.tool(app=True)
async def hub_search_prefab(query: str) -> ToolResult:
"""Run the Prefab UI service with deterministic rendering over raw Hub output."""
try:
wire = await _build_query_wire(query)
except Exception as exc: # noqa: BLE001
traceback.print_exc()
wire = error_wire(str(exc))
return _wire_tool_result(wire)
@mcp.tool
async def hub_search_prefab_wire(query: str | None = None) -> str:
"""Return final deterministic Prefab wire JSON for a Hub query."""
if not query:
return json.dumps(error_wire("Missing required argument: query"), ensure_ascii=False)
try:
wire = await _build_query_wire(query)
return json.dumps(wire, ensure_ascii=False)
except Exception as exc: # noqa: BLE001
traceback.print_exc()
return json.dumps(error_wire(str(exc)), ensure_ascii=False)
@mcp.tool
async def hub_search_raw_debug(query: str | None = None) -> str:
"""Return the raw live-service payload from the raw Hub search agent."""
if not query:
return _missing_query_json()
try:
return await _run_raw(query)
except Exception as exc: # noqa: BLE001
traceback.print_exc()
return json.dumps({"result": None, "meta": {"ok": False, "error": str(exc)}})
def main() -> None:
mcp.run(
"streamable-http",
host=HOST,
port=PORT,
path=PATH,
stateless_http=True,
middleware=_middleware,
)
if __name__ == "__main__":
main()
|