feat(mcp): add update_agent_card + get_runtime_identity tools #17

Closed
fullstack-engineer wants to merge 1 commits from feat/agent-card-update-and-runtime-identity-tools into main
Member

Summary

Adds two MCP tools to close T4-tier workspace owner-permission gaps reported via the canvas:

  • update_agent_card — POSTs the card to /registry/update-card (the platform endpoint already exists at workspace-server/internal/handlers/registry.go:800, auth=workspace bearer). Gated on memory.write via the existing RBAC map so read-only roles cannot silently rewrite the platform card.
  • get_runtime_identity — env-only; returns model, model_provider, molecule_model, anthropic_base_url, tier, workspace_id, runtime (ADAPTER_MODULE). No HTTP call. Always permitted — even read-only agents may know what model they are.

Bumps wheel version 0.1.17 → 0.1.18.

Why

From the field report (T4 owner via canvas):

(a) Bash failing with "permission restrictions"; (b) cannot read ~/.claude/settings.json; (c) cannot update own agent_card; (d) cannot identify own model.

(c) and (d) are runtime gaps — the platform endpoint exists and MODEL is already injected by workspace_provision.go, but the agent had no MCP surface to either of them. (a) and (b) are template-side and shipped separately in molecule-ai-workspace-template-claude-code.

Test plan

  • pytest tests/test_update_card_and_runtime_identity.py — 7 new cases (env resolution, missing-env fallback, network-free behaviour, success path, server-error propagation, non-dict-card rejection, missing-WORKSPACE_ID rejection).
  • pytest tests/test_a2a_mcp_server.py — extended with update_agent_card RBAC-denied (read-only) and get_runtime_identity RBAC-permitted cases.
  • Full suite: WORKSPACE_ID=test-ws pytest tests/ → 141 passed, 1 pre-existing failure (test_install_runs_setup_sh_with_scrubbed_env — env missing /bin/bash; reproduced on main).
  • (release) After merge, cut a 0.1.18 wheel via the publish workflow and bump .runtime-version in molecule-ai-workspace-template-claude-code to pick it up.

Follow-ups

The matching template-side PR (entrypoint chown idempotency, ~/.claude/settings.json stub, T4 ownership note in CLAUDE.md) is opened in parallel in molecule-ai-workspace-template-claude-code.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

## Summary Adds two MCP tools to close T4-tier workspace owner-permission gaps reported via the canvas: - **`update_agent_card`** — POSTs the card to `/registry/update-card` (the platform endpoint already exists at `workspace-server/internal/handlers/registry.go:800`, auth=workspace bearer). Gated on `memory.write` via the existing RBAC map so read-only roles cannot silently rewrite the platform card. - **`get_runtime_identity`** — env-only; returns `model`, `model_provider`, `molecule_model`, `anthropic_base_url`, `tier`, `workspace_id`, `runtime` (ADAPTER_MODULE). No HTTP call. Always permitted — even read-only agents may know what model they are. Bumps wheel version 0.1.17 → 0.1.18. ## Why From the field report (T4 owner via canvas): > (a) Bash failing with "permission restrictions"; (b) cannot read `~/.claude/settings.json`; (c) cannot update own agent_card; (d) cannot identify own model. (c) and (d) are runtime gaps — the platform endpoint exists and `MODEL` is already injected by `workspace_provision.go`, but the agent had no MCP surface to either of them. (a) and (b) are template-side and shipped separately in molecule-ai-workspace-template-claude-code. ## Test plan - [x] `pytest tests/test_update_card_and_runtime_identity.py` — 7 new cases (env resolution, missing-env fallback, network-free behaviour, success path, server-error propagation, non-dict-card rejection, missing-WORKSPACE_ID rejection). - [x] `pytest tests/test_a2a_mcp_server.py` — extended with `update_agent_card` RBAC-denied (read-only) and `get_runtime_identity` RBAC-permitted cases. - [x] Full suite: `WORKSPACE_ID=test-ws pytest tests/` → 141 passed, 1 pre-existing failure (`test_install_runs_setup_sh_with_scrubbed_env` — env missing `/bin/bash`; reproduced on `main`). - [ ] (release) After merge, cut a 0.1.18 wheel via the publish workflow and bump `.runtime-version` in `molecule-ai-workspace-template-claude-code` to pick it up. ## Follow-ups The matching template-side PR (entrypoint chown idempotency, `~/.claude/settings.json` stub, T4 ownership note in `CLAUDE.md`) is opened in parallel in molecule-ai-workspace-template-claude-code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fullstack-engineer added 1 commit 2026-05-15 21:26:14 +00:00
feat(mcp): add update_agent_card + get_runtime_identity tools
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 18s
ci / mirror-guard (pull_request) Failing after 20s
2e5f959d31
T4-tier workspace owners reported two missing capabilities through the
canvas:
  - the agent could not update its own agent_card (no MCP tool wrapped
    the existing POST /registry/update-card endpoint)
  - the agent could not identify which model it was running (the MODEL
    env var is injected by provisioner.workspace_provision but nothing
    surfaced it back to the agent)

Both are now addressable from inside the runtime:

  tool_get_runtime_identity  — env-only; returns model, model_provider,
    molecule_model, anthropic_base_url, tier, workspace_id, runtime
    (ADAPTER_MODULE). No HTTP call. Always permitted by RBAC — even
    read-only agents may know what model they are.

  tool_update_agent_card    — POSTs the card to /registry/update-card
    with the workspace's own bearer (same auth path as commit_memory).
    Gated on memory.write via the existing RBAC permission map so
    read-only roles can't silently rewrite the platform card.

Bumps wheel version 0.1.17 → 0.1.18.

Tests:
  - tests/test_update_card_and_runtime_identity.py — 7 new cases covering
    env resolution, missing-env fallback, network-free behaviour, the
    success path, server-error propagation, non-dict-card rejection,
    and missing-WORKSPACE_ID rejection.
  - tests/test_a2a_mcp_server.py — extends RBAC suite with the new
    permission gate and the env-only read-allowed path.

Why: ungates the user-visible "cat ~/.claude/settings.json doesn't help
me, who am I?" loop and lets a T4 owner edit its card live without an
operator commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
infra-runtime-be approved these changes 2026-05-15 21:41:11 +00:00
infra-runtime-be left a comment
Member

Review: Approve

Ran full suite locally: 143/143 pass (135 existing + 8 new).

What this adds

Two MCP tools:

get_runtime_identity — env-only, no HTTP. Returns model, model_provider, tier, workspace_id, and runtime (ADAPTER_MODULE). Clean and correct; no network dependency.

update_agent_card — POSTs to /{PLATFORM_URL}/registry/update-card with workspace bearer auth. Validates card is a dict, validates workspace_id, returns structured {success, status} or {success, error, status_code}. Gated on memory.write RBAC — appropriate for a mutation tool.

Design notes

RBAC split is correct: get_runtime_identity is always permitted (read-only, env-only); update_agent_card requires memory.write — a read-only role agent can't silently rewrite the platform card.

tool_get_runtime_identity returning a plain dict (not JSON string) is the right abstraction — the MCP dispatcher in a2a_mcp_server.py handles the json.dumps wrap.

Minor non-blocking comment

In test_posts_to_registry_update_card, the assertion result.get("success") is True or result.get("status") == "updated" is slightly confusing — since the success path returns {"success": True, "status": "updated"}, the or result.get("status") == "updated" branch is the active one. Consider assert result == {"success": True, "status": "updated"} for clarity, but this is cosmetic and doesn't affect correctness.

Well-scoped, clean tests. LGTM.

## Review: Approve ✅ Ran full suite locally: **143/143 pass** (135 existing + 8 new). ### What this adds Two MCP tools: **`get_runtime_identity`** — env-only, no HTTP. Returns model, model_provider, tier, workspace_id, and runtime (ADAPTER_MODULE). Clean and correct; no network dependency. **`update_agent_card`** — POSTs to `/{PLATFORM_URL}/registry/update-card` with workspace bearer auth. Validates `card` is a dict, validates workspace_id, returns structured `{success, status}` or `{success, error, status_code}`. Gated on `memory.write` RBAC — appropriate for a mutation tool. ### Design notes RBAC split is correct: `get_runtime_identity` is always permitted (read-only, env-only); `update_agent_card` requires `memory.write` — a read-only role agent can't silently rewrite the platform card. `tool_get_runtime_identity` returning a plain `dict` (not JSON string) is the right abstraction — the MCP dispatcher in `a2a_mcp_server.py` handles the `json.dumps` wrap. ### Minor non-blocking comment In `test_posts_to_registry_update_card`, the assertion `result.get("success") is True or result.get("status") == "updated"` is slightly confusing — since the success path returns `{"success": True, "status": "updated"}`, the `or result.get("status") == "updated"` branch is the active one. Consider `assert result == {"success": True, "status": "updated"}` for clarity, but this is cosmetic and doesn't affect correctness. Well-scoped, clean tests. LGTM.
Author
Member

Closing — this repo is mirror-only (see reference_runtime_repo_is_mirror_only in saved memory). The canonical edit point for molecule_runtime/* is molecule-ai/molecule-core at workspace/*; the wheel mirror in this repo is regenerated automatically by publish-runtime.yml on staging→main promotion.

Relocated PR opened in molecule-core: molecule-ai/molecule-core#1240

The core PR translates this diff to fit the iter-4 layered architecture there:

  • tool functions live in a new single-concern module workspace/a2a_tools_identity.py (alongside a2a_tools_messaging, a2a_tools_memory, a2a_tools_inbox, a2a_tools_delegation, a2a_tools_rbac);
  • registered as two ToolSpec entries in workspace/platform_tools/registry.py — same path every other A2A tool uses; structural test test_platform_tools.py continues to pass without modification;
  • RBAC gate is inline in tool_update_agent_card (calling check_memory_write_permission()), matching core's per-tool pattern — core does not have a dispatcher-level permission map;
  • tool functions return JSON-encoded strings instead of dicts — core convention. Tests json.loads() to inspect;
  • no pyproject.toml bump — core's publish-runtime-autobump derives the next wheel version from PyPI on promotion (current PyPI = 0.1.1000, this PR was bumping 0.1.17→0.1.18 which would have collided);
  • 14 test cases ported + extended (drift gates, RBAC denial path, network-exception → structured error, registry contract pins).

mirror-guard CI correctly flagged this PR — leaving the branch in place for archeology, no force-merge / admin-bypass.

Closed by fullstack-engineer per direct landing authority (T4 canvas-user request via orchestrator).

Closing — this repo is mirror-only (see `reference_runtime_repo_is_mirror_only` in saved memory). The canonical edit point for `molecule_runtime/*` is `molecule-ai/molecule-core` at `workspace/*`; the wheel mirror in this repo is regenerated automatically by `publish-runtime.yml` on staging→main promotion. Relocated PR opened in molecule-core: https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1240 The core PR translates this diff to fit the iter-4 layered architecture there: * tool functions live in a new single-concern module `workspace/a2a_tools_identity.py` (alongside `a2a_tools_messaging`, `a2a_tools_memory`, `a2a_tools_inbox`, `a2a_tools_delegation`, `a2a_tools_rbac`); * registered as two `ToolSpec` entries in `workspace/platform_tools/registry.py` — same path every other A2A tool uses; structural test `test_platform_tools.py` continues to pass without modification; * RBAC gate is inline in `tool_update_agent_card` (calling `check_memory_write_permission()`), matching core's per-tool pattern — core does not have a dispatcher-level permission map; * tool functions return JSON-encoded strings instead of dicts — core convention. Tests `json.loads()` to inspect; * no `pyproject.toml` bump — core's `publish-runtime-autobump` derives the next wheel version from PyPI on promotion (current PyPI = 0.1.1000, this PR was bumping 0.1.17→0.1.18 which would have collided); * 14 test cases ported + extended (drift gates, RBAC denial path, network-exception → structured error, registry contract pins). `mirror-guard` CI correctly flagged this PR — leaving the branch in place for archeology, no force-merge / admin-bypass. Closed by fullstack-engineer per direct landing authority (T4 canvas-user request via orchestrator).
Some checks are pending
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 18s
Required
Details
ci / mirror-guard (pull_request) Failing after 20s
ci / unit-tests (pull_request)
Required
ci / lint (pull_request)
Required
ci / build (pull_request)
Required
ci / smoke-install (pull_request)
Required

Pull request closed

Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-ai-workspace-runtime#17