remove_additional_party
Removes the link between an opportunity or project and an additional party without deleting the party. Requires confirmation and is reversible by re-adding the link.
Instructions
Remove an additional-party link between an opportunity/project and a party. The party itself is NOT deleted. Requires confirm=true. Reversible by re-adding via add_additional_party. Idempotent on retry: response is {removed: true, alreadyRemoved: false, entity, entityId, partyId} on a fresh remove or {removed: true, alreadyRemoved: true, ...} if the link was already gone (Capsule's 404 is caught and converted).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| entity | Yes | Which entity has the additional-party links. Use 'kases' for projects. | |
| entityId | Yes | ||
| partyId | Yes | ||
| confirm | Yes | Must be set to true. Removes the link between the entity and the additional party. The party itself is not deleted. Reversible by re-adding the link. |
Implementation Reference
- src/tools/relationships.ts:118-142 (handler)The handler function that executes the tool logic. Validates confirm=true, then uses the idempotent helper to DELETE /<entity>/{entityId}/parties/{partyId}. Returns {removed, alreadyRemoved, entity, entityId, partyId} on success, or {removed, alreadyRemoved: true} if the link was already gone (404 caught by idempotent).
export async function removeAdditionalParty(input: z.infer<typeof removeAdditionalPartySchema>) { if (input.confirm !== true) { throw new Error("remove_additional_party requires confirm: true"); } // Capsule's 404 here covers both "party doesn't exist" and "party // isn't linked to this entity" — both are observationally "the link // doesn't exist", treat both as already-removed. return idempotent( () => capsuleDelete(`/${input.entity}/${input.entityId}/parties/${input.partyId}`), () => ({ removed: true, alreadyRemoved: false, entity: input.entity, entityId: input.entityId, partyId: input.partyId, }), () => ({ removed: true, alreadyRemoved: true, entity: input.entity, entityId: input.entityId, partyId: input.partyId, }), ); } - src/tools/relationships.ts:109-116 (schema)Zod schema defining the input: entity (opportunities|kases), entityId (positive int), partyId (positive int), confirm (literal true, using shared confirmFlag helper).
export const removeAdditionalPartySchema = z.object({ entity: RelationshipEntity, entityId: z.number().int().positive(), partyId: z.number().int().positive(), confirm: confirmFlag().describe( "Must be set to true. Removes the link between the entity and the additional party. The party itself is not deleted. Reversible by re-adding the link.", ), }); - src/server.ts:560-566 (registration)Registration of the MCP tool 'remove_additional_party' with its description, schema, and handler via the registerTool helper.
registerTool( server, "remove_additional_party", "Remove an additional-party link between an opportunity/project and a party. The party itself is NOT deleted. Requires confirm=true. Reversible by re-adding via add_additional_party. Idempotent on retry: response is `{removed: true, alreadyRemoved: false, entity, entityId, partyId}` on a fresh remove or `{removed: true, alreadyRemoved: true, ...}` if the link was already gone (Capsule's 404 is caught and converted).", removeAdditionalPartySchema, removeAdditionalParty, ); - src/capsule/idempotent.ts:56-69 (helper)Idempotency helper used by the handler. Tries the DELETE op; if it throws a 404 (using default isCapsule404 predicate), returns the alreadyRemoved shape instead of propagating the error.
export async function idempotent<T>( op: () => Promise<unknown>, success: () => T, alreadyDone: () => T, isAlreadyDoneError: (err: unknown) => boolean = isCapsule404, ): Promise<T> { try { await op(); return success(); } catch (err) { if (isAlreadyDoneError(err)) return alreadyDone(); throw err; } } - src/tools/confirm-flag.ts:22-27 (helper)Shared confirmFlag helper that produces a z.literal(true) schema with a custom error message, used by the schema's confirm field.
const CONFIRM_REQUIRED_MESSAGE = "confirm: true is required to perform this destructive operation (set the parameter explicitly to acknowledge the destructive intent)"; export function confirmFlag(): z.ZodLiteral<true> { return z.literal(true, { error: () => CONFIRM_REQUIRED_MESSAGE }); }