Error relay: the missing scope, the field errors and the wait in Postulo's problem documents never reach the agent #15
Labels
No labels
bug
documentation
enhancement
security
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-mcp#15
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo-mcp at
0b29e70b9, reading howPostulo._callturns a refusal into aPostuloError, against the core's problem documents at postulo 95348908d.Since postulo/postulo#296 the core refuses with RFC 9457 problem documents that carry what a client needs to act: the scope a token lacks, the list of field errors, the wait before retrying. The MCP client was written before that and keeps only a fixed sentence or the
detailstring, so the agent is told less than Postulo said, and in one case is pointed at data it is never given.src/postulo_mcp/client.py:117-125(Postulo._call);src/postulo_mcp/client.py:134-143(_detail);tests/test_server.py:347-354(test_other_failures_carry_what_the_instance_said, which still feeds the pre-#296 shape{"detail": "status: not a status"})ScopedAuth(postuloapi/auth.py:86-105) answers "This token does not have the 'write' scope." withkind="insufficient-scope"andscope._callfolds 401 and 403 into "Postulo refused the token. It may be revoked, expired, or without the scope this needs (403)." and drops the detail, so the person is not told which box to tick.detailis built by_validation_sentence(postuloapi/problems.py:244-261) and reads "Refused: due_at. Seeerrorsfor what is wrong with each."; the reasons are in theerrorsmember (api/problems.py:234-241)._detailrelays the sentence and dropserrors, so an agent that sentadd_reminderwith a malformeddue_atis told to look at a list it does not have, and guesses.retry_afterin the body andRetry-Afteras a header (api/api.py:146-149,api/auth.py:29-37,api/problems.py:220-223). Thedetailnames the rate but not the remaining wait, and_callkeeps neither the member nor the header, so the agent cannot know when to try again.api/routers/reminders.py:44), which says which of the ids in anadd_remindercall was wrong._call, parse the body as a problem document when the content type isapplication/problem+json(or the body is a JSON object withdetail). Keep the 401 sentence generic. For 403, relaydetail(andscope). For 422, append a short rendering oferrors, oneloc: msgper entry, capped at a few. For 429, add "try again in N seconds" fromretry_afteror theRetry-Afterheader. For 404, usedetailwhen there is one. Update the test fake's failure shapes to problem documents.Done when
tests/test_server.pyhas tests that feed the client a 403 problem document namingscope: "write"and expect thePostuloErrortext to containwrite; a 422 witherrors: [{"loc": ["body", "payload", "due_at"], "msg": "..."}]and expectdue_atand the message; a 429 withretry_after: 30and expect30. Each fails today.Postulo._callreads every answer whole, with no size ceiling and no deadline for the whole exchange #18