STATE-WP-0070 T03: remove legacy workstream MCP aliases
Meter-gated removal (7 consecutive zero-usage windows reached). Deletes:
- MCP tools: create_workstream, list_workstreams, update_workstream,
update_workstream_status
- MCP resource: state://workstreams/{topic_slug}
- Dead helpers: _LEGACY_MCP_* maps, _legacy_mcp_deprecation,
_meter_legacy_mcp, _attach_legacy_deprecation, _update_workplan_legacy_impl
TOOLS.md keeps a retired→preferred migration map (per backlog). Removed the
now-dead STATEHUB_MCP_LEGACY_METER guard in conftest. Retargeted the
create_workplan error-skip test to the preferred tool; dropped alias-only tests.
Staged on branch state-wp-0070-legacy-retirement — do not merge until the 7th
documented zero-usage window is captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1acad4c02c
commit
ef62cb3872
5 changed files with 28 additions and 328 deletions
|
|
@ -181,102 +181,6 @@ def _json_result(result: Any) -> str:
|
|||
return json.dumps(result, indent=2)
|
||||
|
||||
|
||||
_LEGACY_MCP_TOOL_REPLACEMENTS: dict[str, str] = {
|
||||
"create_workstream": "create_workplan",
|
||||
"list_workstreams": "list_workplans",
|
||||
"update_workstream": "update_workplan",
|
||||
"update_workstream_status": "update_workplan_status",
|
||||
}
|
||||
|
||||
_LEGACY_MCP_RESOURCE_REPLACEMENTS: dict[str, str] = {
|
||||
"state://workstreams/{topic_slug}": "state://workplans/{topic_slug}",
|
||||
}
|
||||
|
||||
|
||||
def _legacy_mcp_deprecation(*, tool: str | None = None, resource: str | None = None) -> dict[str, str]:
|
||||
if tool:
|
||||
replacement = _LEGACY_MCP_TOOL_REPLACEMENTS[tool]
|
||||
return {
|
||||
"deprecated": "true",
|
||||
"tool": tool,
|
||||
"replacement": replacement,
|
||||
"message": (
|
||||
f"{tool} is a legacy MCP alias; use {replacement} instead. "
|
||||
"Retirement tracked by legacy-meter (STATE-WP-0069)."
|
||||
),
|
||||
}
|
||||
if resource:
|
||||
replacement = _LEGACY_MCP_RESOURCE_REPLACEMENTS[resource]
|
||||
return {
|
||||
"deprecated": "true",
|
||||
"resource": resource,
|
||||
"replacement": replacement,
|
||||
"message": (
|
||||
f"{resource} is a legacy MCP resource; use {replacement} instead. "
|
||||
"Retirement tracked by legacy-meter (STATE-WP-0069)."
|
||||
),
|
||||
}
|
||||
raise ValueError("tool or resource required")
|
||||
|
||||
|
||||
def _meter_legacy_mcp(*, tool: str | None = None, resource: str | None = None) -> None:
|
||||
"""Record legacy MCP alias usage on the legacy meter (best-effort).
|
||||
|
||||
Keys match docs/workplan-terminology-legacy-retirement-backlog.md phase 3
|
||||
(`mcp:<tool_name>` / resource URI). Failures never surface to the caller —
|
||||
the meter gates retirement, it must not break the aliased call itself.
|
||||
"""
|
||||
if os.environ.get("STATEHUB_MCP_LEGACY_METER", "on") == "off":
|
||||
return
|
||||
if tool:
|
||||
key = f"mcp:{tool}"
|
||||
replacement = _LEGACY_MCP_TOOL_REPLACEMENTS[tool]
|
||||
elif resource:
|
||||
key = resource
|
||||
replacement = _LEGACY_MCP_RESOURCE_REPLACEMENTS[resource]
|
||||
else:
|
||||
raise ValueError("tool or resource required")
|
||||
try:
|
||||
with _client() as c:
|
||||
c.post(
|
||||
"/legacy-meter/usage",
|
||||
json={
|
||||
"interface_key": key,
|
||||
"interface_kind": "mcp_tool",
|
||||
"replacement_ref": replacement,
|
||||
"owner_component": "state-hub.mcp",
|
||||
"replacement_verified": True,
|
||||
"component_key": "state-hub.mcp",
|
||||
},
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _attach_legacy_deprecation(tool_name: str, result_json: str) -> str:
|
||||
if tool_name not in _LEGACY_MCP_TOOL_REPLACEMENTS:
|
||||
return result_json
|
||||
try:
|
||||
payload = json.loads(result_json)
|
||||
except json.JSONDecodeError:
|
||||
return result_json
|
||||
if isinstance(payload, dict) and payload.get("error"):
|
||||
return result_json
|
||||
if isinstance(payload, dict):
|
||||
payload["_deprecation"] = _legacy_mcp_deprecation(tool=tool_name)
|
||||
return json.dumps(payload, indent=2)
|
||||
if isinstance(payload, list):
|
||||
return json.dumps(
|
||||
{
|
||||
"_deprecation": _legacy_mcp_deprecation(tool=tool_name),
|
||||
"workplans": payload,
|
||||
"workstreams": payload,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
return result_json
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Resources
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -303,25 +207,6 @@ def resource_workplans(topic_slug: str) -> str:
|
|||
return json.dumps(_get("/workplans", {"topic_id": match["id"]}), indent=2)
|
||||
|
||||
|
||||
@mcp.resource("state://workstreams/{topic_slug}")
|
||||
def resource_workstreams(topic_slug: str) -> str:
|
||||
"""Legacy resource alias — prefer state://workplans/{topic_slug}."""
|
||||
_meter_legacy_mcp(resource="state://workstreams/{topic_slug}")
|
||||
topics = _get("/topics", {"status": "active"})
|
||||
match = next((t for t in topics if t["slug"] == topic_slug), None)
|
||||
if not match:
|
||||
return json.dumps({"error": f"Topic '{topic_slug}' not found"})
|
||||
rows = _get("/workplans", {"topic_id": match["id"]})
|
||||
return json.dumps(
|
||||
{
|
||||
"_deprecation": _legacy_mcp_deprecation(resource="state://workstreams/{topic_slug}"),
|
||||
"workplans": rows,
|
||||
"workstreams": rows,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
@mcp.resource("state://decisions/blocking")
|
||||
def resource_blocking_decisions() -> str:
|
||||
"""All pending/escalated decisions."""
|
||||
|
|
@ -709,7 +594,7 @@ def _create_workplan_impl(
|
|||
})
|
||||
if progress_error:
|
||||
return _json_result(progress_error)
|
||||
return _attach_legacy_deprecation(tool_name, _json_result(wp))
|
||||
return _json_result(wp)
|
||||
|
||||
|
||||
def _update_workplan_status_impl(workplan_id: str, status: str, *, tool_name: str) -> str:
|
||||
|
|
@ -727,7 +612,7 @@ def _update_workplan_status_impl(workplan_id: str, status: str, *, tool_name: st
|
|||
})
|
||||
if progress_error:
|
||||
return _json_result(progress_error)
|
||||
return _attach_legacy_deprecation(tool_name, _json_result(wp))
|
||||
return _json_result(wp)
|
||||
|
||||
|
||||
def _update_workplan_impl(
|
||||
|
|
@ -756,31 +641,6 @@ def _update_workplan_impl(
|
|||
return _json_result(_patch(f"/workplans/{workplan_id}", payload))
|
||||
|
||||
|
||||
def _update_workplan_legacy_impl(
|
||||
workplan_id: str,
|
||||
*,
|
||||
tool_name: str,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
owner: str | None = None,
|
||||
due_date: str | None = None,
|
||||
repo_goal_id: str | None = None,
|
||||
status: str | None = None,
|
||||
) -> str:
|
||||
return _attach_legacy_deprecation(
|
||||
tool_name,
|
||||
_update_workplan_impl(
|
||||
workplan_id,
|
||||
title=title,
|
||||
description=description,
|
||||
owner=owner,
|
||||
due_date=due_date,
|
||||
repo_goal_id=repo_goal_id,
|
||||
status=status,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Mutate tools
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -824,36 +684,6 @@ def create_workplan(
|
|||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def create_workstream(
|
||||
title: str,
|
||||
repo_id: str | None = None,
|
||||
topic_id: str | None = None,
|
||||
slug: str | None = None,
|
||||
description: str | None = None,
|
||||
owner: str | None = None,
|
||||
due_date: str | None = None,
|
||||
planning_priority: str | None = None,
|
||||
planning_order: int | None = None,
|
||||
) -> str:
|
||||
"""DEPRECATED legacy alias — use create_workplan(repo_id=...) instead."""
|
||||
_meter_legacy_mcp(tool="create_workstream")
|
||||
if not repo_id:
|
||||
return _json_result(_mcp_error("create_workstream", "repo_id is required"))
|
||||
return _create_workplan_impl(
|
||||
repo_id=repo_id,
|
||||
title=title,
|
||||
topic_id=topic_id,
|
||||
slug=slug,
|
||||
description=description,
|
||||
owner=owner,
|
||||
due_date=due_date,
|
||||
planning_priority=planning_priority,
|
||||
planning_order=planning_order,
|
||||
tool_name="create_workstream",
|
||||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def create_task(
|
||||
workplan_id: str | None = None,
|
||||
|
|
@ -1246,28 +1076,6 @@ def list_workplans(
|
|||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def list_workstreams(
|
||||
topic_id: str | None = None,
|
||||
repo_id: str | None = None,
|
||||
status: str | None = None,
|
||||
owner: str | None = None,
|
||||
slug: str | None = None,
|
||||
) -> str:
|
||||
"""DEPRECATED legacy alias — use list_workplans instead."""
|
||||
_meter_legacy_mcp(tool="list_workstreams")
|
||||
return _attach_legacy_deprecation(
|
||||
"list_workstreams",
|
||||
list_workplans(
|
||||
repo_id=repo_id,
|
||||
topic_id=topic_id,
|
||||
status=status,
|
||||
owner=owner,
|
||||
slug=slug,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def update_workplan_status(workplan_id: str, status: str) -> str:
|
||||
"""Update a workplan's status.
|
||||
|
|
@ -1279,20 +1087,6 @@ def update_workplan_status(workplan_id: str, status: str) -> str:
|
|||
return _update_workplan_status_impl(workplan_id, status, tool_name="update_workplan_status")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def update_workstream_status(
|
||||
status: str,
|
||||
workplan_id: str | None = None,
|
||||
workstream_id: str | None = None,
|
||||
) -> str:
|
||||
"""DEPRECATED legacy alias — use update_workplan_status instead."""
|
||||
_meter_legacy_mcp(tool="update_workstream_status")
|
||||
parent_id = workplan_id or workstream_id
|
||||
if not parent_id:
|
||||
return _json_result(_mcp_error("update_workstream_status", "workplan_id is required"))
|
||||
return _update_workplan_status_impl(parent_id, status, tool_name="update_workstream_status")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def update_workplan(
|
||||
workplan_id: str,
|
||||
|
|
@ -1315,34 +1109,6 @@ def update_workplan(
|
|||
)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def update_workstream(
|
||||
workplan_id: str | None = None,
|
||||
workstream_id: str | None = None,
|
||||
title: str | None = None,
|
||||
description: str | None = None,
|
||||
owner: str | None = None,
|
||||
due_date: str | None = None,
|
||||
repo_goal_id: str | None = None,
|
||||
status: str | None = None,
|
||||
) -> str:
|
||||
"""DEPRECATED legacy alias — use update_workplan instead."""
|
||||
_meter_legacy_mcp(tool="update_workstream")
|
||||
parent_id = workplan_id or workstream_id
|
||||
if not parent_id:
|
||||
return _json_result(_mcp_error("update_workstream", "workplan_id is required"))
|
||||
return _update_workplan_legacy_impl(
|
||||
parent_id,
|
||||
tool_name="update_workstream",
|
||||
title=title,
|
||||
description=description,
|
||||
owner=owner,
|
||||
due_date=due_date,
|
||||
repo_goal_id=repo_goal_id,
|
||||
status=status,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Next-steps suggestion tool (S2.3) — sanctioned write use case #2
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue