From 4f0bf667b116a5bb18befd02d00285a0595b4c2a Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Sat, 15 Aug 2026 05:33:15 +0200 Subject: [PATCH] CB-572: require profiles for send validation --- .../java/dev/ltms/bridged/mcp/BridgeMcp.java | 35 +++++-------------- .../dev/ltms/bridged/mcp/BridgeMcpTest.java | 12 +++---- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/bridged/src/main/java/dev/ltms/bridged/mcp/BridgeMcp.java b/bridged/src/main/java/dev/ltms/bridged/mcp/BridgeMcp.java index 97945ef..dde9242 100644 --- a/bridged/src/main/java/dev/ltms/bridged/mcp/BridgeMcp.java +++ b/bridged/src/main/java/dev/ltms/bridged/mcp/BridgeMcp.java @@ -380,24 +380,15 @@ public final class BridgeMcp { // --- tool logic (thin adapters over the services; unit-testable) --------------------------- - /** {@code bridge_send}: delegate {@code content} to a worker session and block for its reply. */ - static McpSchema.CallToolResult send(MessageService messages, String sessionId, String content, Long timeoutMs) { - return send(messages, sessionId, content, timeoutMs, null); - } - /** - * As {@link #send(MessageService, String, String, Long)}, wiring an accepted-delivery hook + * {@code bridge_send}: delegate {@code content} to a worker session and block for its reply. + * The configured profiles are required so a profile name can never bypass target validation. + * * (CB-548): {@code onAccepted} records delegator ownership the instant the send is accepted, so * a BUSY interloper never claims a turn it did not win. {@code null} disables recording. */ static McpSchema.CallToolResult send(MessageService messages, String sessionId, String content, - Long timeoutMs, Runnable onAccepted) { - return send(messages, sessionId, content, timeoutMs, onAccepted, Set.of()); - } - - /** As above, rejecting a configured profile name before accepting a send to it. */ - static McpSchema.CallToolResult send(MessageService messages, String sessionId, String content, - Long timeoutMs, Runnable onAccepted, Set profiles) { + Long timeoutMs, Runnable onAccepted, Set profiles) { if (isBlank(sessionId) || isBlank(content)) { return error("sessionId and content are required"); } @@ -474,23 +465,13 @@ public final class BridgeMcp { /** * {@code bridge_send} with {@code wait:false}: delegate {@code content} and return a ticket * immediately (fire-and-poll), so a long task isn't cut off by the caller's MCP call timeout. - */ - static McpSchema.CallToolResult sendAsync(MessageService messages, String sessionId, String content) { - return sendAsync(messages, sessionId, content, null); - } - - /** - * As {@link #sendAsync(MessageService, String, String)}, wiring the accepted-delivery hook + * The configured profiles are required so a profile name can never bypass target validation. + * + * This wires the accepted-delivery hook * (CB-548) so an async flooding send records delegator ownership exactly once it is accepted. */ static McpSchema.CallToolResult sendAsync(MessageService messages, String sessionId, String content, - Runnable onAccepted) { - return sendAsync(messages, sessionId, content, onAccepted, Set.of()); - } - - /** As above, rejecting a configured profile name before handing out an async ticket. */ - static McpSchema.CallToolResult sendAsync(MessageService messages, String sessionId, String content, - Runnable onAccepted, Set profiles) { + Runnable onAccepted, Set profiles) { if (isBlank(sessionId) || isBlank(content)) { return error("sessionId and content are required"); } diff --git a/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java b/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java index f1bad0f..0aed9d2 100644 --- a/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java +++ b/bridged/src/test/java/dev/ltms/bridged/mcp/BridgeMcpTest.java @@ -81,7 +81,7 @@ class BridgeMcpTest { void sendThenReplyRoundTrips() throws Exception { // bridge_send blocks; bridge_reply resolves it with the worker's structured answer. CompletableFuture send = CompletableFuture.supplyAsync( - () -> BridgeMcp.send(messages, "term_a", "review this", 4000L)); + () -> BridgeMcp.send(messages, "term_a", "review this", 4000L, null, Set.of())); // Wait until the send has opened its waiter so the reply resolves it (CB-307: reply now // queues in the inbox if no waiter is open, which would break the round-trip). @@ -103,7 +103,7 @@ class BridgeMcpTest { @Test void asyncSendReturnsATicketThenPollReportsTheReply() throws Exception { // wait:false parity — a ticket is issued, resolved by a reply, and surfaced by bridge_poll. - McpSchema.CallToolResult accepted = BridgeMcp.sendAsync(messages, "term_a", "do it"); + McpSchema.CallToolResult accepted = BridgeMcp.sendAsync(messages, "term_a", "do it", null, Set.of()); assertNotEquals(Boolean.TRUE, accepted.isError()); String out = textOf(accepted); assertTrue(out.contains("ticket="), out); @@ -141,15 +141,15 @@ class BridgeMcpTest { @Test void sendTimesOutWithAWorkingNote() { - McpSchema.CallToolResult res = BridgeMcp.send(messages, "term_a", "hi", 120L); + McpSchema.CallToolResult res = BridgeMcp.send(messages, "term_a", "hi", 120L, null, Set.of()); assertNotEquals(Boolean.TRUE, res.isError(), "a timeout is informational, not a tool error"); assertTrue(textOf(res).contains("no reply"), "got: " + textOf(res)); } @Test void sendRejectsMissingArgs() { - assertTrue(BridgeMcp.send(messages, null, "hi", null).isError()); - assertTrue(BridgeMcp.send(messages, "term_a", " ", null).isError()); + assertTrue(BridgeMcp.send(messages, null, "hi", null, null, Set.of()).isError()); + assertTrue(BridgeMcp.send(messages, "term_a", " ", null, null, Set.of()).isError()); } @Test @@ -210,7 +210,7 @@ class BridgeMcpTest { void askThenAnswerRoundTrips() throws Exception { // The primary delegates and blocks; wait until its waiter is open before the worker asks. CompletableFuture send = CompletableFuture.supplyAsync( - () -> BridgeMcp.send(messages, "term_a", "do X", 5000L)); + () -> BridgeMcp.send(messages, "term_a", "do X", 5000L, null, Set.of())); long deadline = System.currentTimeMillis() + 3000; while (!rendezvous.isWaiting("term_a") && System.currentTimeMillis() < deadline) { //noinspection BusyWait