5.9-b Write tools, non-destructive (phase 2)
- Dominant language
- JavaScript
- Stars
- 400
- Forks
- 89
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 149
Description
**Parent:** #7678 (5.9 Notifications and user self-service)
**Tool file:** new `forge/ee/lib/mcp/tools/user.js`
`readOnlyHint: false`, `destructiveHint: false`. Sibling actions that share one concept are consolidated into a single tool with an `action` enum (or an optional target id), following 5.1's pattern.
| Tool | Endpoint(s) | Scope | Annotation |
|---|---|---|---|
| `platform_set_notification_read_state` (single via `notificationId`, or bulk via `ids`) | `PUT /user/notifications/:notificationId`, `PUT /user/notifications` | `user:edit` (self) | write |
| `platform_respond_to_team_invitation` (`action`: `accept`\|`reject`) | `PATCH /user/invitations/:invitationId` (accept), `DELETE /user/invitations/:invitationId` (reject) | `user:edit` (self) | write |
| `platform_update_profile` | `PUT /user` | `user:edit` (self) | write |
**Design notes:**
- `set_notification_read_state` consolidates the single (`PUT /user/notifications/:notificationId`) and bulk (`PUT /user/notifications`) variants: with a `notificationId` the tool calls the single-notification route with `{ read }`; otherwise it calls the bulk route with `{ ids, read }`. There is **no bare "mark all" route** - the bulk route takes an explicit array of notification ids. `read` may be `true` or `false` (mark read or unread), so the tool is named for setting the state, not just marking read.
- `respond_to_team_invitation` consolidates accept and reject on the same invitation resource. `accept` maps to `PATCH`, `reject` maps to `DELETE`; both self-scope via `byId(id, request.session.User)` (404 if not owner). Reject only declines an invitation the caller received, so it is modelled as a non-destructive response rather than a destructive delete.
- `platform_update_profile` should expose only safe profile fields (name, defaultTeam), not privileged fields that only `/users/:userId` (admin) can set. (The route itself also accepts `username`/`email`/`tcs_accepted` as self-service, but v1 exposes only name/defaultTeam.)
**Tool definitions (description + zod inputSchema):**
```js
platform_set_notification_read_state: {
description: 'Set the read state of the caller notifications. Provide notificationId to set the state of one notification, or ids to set several; there is no bare "mark all" shortcut, so bulk changes require an explicit list of ids. read=true marks read, read=false marks unread.',
inputSchema: z.object({
notificationId: z.string().optional().describe('Notification hashid'),
ids: z.array(z.string()).optional().describe('Array of notification hashids'),
read: z.boolean().describe('Read state to set: true marks read, false marks unread')
})
}
platform_respond_to_team_invitation: {
description: 'Respond to a team invitation the caller received. accept joins the team; reject declines the invitation. Neither action requires any additional input.',
inputSchema: z.object({
invitationId: z.string().describe('Invitation hashid the caller received'),
action: z.enum(['accept', 'reject']).describe('Whether to accept or reject the invitation')
})
}
platform_update_profile: {
description: 'Update the current user profile. Only name and defaultTeam are exposed; other self-service profile fields are not editable through this tool.',
inputSchema: z.object({
name: z.string().optional().describe('New display name for the current user'),
defaultTeam: z.string().optional().describe('Team hashid to set as the current user default team')
})
}
```
**Tests:**
- Write tools rejected for read-only PAT.
- `set_notification_read_state` routes to the single-notification endpoint when `notificationId` is supplied, and to the bulk endpoint with `ids` otherwise; both accept `read: true` and `read: false`.
- `respond_to_team_invitation` routes `accept` to `PATCH` and `reject` to `DELETE`.
- Notification/invitation tools operate only on the caller's own records.
---
Contributor guide
Research direction
Implement the tools in forge/ee/lib/mcp/tools/user.js, first comparing the sibling 5.1 pattern and the listed user endpoints. Run the write-tool tests, including read-only PAT rejection, routing for notification and invitation actions, both read states, and self-record restrictions. Done means all three tools expose the specified inputs and annotations and pass those behaviors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- api, backend, tooling
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100