Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@
- Bound MCP attachment downloads before writing them to disk.
- Ignore symlinked and other non-regular entries when reading MCP history or pending files.
- Filter pending MCP history by its saved Discord channel name or ID.
- Filter pending MCP history by the saved message timestamp when a date is requested.
29 changes: 22 additions & 7 deletions src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ function isWithinDiscordMessageLimit(message: string): boolean {
return true;
}

function readPendingChannel(filePath: string): {
function readPendingMetadata(filePath: string): {
channelId: string | undefined;
channelName: string | undefined;
date: string | undefined;
} {
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
const separatorIndex = lines.indexOf("---");
Expand All @@ -58,12 +59,22 @@ function readPendingChannel(filePath: string): {
const channelIdLine = headerLines.find((line) =>
line.startsWith("Channel ID: "),
);
const timestampLine = headerLines.find((line) =>
line.startsWith("Timestamp: "),
);
const timestamp = timestampLine
? new Date(timestampLine.slice("Timestamp: ".length))
: undefined;

return {
channelId: channelIdLine?.slice("Channel ID: ".length),
channelName: channelLine
?.slice("Channel: #".length)
.replace(/[^a-zA-Z0-9-_]/g, "_"),
date:
timestamp && !Number.isNaN(timestamp.getTime())
? timestamp.toISOString().slice(0, 10)
: undefined,
};
}

Expand Down Expand Up @@ -384,18 +395,19 @@ export function createMcpServer(): Server {
)
.map((entry) => {
const filePath = path.join(dir, entry.name);
const pendingChannel =
const pendingMetadata =
type === "pending"
? readPendingChannel(filePath)
? readPendingMetadata(filePath)
: undefined;
return {
displayName: entry.name,
filePath,
channelId: pendingChannel?.channelId,
channelId: pendingMetadata?.channelId,
channelName:
type === "history"
? getLegacyHistoryChannel(entry.name)
: pendingChannel?.channelName,
: pendingMetadata?.channelName,
date: pendingMetadata?.date,
};
});

Expand All @@ -419,6 +431,7 @@ export function createMcpServer(): Server {
),
channelId: parsed?.channelId,
channelName: parsed?.channelName,
date: undefined,
};
});
files.push(...channelFiles);
Expand All @@ -442,8 +455,10 @@ export function createMcpServer(): Server {
);
}
if (date) {
files = files.filter((f) =>
f.displayName.endsWith(`_${date}.txt`),
files = files.filter((file) =>
type === "pending"
? file.date === date
: file.displayName.endsWith(`_${date}.txt`),
);
}

Expand Down
24 changes: 24 additions & 0 deletions tests/mcpHistory.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ test("MCP history filters matching regular files and preserves pending indentati
createdAt: new Date("2026-08-01T12:45:00.000Z"),
content: "other-channel pending entry",
});
savePending({
id: "555555555555555555",
author: { tag: "user#0003" },
channel: { name: "general" },
channelId: "111111111111111111",
createdAt: new Date("2026-08-02T00:15:00.000Z"),
content: "next-day pending entry",
});
fs.writeFileSync(
path.join(messagesDir, "pending", "333333333333333333.txt"),
[
Expand All @@ -158,6 +166,22 @@ test("MCP history filters matching regular files and preserves pending indentati
"utf8",
);

const pendingDateResult = await client.callTool({
name: "read-message-history",
arguments: { type: "pending", date: "2026-08-01" },
});
const pendingDateText = pendingDateResult.content.find(
(item) => item.type === "text",
)?.text;

assert.equal(typeof pendingDateText, "string");
assert.match(pendingDateText, /222222222222222222\.txt/);
assert.match(pendingDateText, /333333333333333333\.txt/);
assert.match(pendingDateText, /444444444444444444\.txt/);
assert.doesNotMatch(pendingDateText, /regular\.txt/);
assert.doesNotMatch(pendingDateText, /555555555555555555\.txt/);
assert.doesNotMatch(pendingDateText, /next-day pending entry/);

for (const channel of ["general", "111111111111111111"]) {
const pendingResult = await client.callTool({
name: "read-message-history",
Expand Down