Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(mcp): pass Manufact app-directory readiness checks

merged#2828CedarCopilot

CedarCopilot wants to merge 6 commits into staging from fix/mcp-manufact-readiness

Live on prod, 1 of 12 surfaces degradedTimeline and evidence
  1. Opened
    Sep 19, 2026, 2:18 PM
  2. Sep 19, 2026, 5:53 PM
  3. Merged
    Sep 19, 2026, 6:05 PM
  4. Live on prod
    Sep 19, 2026, 6:05 PM
  5. Observed 1 hour, 12 surfaces, 147 requests
    Sep 19, 2026, 6:05 PM
  6. Mixed signals

    Live on prod, 1 of 12 surfaces degraded

    Sep 19, 2026, 6:05 PM
  7. Pipelines steady after this deploy
    Sep 19, 2026, 6:05 PM

Behaviors Libra is checking

The deprecated `record-message` and `record-tool-call` compatibility tools are explicitly labeled as no-ops and advertise read-only, non-destructive, non-open-world behavior.Not checked
prod
Document reads require `docs:read`, while document writes, deletes, restores, moves, shares, playbook paths, and daily agendas receive the appropriate `docs:read` or `playbook:write` authorization instead of sharing the old mixed-family scope.Not checked
prod
The MCP document surface exposes separate `document-read` and `document-write` tools, replacing the oversized generic `document` tool and obsolete document-manage/document-share wrappers.Not checked
prod
Every externally registered family tool publishes a title and MCP behavioral annotations whose read-only claims agree with the server's mutation classification.Not checked
prod
Flat MCP arguments are normalized into the nested tool schema before mutation classification and scope checks, preventing a flat playbook write from being under-scoped as an ordinary document read.Not checked
prod
The `load-brain-skills` tool is titled as a Cedar brain-skill loader and declares that it is read-only, non-destructive, and confined to Cedar.Not checked
prod

Libra measured 1 of 12 production surfaces on prod. 11 surfaces had no requests at all. Libra checks hourly for 3 days after each deploy.

Summary

Fixes all 7 issues from Manufact's automated app-directory readiness check against Cedar's public MCP server (https://api.mail.cedarcopilot.com/mcp), ahead of submission to the Claude and OpenAI (Codex) app directories.

  • tool-hints-present / tool-title-present / claude-tool-hints-present , every externally-registered MCP tool (all 33: the 28 shared family tools, the 4 external-only document splits, and board) now declares a top-level title and annotations.{readOnlyHint,destructiveHint,openWorldHint} reflecting its real behavior, via one EXTERNAL_TOOL_METADATA table in server.ts that throws loudly if a tool is registered without an entry. The 5 individually-registered tools (load-skill, load-brain-skills, cedar-staff-debug, record-message, record-tool-call) each got their own title/annotations at their own registerTool call.
  • claude-code-metadata-length , the 7 flagged tools (document, table, config-read, debug, run-sql, conversation-read, mail) were trimmed. The reported byte counts turned out to be the source description plus the Armature telemetry hint Cedar's analytics wrapper appends when ANALYTICS_INGEST_API_KEY is set (171 bytes before this PR's telemetry patch, 156 after) , tool-description-length.test.ts was rewritten to measure real UTF-8 bytes (not .length, which undercounts every em dash/curly quote in this codebase's own house style) against budget minus that hint reserve, computed dynamically via appendTelemetryHint('') so it can't drift from the patched package. That stricter check caught two more real, previously-undetected bugs: board's description was also over budget (invisible to Manufact's live scan only because board has no scope mapping yet), and buildSessionInstructions's company-background truncation math was also .length-based and could land a few bytes over the real ceiling.
  • policy-review (telemetry over-collection) , patched @armature-tech/mcp-analytics@0.6.36 (via pnpm patch, same mechanism as the existing postgres/@superglue/client patches) so its telemetry.agent_thinking field description reads as genuinely optional rather than "include on every call... even when marked optional." The underlying Zod schema was already .optional(); only the coercive wording changed. Cedar's own first-party agents (including Cedar staff connecting their own Claude over external OAuth, which goes through this exact same code path) can keep sending it voluntarily.
  • policy-review (document tool scope) , split the external registration of document into four risk-grouped tools: document-read (read/list/search), document-write, document-manage (delete/restore/move), document-share. The internal Mastra documentTool itself is untouched and stays in FAMILY_TOOLS unsplit, because that array is also consumed directly by the in-app chat harness (workflows/chat/harness/mcp-server.ts) and other first-party callers that must keep seeing one consolidated tool , the external registration loop in server.ts explicitly skips document there and registers the four split tools (document-external-tools.ts) in its place instead. scope-map.ts carries matching document-read/write/manage/share entries that preserve the exact per-action scope requirements the single document id used to resolve, plus a document entry retained solely for the in-app viewer-role gate.
  • policy-review (exposed identifiers) , the MCP session instructions text no longer echoes the caller's literal userId/orgId. Identity is already resolved server-side per request; no external tool's input schema asks the model to supply its own userId/orgId as an argument (no-bare-user-id-inputs.test.ts enforces this structurally , every surviving userId-shaped field is a deprecated targetUserId alias for cross-user delegation, never "tell us who you are"). One judgment call: run-sql's free-text SQL query genuinely needs the caller
Show production surfaces and changed-file mapping

Production surfaces

SurfaceRequestsErrorsp95UsersVerdict
db.getConversationIdForThread236 → 1470 → 0 (0%)48 ms → 109 ms0Degraded
Error rate is clean (0 of 147), but p95 latency more than doubled since the deploy. p95 109 ms, was 48 ms.
execute_tool update-conversation-fields3 → 00 → 0 (0%)5230 ms → not measured0No traffic
No requests recorded since this deploy.
trpc.crm.getConversation5 → 00 → 0 (0%)1035 ms → not measured0No traffic
No requests recorded since this deploy.
model_chunk Conversation Field Updater2 → 00 → 0 (0%)1 ms → not measured0No traffic
No requests recorded since this deploy.
/api/trpc/crm.getConversation35 → 00 → 0 (0%)9532 ms → not measured0No traffic
No requests recorded since this deploy.
POST /api/trpc/crm.getConversation23 → 00 → 0 (0%)9519 ms → not measured0No traffic
No requests recorded since this deploy.
enrichment.enrichConversationContacts9 → 00 → 0 (0%)2443 ms → not measured0No traffic
No requests recorded since this deploy.
/api/trpc/crm.searchConversationsMinimal3 → 00 → 0 (0%)9572 ms → not measured0No traffic
No requests recorded since this deploy.
/api/trpc/crm.listConversationDocs0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/crm.getConversations0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/crm.getConversationFieldDefinitions0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
crm.getConversation11 → 00 → 0 (0%)9392 ms → not measured0No traffic
No requests recorded since this deploy.

Changed files → surfaces

  • apps/server/src/mastra/tools/conversation/conversationTool.tsdb.getConversationIdForThreadexecute_tool update-conversation-fieldstrpc.crm.getConversationmodel_chunk Conversation Field Updater/api/trpc/crm.getConversationPOST /api/trpc/crm.getConversation+6
  • apps/server/.claude/skills/collateral/SKILL.mdno production surface mapped
  • apps/server/.claude/skills/documents/SKILL.mdno production surface mapped
  • apps/server/src/mastra/mcp/external/__tests__/flat-args.test.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/__tests__/mutating-actions-are-administrable.test.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/__tests__/no-bare-user-id-inputs.test.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/__tests__/scope-map.test.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/__tests__/server.test.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/__tests__/tool-description-length.test.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/__tests__/tool-hints-match-behavior.test.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/__tests__/tool-inputschema-size.test.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/cedar-staff-debug-tool.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/deprecated-record-shims.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/load-brain-skills-tool.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/load-skill-tool.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/scope-map.tsno production surface mapped
  • apps/server/src/mastra/mcp/external/server.tsno production surface mapped
  • apps/server/src/mastra/tools/__tests__/master-surface.test.tsno production surface mapped
  • apps/server/src/mastra/tools/config/config-read-tool.tsno production surface mapped
  • apps/server/src/mastra/tools/debug/debugTool.tsno production surface mapped
  • apps/server/src/mastra/tools/document/__tests__/documentTool.delete.integration.test.tsno production surface mapped
  • apps/server/src/mastra/tools/document/__tests__/documentTool.test.tsno production surface mapped
  • apps/server/src/mastra/tools/document/__tests__/shareDocumentTool.test.tsno production surface mapped
  • apps/server/src/mastra/tools/document/boardTool.tsno production surface mapped
  • apps/server/src/mastra/tools/document/documentTool.schemas.tsno production surface mapped
  • apps/server/src/mastra/tools/document/documentTool.tsno production surface mapped
  • apps/server/src/mastra/tools/document/shareDocumentTool.tsno production surface mapped
  • apps/server/src/mastra/tools/document/tableTool.tsno production surface mapped
  • apps/server/src/mastra/tools/draft-comms/draftCommsTool.tsno production surface mapped
  • apps/server/src/mastra/tools/family-tool-names.tsno production surface mapped
  • apps/server/src/mastra/tools/mail/mailTool.tsno production surface mapped
  • apps/server/src/mastra/tools/run-sql-query/runSQLTool.tsno production surface mapped
  • apps/server/src/mastra/utils/__tests__/approval.test.tsno production surface mapped
  • apps/server/src/mastra/utils/approval-policy.tsno production surface mapped
  • apps/server/src/mastra/workflows/chat/harness/subagents.tsno production surface mapped
  • apps/server/src/services/access/__tests__/no-model-grants.test.tsno production surface mapped
  • apps/server/src/services/auth/__tests__/target-user-id-reaches-the-authority.test.tsno production surface mapped
  • package.jsonno production surface mapped
  • patches/@armature-tech__mcp-analytics@0.6.36.patchno production surface mapped
  • pnpm-lock.yamlno production surface mapped