Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ shows the set your scope allows, with per-tool descriptions.
| --- | --- |
| `send_message` | Send a new email. When the agent's outbound policy or content scan holds it for review, the message is held and returns `status: pending_review` instead of `sent`. |
| `reply_to_message` | Reply to a message — one the agent received (replies to its sender) or one it sent (continues the thread to the original recipients). Preserves In-Reply-To / References for thread continuity. |
| `list_messages` | List mail; pass `deleted:true` to list trash. Filter by `read_status`; cursor-paginated (`cursor` + `limit` in, `next_cursor` out). |
| `list_messages` | List mail; pass `deleted:true` to list trash. Filter by `read_status` (unread / read / all) and sender with reserved-word-safe `from_`; cursor-paginated (`cursor` + `limit` in, `next_cursor` out). |
| `restore_message` | Restore a soft-deleted message and resume its retention clock. |
| `get_message` | Fetch full body, headers, and attachment metadata for one message. |
| `get_attachment` | Get one attachment's metadata + a short-lived `download_url` (fetch the bytes out of band); `inline: true` returns base64 `data` for small files (≤256 KB). |
Expand Down
3 changes: 2 additions & 1 deletion mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
],
"scripts": {
"build": "tsc",
"test": "vitest run",
"test": "vitest run && npm run test:types",
"test:types": "tsc -p tsconfig.type-tests.json",
"prepublishOnly": "npm run build"
},
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion mcp/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ export class McpClient {
direction?: "inbound" | "outbound" | "all";
readStatus?: "unread" | "read" | "all";
sort?: "asc" | "desc";
from?: string;
from_?: string;
subjectContains?: string;
conversationId?: string;
since?: string;
Expand Down
6 changes: 3 additions & 3 deletions mcp/src/tools/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ export function registerMessageTools(server: McpServer, client: McpClient): void
title: "List messages (inbox or sent)",
annotations: { readOnlyHint: true },
description:
"List the agent's messages, newest first by default. Use `direction` to pick the folder: `inbound` (the Inbox — received mail, the default), `outbound` (the Sent folder — mail this agent sent, including held drafts), or `all` (both). Filter received mail by `read_status` (unread/read/all; default unread — applies to inbound only; sent mail has no read-state). **Cursor-paginated:** returns one page in `messages` plus a `next_cursor` when more remain — pass it back as `cursor` for the next page (keep the same filters + sort). Pass `sort: \"asc\"` for FIFO order (oldest first) to drain in arrival order. **Search filters** (`from`, `subject_contains`, `conversation_id`, `since`, `until`) narrow server-side — use them instead of paging the whole folder. Returns summaries only — use `get_message` for the full body.",
"List the agent's messages, newest first by default. Use `direction` to pick the folder: `inbound` (the Inbox — received mail, the default), `outbound` (the Sent folder — mail this agent sent, including held drafts), or `all` (both). Filter received mail by `read_status` (unread/read/all; default unread — applies to inbound only; sent mail has no read-state). **Cursor-paginated:** returns one page in `messages` plus a `next_cursor` when more remain — pass it back as `cursor` for the next page (keep the same filters + sort). Pass `sort: \"asc\"` for FIFO order (oldest first) to drain in arrival order. **Search filters** (`from_`, `subject_contains`, `conversation_id`, `since`, `until`) narrow server-side — use them instead of paging the whole folder. Returns summaries only — use `get_message` for the full body.",
inputSchema: strictInputSchema({
direction: z
.enum(["inbound", "outbound", "all"])
Expand All @@ -352,7 +352,7 @@ export function registerMessageTools(server: McpServer, client: McpClient): void
.describe(
"Sort order by created_at. Defaults to `desc` (newest first). Pass `asc` for FIFO polling — drain the inbox in arrival order. Switching sort mid-pagination rejects the existing cursor.",
),
from: z
from_: z
.string()
.max(200)
.optional()
Expand Down Expand Up @@ -404,7 +404,7 @@ export function registerMessageTools(server: McpServer, client: McpClient): void
...(args.cursor !== undefined ? { cursor: args.cursor } : {}),
...(args.limit !== undefined ? { limit: args.limit } : {}),
...(args.sort !== undefined ? { sort: args.sort } : {}),
...(args.from !== undefined ? { from: args.from } : {}),
...(args.from_ !== undefined ? { from_: args.from_ } : {}),
...(args.subject_contains !== undefined
? { subjectContains: args.subject_contains }
: {}),
Expand Down
11 changes: 11 additions & 0 deletions mcp/tests/client.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { McpClient } from "../src/client.js";

type ListMessagesParams = Parameters<McpClient["listMessages"]>[0];

const senderFilter: ListMessagesParams = { from_: "alice@example.com" };
void senderFilter;

// The MCP tool and its wrapper follow the SDK's reserved-word-safe spelling.
// @ts-expect-error `from` is the REST wire name, not the MCP input name.
const removedSenderFilter: ListMessagesParams = { from: "alice@example.com" };
void removedSenderFilter;
18 changes: 17 additions & 1 deletion mcp/tests/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,15 +600,31 @@ describe("e2a MCP server", () => {
it("list_messages forwards filters + cursor/limit", async () => {
await client.callTool({
name: "list_messages",
arguments: { read_status: "unread", limit: 10, cursor: "c_prev" },
arguments: {
read_status: "unread",
from_: "alice@example.com",
limit: 10,
cursor: "c_prev",
},
});
expect(stub.listMessages).toHaveBeenCalledWith({
readStatus: "unread",
from_: "alice@example.com",
limit: 10,
cursor: "c_prev",
});
});

it("list_messages rejects the removed from spelling", async () => {
const res = await client.callTool({
name: "list_messages",
arguments: { from: "alice@example.com" },
});

expect(res.isError).toBe(true);
expect(stub.listMessages).not.toHaveBeenCalled();
});

it("list_messages forwards deleted:true for the trash", async () => {
await client.callTool({ name: "list_messages", arguments: { deleted: true } });
expect(stub.listMessages).toHaveBeenCalledWith({ deleted: true });
Expand Down
9 changes: 9 additions & 0 deletions mcp/tsconfig.type-tests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"rootDir": "."
},
"include": ["src", "tests/client.types.ts"],
"exclude": ["dist", "node_modules"]
}
3 changes: 2 additions & 1 deletion sdks/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
],
"scripts": {
"build": "tsc",
"test": "npm run typecheck && npm run test:unit",
"test": "npm run typecheck && npm run test:unit && npm run test:types",
"typecheck": "tsc --project tsconfig.test.json",
"test:unit": "vitest run test/v1/client.test.ts test/v1/ws.test.ts test/v1/webhook-signature.test.ts test/v1/webhook-payloads.test.ts test/v1/errors.test.ts test/v1/retry.test.ts test/v1/pagination.test.ts",
"test:types": "tsc -p tsconfig.type-tests.json",
"test:contract": "vitest run test/v1/contract.test.ts",
"test:live": "vitest run test/e2e.test.ts",
"prepublishOnly": "npm run build"
Expand Down
4 changes: 2 additions & 2 deletions sdks/typescript/src/v1/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export interface ListMessagesParams {
direction?: "inbound" | "outbound" | "all";
readStatus?: "unread" | "read" | "all";
sort?: "asc" | "desc";
from?: string;
from_?: string;
subjectContains?: string;
conversationId?: string;
labels?: string[];
Expand All @@ -276,7 +276,7 @@ class MessagesResource {
list(email: string, params: ListMessagesParams = {}): AutoPager<MessageSummaryView> {
return new AutoPager(async (cursor) => {
const page = await call(() =>
this.api.listMessages(email, params.direction, params.readStatus, params.sort, params.from,
this.api.listMessages(email, params.direction, params.readStatus, params.sort, params.from_,
params.subjectContains, params.conversationId, params.labels, params.since, params.until,
cursor, params.limit, params.deleted),
);
Expand Down
10 changes: 10 additions & 0 deletions sdks/typescript/test/v1/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,16 @@ describe("E2AClient", () => {
expect(calls[1]).toContain("cursor=cur_2");
});

it("messages.list exposes from_ and serializes it as the wire from query", async () => {
globalThis.fetch = mockFetch(200, { items: [], next_cursor: null });

await client.messages.list("bot@test.dev", { from_: "alice@example.com" }).page();

const url = new URL(lastCall().url);
expect(url.searchParams.get("from")).toBe("alice@example.com");
expect(url.searchParams.has("from_")).toBe(false);
});

it("messages.list({ deleted: true }) lists the trash", async () => {
globalThis.fetch = mockFetch(200, { items: [], next_cursor: null });
await client.messages.list("bot@test.dev", { deleted: true }).toArray({ limit: 10 });
Expand Down
9 changes: 9 additions & 0 deletions sdks/typescript/test/v1/client.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { ListMessagesParams } from "../../src/v1/index.js";

const senderFilter: ListMessagesParams = { from_: "alice@example.com" };
void senderFilter;

// The pre-GA breaking rename intentionally removes the old public spelling.
// @ts-expect-error `from` is the wire name; SDK callers use `from_`.
const removedSenderFilter: ListMessagesParams = { from: "alice@example.com" };
void removedSenderFilter;
9 changes: 9 additions & 0 deletions sdks/typescript/tsconfig.type-tests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"rootDir": "."
},
"include": ["src", "test/v1/client.types.ts"],
"exclude": ["dist", "node_modules"]
}
Loading