fix(mcp): retain same-address procedure aliases in completion

Fixes #4623. Check name uniqueness against every procedure row before deriving completion candidates.
This commit is contained in:
shreyansh1729 committed 2026-10-17 07:33:21 +05:30
1 parent 150e996a86
commit 5b019e1179
2 files changed
+63 -13

No files matched your search

+11 -13
View File
@@ -99,7 +99,8 @@ const procedureCandidates = async (
session: BinarySessionPort | undefined,
document: string | undefined,
): Promise<readonly string[]> => {
const byAddress = new Map<string, string>();
const addresses = new Set<string>();
const addressesByName = new Map<string, Set<string>>();
const input =
document === undefined || document.length === 0 ? {} : { document };
const result = await completionInventoryLookup(
@@ -114,20 +115,17 @@ const procedureCandidates = async (
.array(z.object({ address: z.string(), value: z.string() }))
.safeParse(result.value.result);
if (parsed.success)
for (const item of parsed.data)
if (!byAddress.has(item.address))
byAddress.set(item.address, item.value);
for (const { address, value: name } of parsed.data) {
addresses.add(address);
const namedAddresses = addressesByName.get(name) ?? new Set<string>();
namedAddresses.add(address);
addressesByName.set(name, namedAddresses);
}
}
const addressesByName = new Map<string, Set<string>>();
for (const [address, name] of byAddress) {
const addresses = addressesByName.get(name) ?? new Set<string>();
addresses.add(address);
addressesByName.set(name, addresses);
}
const candidates = [...byAddress.keys()];
for (const [name, addresses] of addressesByName)
if (addresses.size === 1 && name.length > 0) candidates.push(name);
const candidates = [...addresses];
for (const [name, namedAddresses] of addressesByName)
if (namedAddresses.size === 1 && name.length > 0) candidates.push(name);
return candidates;
};
+52
View File
@@ -54,6 +54,58 @@ afterEach(async () => {
);
});
it.each([false, true])(
"retains same-address procedure aliases (reversed: %s)",
async (reversed) => {
const rows = [
{ address: "0x1000", value: "foo" },
{ address: "0x1000", value: "bar" },
{ address: "0x1000", value: "ambiguous" },
{ address: "0x2000", value: "ambiguous" },
{ address: "0x1000", value: "bar" },
];
if (reversed) rows.reverse();
const client = await connect(
createServer({
kind: "fixed",
analysis: {
execute: async () =>
resultOk(
createAnalysisExecution(rows, {
id: "fixture",
name: "Fixture",
version: "1",
}),
),
},
}),
);
const inventory = await client.callTool({
name: "list_procedures",
arguments: {},
});
expect(inventory.isError).not.toBe(true);
expect(inventory.structuredContent).toMatchObject({
normalized_result: rows,
});
for (const [value, values] of [
["", ["0x1000", "0x2000", "bar", "foo"]],
["BAR", ["bar"]],
["foo", ["foo"]],
["ambiguous", []],
] as const) {
expect(
await client.complete({
ref: { type: "ref/prompt", name: "investigate_feature" },
argument: { name: "procedure", value },
}),
).toEqual({
completion: { values, total: values.length, hasMore: false },
});
}
},
);
describe("guided prompts over MCP", () => {
it("lists and renders all workflows without changing the tool inventory", async () => {
const session = fixtureSession();