#5015 "Built-in host-agent playbooks make an MCP caller supply steward_url" - run_playbook, create_schedule and set_alert_action fill a steward-builtin playbook's steward_url when the caller leaves it out or empty, from Settings → General → Public base URL, else the scheme and host the MCP request arrived on — the same source the web actions use. - run_playbook's reply names what it filled (filled_by_steward), and get_playbook shows that value as the variable's default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
1.9 KiB
Python
51 lines
1.9 KiB
Python
"""Per-request MCP context: who is calling, and which app they are calling.
|
|
|
|
The /mcp transport bypasses Quart's request pipeline, so neither `session`
|
|
nor `current_app` exists inside a tool. The ASGI auth wrapper in server.py
|
|
sets these from the resolved bearer token before dispatching; tools read them
|
|
through `current_caller()` and `mcp_app()`.
|
|
|
|
Neither falls back to a default: a tool reached without an authenticated
|
|
caller is a bug in the transport, and it raises rather than acting for nobody.
|
|
"""
|
|
from __future__ import annotations
|
|
from contextvars import ContextVar
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from quart import Quart
|
|
from steward.core.api_tokens import Caller
|
|
|
|
_caller_ctx: ContextVar["Caller | None"] = ContextVar("steward_mcp_caller", default=None)
|
|
_app_ctx: ContextVar["Quart | None"] = ContextVar("steward_mcp_app", default=None)
|
|
# The scheme and host the caller reached /mcp on, e.g. "https://steward.example".
|
|
_base_url_ctx: ContextVar[str] = ContextVar("steward_mcp_base_url", default="")
|
|
|
|
|
|
def current_caller() -> "Caller":
|
|
caller = _caller_ctx.get()
|
|
if caller is None:
|
|
raise RuntimeError("no MCP caller in context: the request was not bearer-authenticated")
|
|
return caller
|
|
|
|
|
|
def current_caller_or_none() -> "Caller | None":
|
|
return _caller_ctx.get()
|
|
|
|
|
|
def mcp_app() -> "Quart":
|
|
app = _app_ctx.get()
|
|
if app is None:
|
|
raise RuntimeError("no Steward app in MCP context")
|
|
return app
|
|
|
|
|
|
def public_url() -> str:
|
|
"""Steward's externally reachable base URL, as the web side's
|
|
public_base_url() gives it: Settings → General → Public base URL when set,
|
|
else the scheme and host this MCP request arrived on; "" when neither is
|
|
known."""
|
|
app = _app_ctx.get()
|
|
configured = (app.config.get("PUBLIC_BASE_URL") or "").strip() if app is not None else ""
|
|
return (configured or _base_url_ctx.get()).rstrip("/")
|