salesforce-mcp v7.1.0 — P1/P6 zip-prefix bug blocks operator-intent surface for Profiles/PermissionSets

Summary

salesforce-mcp v7.1.0 ships the four R48 zero-limitation operator-intent tools (P1 sf_get_profile_full_metadata, P2 sf_validate_user_permissions, P3 sf_resolve_permission_dependencies, P5 sf_disable_profile_permissions / sf_enable_profile_permissions, P6 sf_get_permission_set_full_metadata / sf_get_muting_permission_set_full_metadata) — but two defects make the Profile/PermissionSet write surface non-functional in the cutover build.

Defect 1 — P5 fail-closed on retired permission names

P5 (sf_disable_profile_permissions) wraps P2 pre-flight, then rejects the entire batch if any name is bucketed retired instead of auto-skipping retired names and proceeding with the valid subset. Operator must read the rejection, drop retired names, re-call. Two round-trips minimum — breaks the documented R48 “MCP absorbs all iteration / single round-trip” promise.

Reproduction (06-May-2026, fullcopy):

sf_disable_profile_permissions(profile="System Administrator Clone",
  names=["ManageUsers", ..., "ManageMobileConfigurations", ...18 others],
  org_alias="fullcopy")
→ status=error, skipped_retired=[{name: ManageMobileConfigurations, retired_in_api: v58, ...}],
  would_have_acted_on=[18 valid names]  # but did NOT act

Defect 2 — P1 zip-prefix bug (HARD, blocks all writes)

P1 (sf_get_profile_full_metadata) — also called internally by P5 to read current state — looks for the retrieved Profile at zip path unpackaged/profiles/{name}.profile but the actual zip returned by Salesforce contains it at profiles/{name}.profile (no unpackaged/ prefix). Returns status=error with misleading “Check fullname spelling” message while the same response’s zip_namelist proves the profile WAS retrieved successfully.

Reproduction:

sf_get_profile_full_metadata(profile_name="System Administrator Clone", org_alias="fullcopy")
→ status=error, zip_namelist=["profiles/System Administrator Clone.profile", "package.xml"],
  insights="Expected entry 'unpackaged/profiles/...' not found in zip"

Suspected root cause: Modern simple-salesforce mdapi.retrieve() (Tooling/REST path) returns zip entries WITHOUT unpackaged/ prefix; legacy SOAP MDAPI returned WITH prefix. P1/P6 readers likely tested against SOAP-shape fixtures or coded against SOAP-era documentation.

Single-line candidate fix (tools/profile_complete/operations.py):

candidates = [f"profiles/{name}.profile", f"unpackaged/profiles/{name}.profile"]
matched = next((c for c in candidates if c in zip_namelist), None)

P6 readers (sf_get_permission_set_full_metadata, sf_get_muting_permission_set_full_metadata) almost certainly carry the same defect — added together in Bible v19.1.10 Check 15.

Why Phase 9 census missed it

Cutover census validated symmetric-reader presence (method exists + returns a structured response) but NOT correctness against a real retrieved zip. Add a real-zip-roundtrip check to Phase 9: assert P1 returns parsed Profile shape (not error) for at least one known-existing profile per env.

Behavior to expect / workaround until v7.1.1

  • ❌ Do NOT fall back to raw sf_update_profile_metadata for operator-intent flows — defeats the entire R48 validation purpose.
  • ❌ Do NOT script around with live-cred Python — violates tool-ambiguity protocol.
  • ✅ Halt. Document. Wait for v7.1.1.
  • ✅ For the rare unavoidable case, use Setup UI manually (~3 min for a 19-perm tightening).

What IS confirmed working in v7.1.0

  • health_check reports 7.1.0 / Bible-v19.1.10 / 684 tools / both orgs healthy
  • All four P1-P6 tools resolved via ToolSearch
  • sf_validate_user_permissions (P2) correctly buckets retired names with retired_in_api + retirement_reason metadata
  • mdapi.retrieve() itself succeeds — zip is fetched, contains the requested profile
  1. Patch P1 + both P6 readers to try both zip-path candidates (no-prefix first).
  2. Add Defect 1 policy decision — recommended: opt-in skip_retired_names: bool = False param (preserves strict default, honors R48 when caller opts in).
  3. Add real-zip-roundtrip regression test + Phase 9 census check.
  4. Cutover v7.1.1 → resume profile tightening with same call shape.

Time-saving for next operator

This bug post-mortem at /root/aj-ea/outputs/documents/mcp-tool-limitation-salesforce-v7.1.0-p1-zip-prefix-bug-06-may-2026.md saves 30-45 min of debugging on next encounter — operator goes straight to “this is the v7.1.0 P1 prefix bug, halt and wait for v7.1.1” instead of re-discovering it.