itfin-mcp 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,7 +78,7 @@ In CLI mode each tool is a command without the `itfin_` prefix: `itfin_get_entri
78
78
  | `itfin_get_entries` | Your time entries, and which days are open, closed or in the future |
79
79
  | `itfin_create_entry` / `itfin_update_entry` / `itfin_delete_entry` | Change time entries |
80
80
  | `itfin_get_workspace_settings` | Minimum comment length; whether reopen requests are on |
81
- | `itfin_request_reopen` / `itfin_list_reopen_requests` | Ask a manager to reopen closed days; see your requests |
81
+ | `itfin_request_reopen` / `itfin_list_reopen_requests` / `itfin_cancel_reopen_request` | Ask a manager to reopen closed days; see or cancel your requests |
82
82
  | `itfin_list_leave_types` | Leave types and reasons you can use |
83
83
  | `itfin_request_leave` | Request full days, or some hours of one day |
84
84
  | `itfin_list_leave_requests` / `itfin_cancel_leave_request` | See or cancel your leave requests |
package/dist/server.js CHANGED
@@ -146,7 +146,9 @@ export function createItfinServer(deps) {
146
146
  return me.Id;
147
147
  }
148
148
  async function reopenRequests() {
149
- return itfin.get("/v1/requests/my", { "filter[requestType]": "OpenReporting", page: 1, size: 50 });
149
+ const requests = await itfin.get("/v1/requests/my", { "filter[requestType]": "OpenReporting", page: 1, size: 50 });
150
+ // Also filtered here, so itfin_cancel_reopen_request can't reach a leave request if ITFin ignores the filter.
151
+ return requests.filter((r) => (r.RequestType ?? "OpenReporting") === "OpenReporting");
150
152
  }
151
153
  async function reopenRequestsCovering(date) {
152
154
  return (await reopenRequests()).map(toReopenRequest).filter((r) => r.from <= date && date <= r.to);
@@ -348,6 +350,23 @@ export function createItfinServer(deps) {
348
350
  }
349
351
  return fileReopenRequest({ from: args.from, to: args.to, reason: args.reason });
350
352
  }));
353
+ server.registerTool("itfin_cancel_reopen_request", {
354
+ title: "Cancel a reopen request",
355
+ description: "Cancels (deletes) one of the user's reopen requests by id (from itfin_list_reopen_requests). Only call this when the user asked to cancel that request. " +
356
+ "Works for pending requests; whether an approved one can still be cancelled is up to ITFin.",
357
+ inputSchema: { id: z.number().int() },
358
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
359
+ }, ({ id }) => handle(async () => {
360
+ // Look the id up among the user's reopen requests, so this tool can't cancel other kinds of requests.
361
+ const request = (await reopenRequests()).find((r) => r.Id === id);
362
+ if (!request)
363
+ throw new ToolError("NOT_FOUND", `No reopen request ${id} among the user's recent reopen requests.`);
364
+ if ([...CLOSED_REQUEST_STATUSES, "Declined"].includes(request.Status)) {
365
+ throw new ToolError("VALIDATION", `Reopen request ${id} is already ${request.Status}.`, { request: toReopenRequest(request) });
366
+ }
367
+ await itfin.request("DELETE", `/v1/requests/${id}`);
368
+ return { cancelled: id, request: toReopenRequest(request) };
369
+ }));
351
370
  /** Leave types the user can request, as the web app's "Request time off" form offers them. */
352
371
  async function leaveTypes() {
353
372
  const [types, settings] = await Promise.all([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itfin-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "ITFin for AI agents, as a local MCP server or a CLI: projects, time entries, leave and reopen requests",
5
5
  "keywords": [
6
6
  "mcp",