CedarCopilot wants to merge 2 commits into staging from fix/mcp-tool-annotations-match-behavior
Libra exercised 2 changed behaviors; none regressed.
Libra ran this behavior against the change and confirmed the check detects when it breaks.
Changed code: server.ts.
Libra ran this behavior against the change and confirmed the check detects when it breaks.
Changed code: server.ts.
Live on prod, watching, 1 day left
Libra has verdicts on 0 of 12 tracked behaviors on prod; 12 are still being checked. Libra checks hourly for 3 days after each deploy.
OpenAI rejected Cedar v1.0.0 in app review: "one or more of your tool's annotations do not appear to match the tool's behavior", and asked for explicit true/false hints with a justification per hint. Every tool already had all three hints set, so the gap was wrong values, not missing ones.
Audited every external tool's actions (source, not descriptions) against its hints and corrected 13 in EXTERNAL_TOOL_METADATA:
| Tool | Change | Reason |
|---|---|---|
| table | openWorld → true | fill subagents use web search/fetch, enrichment, live Slack |
| board | openWorld → true | move/set can POST to a user-configured URL |
| config-write | openWorld → true | create-field (sourceFromCrm) and link-mapping hit the live CRM |
| workspace-write | destructive → true, openWorld → true | hard deletes; Gmail label rename/delete; webhook export config |
| debug | destructive → true | test-run with deliverForReal sends real Slack/email |
| run-sql | readOnly → false, openWorld → true | out writes to S3 and returns a presigned URL; title now "Run a SQL query (SELECT only)" |
| analytics | openWorld → true | monthly-growth calls PostHog |
| conversation-write | destructive → true | merge_conversations deletes source, clear_working_memory wipes, update-fields overwrites |
| slack-read | readOnly → false | read-thread ingests messages into the deal timeline |
| task | openWorld → true | deletes Gmail drafts, relabels threads |
| notify | destructive → true | real sends cannot be unsent |
| draft-comms | destructive → true | draft-email replaces an existing provider draft in place |
| linkedin-write | destructive → true | messages/invitations/InMail cannot be unsent |
Each table comment now states why the hint is set, which is the justification OpenAI asks for.
notify and linkedin-write are marked destructive because sends are irreversible. Revert those two if "destructive" should mean delete/overwrite only.run-sql: alternative is stripping out from the external schema and keeping the read-only hint.send-user-feedback left as is (one post to Cedar's own feedback channel).conversation-read, conversation-analytics, mail write ephemeral scratch buffers but stay read-only. First candidates if OpenAI rejects again.pnpm --filter @zero/server exec vitest run src/mastra/mcp/external/__tests__/tool-hints-match-behavior.test.ts src/mastra/mcp/external/__tests__/server.test.ts passes (54 tests).tools/list on the external MCP should show the new values, e.g. linkedin-write with destructiveHint: true, slack-read with readOnlyHint: false. Then resubmit from the OpenAI Platform dashboard.🤖 Generated with Claude Code
The PR is not safe to merge until the table, run-sql, and additive-send annotations are reconciled with actual behavior and the metadata definitions.
Libra has not measured any production surfaces for this change yet.