Skip to content

Commit 778d0e5

Browse files
committed
fix(gateway): split loopback owner cache key
1 parent 82bdc54 commit 778d0e5

2 files changed

Lines changed: 44 additions & 1 deletion

File tree

src/gateway/mcp-http.runtime.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ export class McpLoopbackToolCache {
7070
params.accountId ?? "",
7171
params.inboundEventKind ?? "",
7272
params.sourceReplyDeliveryMode ?? "",
73-
params.senderIsOwner === true ? "owner" : "non-owner",
73+
params.senderIsOwner === true
74+
? "owner"
75+
: params.senderIsOwner === false
76+
? "non-owner"
77+
: "unknown-owner",
7478
].join("\u0000");
7579
const now = Date.now();
7680
for (const [key, entry] of this.#entries) {

src/gateway/mcp-http.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,45 @@ describe("mcp loopback server", () => {
738738
expect(getScopedToolsCall(3).currentInboundAudio).toBe(true);
739739
});
740740

741+
it("keeps explicit non-owner and unknown-owner loopback cache entries separate", () => {
742+
const cache = new McpLoopbackToolCache();
743+
const baseParams = {
744+
accountId: undefined,
745+
cfg: { session: { mainKey: "main" } } as never,
746+
currentChannelId: "telegram:chat123",
747+
currentInboundAudio: undefined,
748+
currentMessageId: "message-1",
749+
currentThreadTs: "thread-1",
750+
inboundEventKind: "room_event",
751+
messageProvider: "telegram",
752+
sessionKey: "agent:main:telegram:group:chat123",
753+
sourceReplyDeliveryMode: "message_tool_only",
754+
} satisfies Omit<Parameters<McpLoopbackToolCache["resolve"]>[0], "senderIsOwner">;
755+
resolveGatewayScopedToolsMock.mockImplementation((input: unknown) => {
756+
const params = input as { senderIsOwner?: boolean };
757+
return {
758+
agentId: "main",
759+
tools:
760+
params.senderIsOwner === false
761+
? [makeMessageTool()]
762+
: [makeMessageTool(), makeCronTool()],
763+
};
764+
});
765+
766+
const unknownFirst = cache.resolve({ ...baseParams, senderIsOwner: undefined });
767+
const nonOwnerSecond = cache.resolve({ ...baseParams, senderIsOwner: false });
768+
expect(unknownFirst.toolSchema.map((tool) => tool.name)).toContain("cron");
769+
expect(nonOwnerSecond.toolSchema.map((tool) => tool.name)).not.toContain("cron");
770+
771+
const secondCache = new McpLoopbackToolCache();
772+
const nonOwnerFirst = secondCache.resolve({ ...baseParams, senderIsOwner: false });
773+
const unknownSecond = secondCache.resolve({ ...baseParams, senderIsOwner: undefined });
774+
expect(nonOwnerFirst.toolSchema.map((tool) => tool.name)).not.toContain("cron");
775+
expect(unknownSecond.toolSchema.map((tool) => tool.name)).toContain("cron");
776+
777+
expect(resolveGatewayScopedToolsMock).toHaveBeenCalledTimes(4);
778+
});
779+
741780
it("caps loopback tool cache cardinality by evicting oldest contexts", () => {
742781
const cache = new McpLoopbackToolCache();
743782
const baseParams = {

0 commit comments

Comments
 (0)