hermes-backup/daily/2026-04-28_203212/skills/devops/arifos-mcp-meta-override/SKILL.md
Override FastMCP's built-in /tools endpoint to expose tool meta (stage_code, risk_tier, use_when, authority_boundary) from tool_manifest.py in arifOS MCP servers. Also covers HEALTHCHECK /sse bug and bind mount docker cp Gotchas.
npx skillsauth add ariffazil/openclaw-workspace arifos-mcp-meta-overrideInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
FastMCP v2's built-in /tools HTTP endpoint only exposes name, description, parameters. Any meta or annotations fields passed during tool registration are silently dropped. Constitutional metadata (stage_code, risk_tier, use_when, authority_boundary) from tool_manifest.py is invisible to HTTP MCP clients.
FastMCP v2's HTTP app has built-in routes for /tools that read only core fields. mcp._tool_manager (v1) doesn't exist in v2 — the accessor is mcp._local_provider._components. app.add_route("/tools", ...) does NOT override existing routes — both coexist and FastMCP's built-in matches first.
Add in the FastMCP HTTP app setup block. Must prepend at index 0 to override FastMCP's built-in:
from starlette.routing import Route
async def tools_with_meta(request: Request) -> JSONResponse:
from arifosmcp.tool_manifest import TOOL_MANIFEST
from arifosmcp.server import mcp
tool_summaries = []
lp = mcp._local_provider
for key, tool in lp._components.items():
if not key.startswith("tool:"):
continue
raw = getattr(tool, "raw_tool", None) or getattr(tool, "_tool", None)
if raw is None:
continue
tool_name = raw.name
desc = getattr(raw, "description", "") or ""
inp = getattr(raw, "inputSchema", {}) or {}
stage = getattr(raw, "stage", "")
lane = getattr(raw, "lane", "")
manifest = TOOL_MANIFEST.get(tool_name, {})
meta_dict = getattr(tool, "meta", None) or {}
arifos_m = meta_dict.get("arifos_manifest", {}) or manifest
entry = {
"name": tool_name,
"description": desc,
"parameters": inp,
"stage": arifos_m.get("stage_code", ""),
"lane": arifos_m.get("lane", ""),
"meta": {
"arifos_manifest": arifos_m,
"stage_code": meta_dict.get("stage_code", arifos_m.get("stage_code", "")),
"stage_name": meta_dict.get("stage_name", arifos_m.get("stage_name", "")),
"risk_tier": meta_dict.get("risk_tier", arifos_m.get("risk", {}).get("tier", "low")),
"irreversible": meta_dict.get("irreversible", arifos_m.get("risk", {}).get("irreversible", False)),
"requires_human_ack": meta_dict.get("requires_human_ack", arifos_m.get("risk", {}).get("requires_human_ack", False)),
"use_when": arifos_m.get("use_when", []),
"do_not_use_when": arifos_m.get("do_not_use_when", []),
"next_recommended_tools": arifos_m.get("next_recommended_tools", []),
"authority_boundary": arifos_m.get("authority_boundary", {}),
"privacy_scope": arifos_m.get("privacy_scope", []),
}
}
tool_summaries.append(entry)
return JSONResponse({"tools": tool_summaries, "count": len(tool_summaries)})
# Prepend at index 0 to override FastMCP's built-in /tools
app.router.routes.insert(0, Route("/tools", tools_with_meta, methods=["GET"]))
Symptom: Container marked (unhealthy) with 404 errors. Caddy's reverse proxy intermittently returns 404 for mcp.arif-fazil.com and arifosmcp.arif-fazil.com. External health monitors fire alerts.
Root Cause: Dockerfile's HEALTHCHECK hits /sse — an SSE transport endpoint. But arifOS MCP runs streamable-http transport. FastMCP returns 404 → Docker marks container unhealthy → Caddy's handle /mcp* routes become unreliable.
# WRONG — /sse does not exist on streamable-http transport
HEALTHCHECK --interval=20s --timeout=5s --start-period=30s --retries=3 \
CMD curl -fsS --max-time 3 http://localhost:8080/sse || exit 1
# CORRECT — use /health
HEALTHCHECK --interval=20s --timeout=5s --start-period=30s --retries=3 \
CMD curl -fsS --max-time 3 http://localhost:8080/health || exit 1
Symptom: docker cp host→container:/usr/src/app/... fails with Permission denied even with root.
Root Cause: The compose bind mount /root/arifOS → /usr/src/app is root:root owned but the container process runs as uid=1000(arifos). The mount point is mounted rw but the directory permissions on the host are root:root so the container user can't write to it.
Two-step workaround — use /tmp as staging:
# Copy to container's /tmp (which is writable)
docker cp server.py arifosmcp:/tmp/server_new.py
# Use root inside container to copy from /tmp to /usr/src/app
docker exec --user root arifosmcp cp /tmp/server_new.py /usr/src/app/arifosmcp/server.py
# Restart to reload
docker restart arifosmcp
Or just rebuild the image — cleaner, ensures the fix is baked in:
cd /root/arifOS
docker build -t ariffazil/arifos:2026.04.26-KANON -t compose-arifosmcp:sovereign-mirror .
docker compose -f /root/compose/docker-compose.yml up -d --build arifosmcp
# Local /tools with meta
curl http://localhost:8080/tools | python3 -c "
import sys,json; d=json.load(sys.stdin)
t = d['tools'][0]
print('Keys:', list(t.keys()))
print('meta present:', 'meta' in t)
print('stage_code:', t.get('meta',{}).get('stage_code'))
"
# Inside container via TestClient (bypasses Caddy)
docker exec arifosmcp python3 -c "
import sys; sys.path.insert(0,'/usr/src/app')
from starlette.testclient import TestClient
from arifosmcp.server import app
client = TestClient(app)
r = client.get('/tools')
d = r.json()
print('status:', r.status_code)
print('keys:', list(d['tools'][0].keys()))
"
# Public endpoint
curl https://mcp.arif-fazil.com/tools 2>/dev/null | python3 -c "
import sys,json; d=json.load(sys.stdin)
print(f'count: {d[\"count\"]}')
print(f'first meta.stage_code: {d[\"tools\"][0][\"meta\"][\"stage_code\"]}')
"
# Container health
docker inspect arifosmcp --format '{{.State.Health.Status}}'
arifosmcp/server.py — where override is addedarifosmcp/tool_manifest.py — TOOL_MANIFEST dict sourcearifosmcp/runtime/tools.py — register_tools() passes meta={} to FastMCPDockerfile — HEALTHCHECK configuration (fix: /sse → /health)compose/docker-compose.yml — arifosmcp service with bind mountTestClient inside container — external curl routes through Caddy which can mask issuesapp object is served (runtime.server:app vs server:app) — routes set at import time/health for streamable-http transport, never /sseapp.router.routes.clear() DELETES all routes — never use. Prepend at index 0 instead.FastMCP v2's built-in /tools HTTP endpoint only exposes name, description, parameters. Any meta or annotations fields passed during tool registration are silently dropped. Constitutional metadata (stage_code, risk_tier, use_when, authority_boundary) from tool_manifest.py is invisible to HTTP MCP clients.
FastMCP v2's HTTP app has built-in routes for /tools that read only core fields. mcp._tool_manager (v1) doesn't exist in v2 — the accessor is mcp._local_provider._components. app.add_route("/tools", ...) does NOT override existing routes — both coexist and FastMCP's built-in matches first.
Add in the FastMCP HTTP app setup block. Must prepend at index 0 to override FastMCP's built-in:
async def tools_with_meta(request: Request) -> JSONResponse:
from arifosmcp.tool_manifest import TOOL_MANIFEST
tool_summaries = []
try:
lp = getattr(mcp, "_local_provider", None)
if lp:
raw_tools = {k: v for k, v in lp._components.items() if k.startswith("tool:")}
else:
raw_tools = {}
for name, tool in raw_tools.items():
tool_name = name.replace("tool:", "").rstrip("@")
base = {"description": tool.description or "", "parameters": tool.parameters or {}}
manifest = TOOL_MANIFEST.get(tool_name, {})
meta_dict = getattr(tool, "meta", None) or {}
arifos_m = meta_dict.get("arifos_manifest", {}) or manifest
entry = {
"name": tool_name,
"description": base["description"],
"parameters": base["parameters"],
"stage": arifos_m.get("stage_code", ""),
"lane": arifos_m.get("lane", ""),
"meta": {
"arifos_manifest": arifos_m,
"stage_code": meta_dict.get("stage_code", arifos_m.get("stage_code", "")),
"stage_name": meta_dict.get("stage_name", arifos_m.get("stage_name", "")),
"risk_tier": meta_dict.get("risk_tier", arifos_m.get("risk", {}).get("tier", "low")),
"irreversible": meta_dict.get("irreversible", arifos_m.get("risk", {}).get("irreversible", False)),
"requires_human_ack": meta_dict.get("requires_human_ack", arifos_m.get("risk", {}).get("requires_human_ack", False)),
"use_when": arifos_m.get("use_when", []),
"do_not_use_when": arifos_m.get("do_not_use_when", []),
"next_recommended_tools": arifos_m.get("next_recommended_tools", []),
"authority_boundary": arifos_m.get("authority_boundary", {}),
"privacy_scope": arifos_m.get("privacy_scope", []),
"canonical_order": meta_dict.get("canonical_order", arifos_m.get("canonical_order", [])),
}
}
tool_summaries.append(entry)
except Exception as e:
logger.warning(f"Failed to build meta-enriched tools response: {e}")
tool_summaries = []
return JSONResponse({"tools": tool_summaries, "count": len(tool_summaries)})
from starlette.routing import Route
tools_meta_route = Route("/tools", tools_with_meta, methods=["GET"])
app.router.routes.insert(0, tools_meta_route) # prepend to override
mcp._local_provider._components (dict, keys "tool:NAME@"); tool.meta (dict with arifos_manifest); tool.description; tool.parametersapp.add_route("/tools", ...) does NOT override — must use app.router.routes.insert(0, Route(...))uid=1000(arifos) but bind mount files are root:root — runtime docker cp and touch inside mount point will fail with Permission Denied. Must rebuild image to update mounted files./root/arifOS (primary, git repo) and /opt/arifos/src/arifOS (symlink target at /root/arifos → /opt/arifos/src/arifOS) — bind mount serves from /opt/arifos/src/arifOS. Keep in sync via filesystem or rebuild.uid=1000(arifos) but bind mount files are root:root (read-only for non-root). Cannot docker cp directly into mount point. Two valid workarounds: (a) docker cp <file> CONTAINER:/tmp/<file> && docker exec --user root CONTAINER cp /tmp/<file> /usr/src/app/arifosmcp/<file> then restart; (b) rebuild the image — bind mount files are updated at container start, so the new binary is in the image. Rebuild is cleaner for production.docker compose up -d --build arifosmcp, the new image is used but the bind mount still serves the source tree files — changes to /root/arifOS are reflected immediately without rebuild. However if server.py was changed in the image, you must restart the container (docker restart arifosmcp) for the new Python module bytes to be loaded.curl -s http://localhost:8080/tools | python3 -c "
import sys,json; d=json.load(sys.stdin)
t = d['tools'][0]
print('Keys:', list(t.keys()), 'meta present:', 'meta' in t)
if 'meta' in t:
m = t['meta']
print('stage_code:', m.get('stage_code'), 'risk_tier:', m.get('risk_tier'))
print('count:', d.get('count'))
for t in d['tools']:
m = t.get('meta',{})
print(f' {t[\"name\"]}: stage={m.get(\"stage_code\")} risk={m.get(\"risk_tier\")}')
"
arifosmcp/server.py — where override is added (in if IS_FASTMCP_3 block)arifosmcp/tool_manifest.py — TOOL_MANIFEST dict sourcearifosmcp/runtime/tools.py — register_tools() passes meta={} to FastMCPcompose/docker-compose.yml — arifosmcp service with bind mountTestClient inside container — external curl routes differentlyapp object is served (runtime.server:app vs server:app) — routes set at import timedocker cpDITEMPA BUKAN DIBERI — Forged, Not Given
development
Federation-wide gold (XAUUSD) trading capability. Python stack, OANDA broker, backtesting, macro signals, RSI strategy. Every organ has a role.
development
Capital claim state management — tracks claim lifecycle across WEALTH organ.
development
Archived constitutional warga placeholder retained only for audit provenance. Do not use for active work; use the live arifOS governance and constitutional skills instead.
testing
Warga (citizen) agent skills for AAA federation members. See subdirectories for specialized warga skills.