@remit/mcp-server 0.7.2 → 0.8.0

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/dist/deps.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { RemitClient } from "@remit/backend/client";
2
- import type { IAccountRepository, IFilterRepository, IMailboxRepository, IMessageRepository, IOrganizeJobRequestRepository, IThreadMessageRepository, OrganizeJobEvent, QueueProducer } from "@remit/data-ports";
2
+ import type { IAccountRepository, IAddressRepository, IFilterRepository, IMailboxRepository, IMessageRepository, IOrganizeJobRequestRepository, IThreadMessageRepository, OrganizeJobEvent, QueueProducer } from "@remit/data-ports";
3
3
  import type { BodyPartMaterializer, BodySyncCue, MailboxQueueService, MaterializeLogger, OrganizeJobLogger } from "@remit/mailbox-service";
4
4
  import type { StorageService } from "@remit/storage-service";
5
5
  export declare const FOLDER_SETTLE_TIMEOUT_MS = 20000;
@@ -17,7 +17,8 @@ export interface FilterToolDeps {
17
17
  export interface McpDeps {
18
18
  account: Pick<IAccountRepository, "get" | "listAllByAccountConfig">;
19
19
  mailbox: Pick<IMailboxRepository, "get" | "listAllByAccount" | "findByPath" | "resolveAccountId">;
20
- message: Pick<IMessageRepository, "get" | "describe">;
20
+ address: Pick<IAddressRepository, "countCategoryOverridesSince">;
21
+ message: Pick<IMessageRepository, "get" | "describe" | "countClassifiedSince">;
21
22
  threadMessage: Pick<IThreadMessageRepository, "listByMailbox" | "listByThread" | "searchByDate">;
22
23
  filter: Pick<IFilterRepository, "listPageByAccountConfig" | "refreshExpiry" | "create" | "get" | "update">;
23
24
  organizeJobRequest: Pick<IOrganizeJobRequestRepository, "create" | "get">;
package/dist/deps.js CHANGED
@@ -3,6 +3,7 @@ export const FOLDER_POLL_INTERVAL_MS = 500;
3
3
  export const mcpDepsOf = (client, { logger, settle }) => ({
4
4
  account: client.account,
5
5
  mailbox: client.mailbox,
6
+ address: client.address,
6
7
  message: client.message,
7
8
  threadMessage: client.threadMessage,
8
9
  filter: client.filter,
@@ -1,7 +1,8 @@
1
1
  import { NotFoundError } from "@remit/data-ports/errors";
2
2
  import { AddressRole, ContentDisposition, MediaType, } from "@remit/domain-enums";
3
- import { materializeBodyParts, ownedMessages } from "@remit/mailbox-service";
3
+ import { materializeBodyParts, ownedMessages, readClassifierStats, } from "@remit/mailbox-service";
4
4
  import { z } from "zod";
5
+ import { toolError } from "./tool-result.js";
5
6
  export const MAX_BODY_BYTES = 20 * 1024;
6
7
  const DEFAULT_PAGE_SIZE = 50;
7
8
  const MAX_PAGE_SIZE = 50;
@@ -21,7 +22,17 @@ const continuationToken = z
21
22
  .optional()
22
23
  .describe("Opaque cursor from a previous response's continuationToken");
23
24
  const order = z.enum(["asc", "desc"]).optional().describe("Sort order by date");
24
- const toThreadRow = (row) => ({
25
+ const classificationOf = (message, detail) => ({
26
+ classificationState: message.classificationState,
27
+ category: message.category,
28
+ scores: detail === "All"
29
+ ? message.classifierScores
30
+ : message.classifierScores.filter((score) => score.confident),
31
+ });
32
+ const authOf = (message) => message.authResult === undefined
33
+ ? { state: "NotParsed" }
34
+ : { state: "Parsed", ...message.authResult };
35
+ const toThreadRow = (row, message, detail) => ({
25
36
  threadId: row.threadId,
26
37
  messageId: row.messageId,
27
38
  mailboxId: row.mailboxId,
@@ -33,11 +44,26 @@ const toThreadRow = (row) => ({
33
44
  isRead: row.isRead,
34
45
  hasAttachment: row.hasAttachment,
35
46
  hasStars: row.hasStars,
47
+ classification: classificationOf(message, detail),
48
+ auth: authOf(message),
36
49
  });
37
- const toPage = (result) => ({
38
- items: result.items.map(toThreadRow),
39
- continuationToken: result.continuationToken,
40
- });
50
+ const toPage = async (deps, result, detail) => {
51
+ const messages = new Map((await deps.message.get(result.items.map((row) => row.messageId))).map((message) => [message.messageId, message]));
52
+ const items = result.items.flatMap((row) => {
53
+ const message = messages.get(row.messageId);
54
+ return message === undefined ? [] : [toThreadRow(row, message, detail)];
55
+ });
56
+ if (items.length !== result.items.length) {
57
+ const missing = result.items
58
+ .map((row) => row.messageId)
59
+ .filter((messageId) => !messages.has(messageId));
60
+ throw new NotFoundError(`Message not found: ${missing.join(", ")}`);
61
+ }
62
+ return {
63
+ items,
64
+ continuationToken: result.continuationToken,
65
+ };
66
+ };
41
67
  const addressesByRole = (addresses, role) => addresses
42
68
  .filter((address) => address.addressRole === role)
43
69
  .sort((left, right) => left.addressOrder - right.addressOrder)
@@ -98,12 +124,12 @@ export const registerReadTools = (server, deps, accountConfigId) => {
98
124
  order,
99
125
  },
100
126
  annotations: READ_ONLY,
101
- }, async (args) => asJson(toPage(await deps.threadMessage.listByMailbox(accountConfigId, args.mailboxId, {
127
+ }, async (args) => asJson(await toPage(deps, await deps.threadMessage.listByMailbox(accountConfigId, args.mailboxId, {
102
128
  order: args.order ?? "desc",
103
129
  limit: args.limit ?? DEFAULT_PAGE_SIZE,
104
130
  continuationToken: args.continuationToken,
105
131
  excludeDeleted: true,
106
- }))));
132
+ }), "Confident")));
107
133
  server.registerTool("search_threads", {
108
134
  description: "Search the user's mail by free text, with optional sender, subject and state filters; narrow to one folder with mailboxId.",
109
135
  inputSchema: {
@@ -122,7 +148,7 @@ export const registerReadTools = (server, deps, accountConfigId) => {
122
148
  order,
123
149
  },
124
150
  annotations: READ_ONLY,
125
- }, async (args) => asJson(toPage(await deps.threadMessage.searchByDate(accountConfigId, {
151
+ }, async (args) => asJson(await toPage(deps, await deps.threadMessage.searchByDate(accountConfigId, {
126
152
  query: args.query,
127
153
  from: args.from,
128
154
  subject: args.subject,
@@ -137,7 +163,7 @@ export const registerReadTools = (server, deps, accountConfigId) => {
137
163
  ? new Set([args.mailboxId])
138
164
  : undefined,
139
165
  excludeDeleted: true,
140
- }))));
166
+ }), "Confident")));
141
167
  server.registerTool("get_thread", {
142
168
  description: "Fetch the messages of one conversation, oldest first, at envelope level.",
143
169
  inputSchema: {
@@ -146,12 +172,27 @@ export const registerReadTools = (server, deps, accountConfigId) => {
146
172
  continuationToken,
147
173
  },
148
174
  annotations: READ_ONLY,
149
- }, async (args) => asJson(toPage(await deps.threadMessage.listByThread(args.threadId, accountConfigId, {
175
+ }, async (args) => asJson(await toPage(deps, await deps.threadMessage.listByThread(args.threadId, accountConfigId, {
150
176
  order: "asc",
151
177
  limit: args.limit ?? DEFAULT_PAGE_SIZE,
152
178
  continuationToken: args.continuationToken,
153
179
  excludeDeleted: true,
154
- }))));
180
+ }), "All")));
181
+ server.registerTool("classifier_stats", {
182
+ description: "Count what the classifier decided for mail that arrived on or after a date: messages examined, messages per category, confident scores per label, and how often the user corrected it.",
183
+ inputSchema: {
184
+ since: z
185
+ .string()
186
+ .min(1)
187
+ .describe("Start of the window, an ISO 8601 date-time with zone offset, such as 2026-11-01T00:00:00+01:00"),
188
+ },
189
+ annotations: READ_ONLY,
190
+ }, async (args) => {
191
+ const stats = await readClassifierStats(deps, accountConfigId, args.since);
192
+ if (!stats.ok)
193
+ return toolError(`${stats.message}.`);
194
+ return asJson(stats.value);
195
+ });
155
196
  server.registerTool("get_message", {
156
197
  description: `Read one message: envelope, flags, attachment metadata and its plain-text body, up to ${MAX_BODY_BYTES / 1024} KB.`,
157
198
  inputSchema: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/mcp-server",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "The remit MCP server: mail tools over streamable HTTP and the OAuth authorization server that guards them.",
5
5
  "type": "module",
6
6
  "exports": {