All files / src/tools returns.ts

85.71% Statements 24/28
50% Branches 19/38
88.88% Functions 8/9
85.71% Lines 24/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210          2x 13x                                     4x 4x 3x   4x 2x     1x       1x           1x                               1x         13x                         2x 2x   1x             1x                 1x                       1x         13x                                                       2x 2x         1x               1x         13x                                                                                                  
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import * as z from "zod/v4";
import type { BolClient } from "../bol-client.js";
import { toTextResult, toErrorResult } from "../tool-result.js";
 
export const registerReturnTools = (server: McpServer, client: BolClient): void => {
  server.registerTool(
    "list_returns",
    {
      title: "List Returns",
      description:
        "List returns from bol.com. Filter by handled status and fulfilment method. " +
        "Unhandled returns require action — use handle_return to process them.",
      annotations: { readOnlyHint: true, openWorldHint: true },
 
      inputSchema: z.object({
        page: z.number().int().min(1).default(1).describe("Page number (1-based)."),
        handled: z.boolean().optional().describe("Filter by handled status. true = handled, false = unhandled."),
        fulfilmentMethod: z
          .enum(["FBR", "FBB"])
          .optional()
          .describe("Filter by fulfilment method."),
      }),
    },
    async ({ page, handled, fulfilmentMethod }) => {
      try {
        const response = await client.getReturns(page, handled, fulfilmentMethod);
        const returns = response.returns ?? [];
 
        if (returns.length === 0) {
          return toTextResult("No returns found.");
        }
 
        return toTextResult(
          [
            `Returns (page ${page}): ${returns.length} results`,
            ...returns.map((r) =>
              [
                `  - Return ${r.returnId}`,
                r.registrationDateTime ? `    Registered: ${r.registrationDateTime}` : null,
                r.fulfilmentMethod ? `    Fulfilment: ${r.fulfilmentMethod}` : null,
                `    Items: ${r.returnItems.length}`,
                ...r.returnItems.map((item) =>
                  [
                    `      ${item.ean} - ${item.title ?? "unknown"} x${item.expectedQuantity}`,
                    item.returnReason ? `        Reason: ${item.returnReason.mainReason}${item.returnReason.detailedReason ? ` - ${item.returnReason.detailedReason}` : ""}` : null,
                    item.handled !== undefined ? `        Handled: ${item.handled}` : null,
                  ]
                    .filter(Boolean)
                    .join("\n"),
                ),
              ]
                .filter(Boolean)
                .join("\n"),
            ),
          ].join("\n"),
          { returns } as Record<string, unknown>,
        );
      } catch (error) {
        return toErrorResult(error);
      }
    },
  );
 
  server.registerTool(
    "get_return",
    {
      title: "Get Return Details",
      description:
        "Get detailed information about a specific return including return items, reasons, tracking, and processing results.",
      annotations: { readOnlyHint: true, openWorldHint: true },
 
      inputSchema: z.object({
        returnId: z.string().min(1).describe("The bol.com return ID."),
      }),
    },
    async ({ returnId }) => {
      try {
        const ret = await client.getReturn(returnId);
 
        return toTextResult(
          [
            `Return: ${ret.returnId}`,
            ret.registrationDateTime ? `Registered: ${ret.registrationDateTime}` : null,
            ret.fulfilmentMethod ? `Fulfilment: ${ret.fulfilmentMethod}` : null,
            `Items (${ret.returnItems.length}):`,
            ...ret.returnItems.map((item) =>
              [
                `  - ${item.ean}: ${item.title ?? "unknown"} x${item.expectedQuantity}`,
                `    Order: ${item.orderId}${item.rmaId ? ` / RMA: ${item.rmaId}` : ""}`,
                item.returnReason
                  ? `    Reason: ${item.returnReason.mainReason}${item.returnReason.detailedReason ? ` - ${item.returnReason.detailedReason}` : ""}${item.returnReason.customerComments ? ` ("${item.returnReason.customerComments}")` : ""}`
                  : null,
                item.trackAndTrace ? `    Tracking: ${item.trackAndTrace}` : null,
                item.handled !== undefined ? `    Handled: ${item.handled}` : null,
                item.processingResults
                  ? item.processingResults.map((pr) => `    Processing: ${pr.processingResult} / ${pr.handlingResult} (qty: ${pr.quantity})`).join("\n")
                  : null,
              ]
                .filter(Boolean)
                .join("\n"),
            ),
          ]
            .filter(Boolean)
            .join("\n"),
          ret as Record<string, unknown>,
        );
      } catch (error) {
        return toErrorResult(error);
      }
    },
  );
 
  server.registerTool(
    "handle_return",
    {
      title: "Handle Return",
      description:
        "Handle/process a return item by its RMA ID. The RMA ID can be found in the return items from get_return. " +
        "Set the handling result and quantity returned. " +
        "Valid handling results: RETURN_RECEIVED, EXCHANGE_PRODUCT, RETURN_DOES_NOT_MEET_CONDITIONS, REPAIR_PRODUCT, CUSTOMER_KEEPS_PRODUCT_PAID, STILL_APPROVED. " +
        "Returns a process status — the return is handled asynchronously. " +
        "Always review the return details with get_return before handling.",
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
 
      inputSchema: z.object({
        rmaId: z.string().min(1).describe("The RMA ID of the return item (found in return items from get_return)."),
        handlingResult: z
          .enum([
            "RETURN_RECEIVED",
            "EXCHANGE_PRODUCT",
            "RETURN_DOES_NOT_MEET_CONDITIONS",
            "REPAIR_PRODUCT",
            "CUSTOMER_KEEPS_PRODUCT_PAID",
            "STILL_APPROVED",
          ])
          .describe("How the return was handled."),
        quantityReturned: z.number().int().min(1).max(9999).describe("Number of items returned."),
      }),
    },
    async ({ rmaId, handlingResult, quantityReturned }) => {
      try {
        const result = await client.handleReturn(rmaId, {
          handlingResult,
          quantityReturned,
        });
 
        return toTextResult(
          [
            `Return RMA ${rmaId} handling initiated: ${handlingResult} (${quantityReturned} items)`,
            `Process status: ${result.processStatusId} (${result.status})`,
          ].join("\n"),
          result as Record<string, unknown>,
        );
      } catch (error) {
        return toErrorResult(error);
      }
    },
  );
 
  server.registerTool(
    "create_return",
    {
      title: "Create Return",
      description:
        "Create a return and automatically handle it with the provided handling result. " +
        "When successfully created, the resulting return ID is provided in the process status. " +
        "Returns a process status — the return is created asynchronously.",
      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
 
      inputSchema: z.object({
        orderItemId: z.string().min(1).describe("The order item ID to create a return for."),
        quantityReturned: z.number().int().min(1).max(9999).describe("The quantity of items returned."),
        handlingResult: z
          .enum([
            "RETURN_RECEIVED",
            "EXCHANGE_PRODUCT",
            "RETURN_DOES_NOT_MEET_CONDITIONS",
            "REPAIR_PRODUCT",
            "CUSTOMER_KEEPS_PRODUCT_PAID",
          ])
          .describe("How the return should be handled."),
      }),
    },
    async ({ orderItemId, quantityReturned, handlingResult }) => {
      try {
        const result = await client.createReturn({
          orderItemId,
          quantityReturned,
          handlingResult,
        });
 
        return toTextResult(
          [
            `Return created for order item ${orderItemId}`,
            `Handling: ${handlingResult} (${quantityReturned} items)`,
            `Process status: ${result.processStatusId} (${result.status})`,
            result.entityId ? `Return ID: ${result.entityId}` : null,
          ]
            .filter(Boolean)
            .join("\n"),
          result as Record<string, unknown>,
        );
      } catch (error) {
        return toErrorResult(error);
      }
    },
  );
};