From 327f899f452868248d0822380e98778688b84ce7 Mon Sep 17 00:00:00 2001 From: tcheeric Date: Fri, 25 Sep 2026 16:52:36 +0100 Subject: [PATCH 1/2] fix(mcp): keep tag values verbatim in nostr_publish_event nestedTexts reused texts(), which trims values and drops empty ones. Tags are positional, so ["p", pk, "", "mention"] went out as ["p", pk, "mention"], moving the marker into the relay-hint slot. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 6 +++ .../nostr/mcp/argument/ToolArguments.java | 11 +++++- .../nostr/mcp/argument/ToolArgumentsTest.java | 39 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 nostr-java-mcp/src/test/java/nostr/mcp/argument/ToolArgumentsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f8dc448..94f89fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is inspired by Keep a Changelog, and this project adheres to semantic ## [Unreleased] +### Fixed +- `nostr_publish_event` dropped empty tag values and trimmed the rest. Tags are positional, so + `["p", pk, "", "mention"]` went out as `["p", pk, "mention"]`, putting the marker in the + relay-hint slot, and a signed event could differ from the one previewed for approval. Tag + values are now passed through verbatim. + ## [2.4.0] - 2026-09-21 ### Added diff --git a/nostr-java-mcp/src/main/java/nostr/mcp/argument/ToolArguments.java b/nostr-java-mcp/src/main/java/nostr/mcp/argument/ToolArguments.java index d8d143b5..0989781c 100644 --- a/nostr-java-mcp/src/main/java/nostr/mcp/argument/ToolArguments.java +++ b/nostr-java-mcp/src/main/java/nostr/mcp/argument/ToolArguments.java @@ -130,6 +130,9 @@ public Map> tagFilters(@NonNull String name) { *

A single flat list is read as one entry, since a model given an example of nested arrays * will sometimes send just the inner one. * + *

Values are kept verbatim, unlike {@link #texts}: tags are positional, so dropping an empty + * relay hint in {@code ["p", pk, "", "mention"]} would move the marker into its place. + * * @param name the argument to read * @return each inner list's values */ @@ -139,14 +142,18 @@ public List> nestedTexts(@NonNull String name) { return List.of(); } if (outer.stream().noneMatch(List.class::isInstance)) { - return List.of(texts(name)); + return List.of(verbatim(outer)); } return outer.stream() .filter(List.class::isInstance) - .map(inner -> new ToolArguments(Map.of("v", inner)).texts("v")) + .map(inner -> verbatim((List) inner)) .toList(); } + private static List verbatim(List values) { + return values.stream().map(String::valueOf).toList(); + } + private int parseInteger(String name, String text) { try { return (int) Double.parseDouble(text); diff --git a/nostr-java-mcp/src/test/java/nostr/mcp/argument/ToolArgumentsTest.java b/nostr-java-mcp/src/test/java/nostr/mcp/argument/ToolArgumentsTest.java new file mode 100644 index 00000000..c44f7321 --- /dev/null +++ b/nostr-java-mcp/src/test/java/nostr/mcp/argument/ToolArgumentsTest.java @@ -0,0 +1,39 @@ +package nostr.mcp.argument; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** Verifies tool arguments are read without changing what the caller meant. */ +class ToolArgumentsTest { + + // Verifies an empty tag value keeps its position, since NIP-01 tags are positional and + // dropping the empty relay hint in ["p", pk, "", "mention"] moves the marker into its place. + @Test + void nestedTextsKeepEmptyValuesInPlace() { + var arguments = new ToolArguments(Map.of("tags", List.of(List.of("p", "abc", "", "mention")))); + + assertEquals(List.of(List.of("p", "abc", "", "mention")), arguments.nestedTexts("tags")); + } + + // Verifies tag values are not trimmed, since a tag can carry quoted text that must go out + // byte-for-byte. + @Test + void nestedTextsDoNotTrim() { + var arguments = new ToolArguments(Map.of("tags", List.of(List.of("comment", " spaced \n")))); + + assertEquals(List.of(List.of("comment", " spaced \n")), arguments.nestedTexts("tags")); + } + + // Verifies a single flat tag is read the same way, since that path is taken when a model sends + // only the inner array. + @Test + void aFlatTagKeepsEmptyValuesInPlace() { + var arguments = new ToolArguments(Map.of("tags", List.of("p", "abc", "", "mention"))); + + assertEquals(List.of(List.of("p", "abc", "", "mention")), arguments.nestedTexts("tags")); + } +} From 1ab39cb056219c9e98f2b6087f635a659952e4b3 Mon Sep 17 00:00:00 2001 From: tcheeric Date: Fri, 25 Sep 2026 17:05:42 +0100 Subject: [PATCH 2/2] chore(release): bump to 2.4.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nostr-java (all modules): 2.4.0 → 2.4.1 (patch) [maven] Fixed: nostr_publish_event dropped empty tag values and trimmed the rest, so positional tags like ["p", pk, "", "mention"] shifted. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 2 ++ docs/howto/multi-relay-publishing.md | 2 +- nostr-java-api/pom.xml | 2 +- nostr-java-client/pom.xml | 2 +- nostr-java-core/pom.xml | 2 +- nostr-java-event/pom.xml | 2 +- nostr-java-identity/pom.xml | 2 +- nostr-java-mcp/pom.xml | 2 +- pom.xml | 2 +- 9 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94f89fbc..b479c634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is inspired by Keep a Changelog, and this project adheres to semantic ## [Unreleased] +## [2.4.1] - 2026-09-25 + ### Fixed - `nostr_publish_event` dropped empty tag values and trimmed the rest. Tags are positional, so `["p", pk, "", "mention"]` went out as `["p", pk, "mention"]`, putting the marker in the diff --git a/docs/howto/multi-relay-publishing.md b/docs/howto/multi-relay-publishing.md index c43303a3..b30e2e2f 100644 --- a/docs/howto/multi-relay-publishing.md +++ b/docs/howto/multi-relay-publishing.md @@ -10,7 +10,7 @@ send a private direct message. xyz.tcheeric nostr-java-api - 2.4.0 + 2.4.1 ``` diff --git a/nostr-java-api/pom.xml b/nostr-java-api/pom.xml index 79233679..9f780eb6 100644 --- a/nostr-java-api/pom.xml +++ b/nostr-java-api/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 2.4.0 + 2.4.1 ../pom.xml diff --git a/nostr-java-client/pom.xml b/nostr-java-client/pom.xml index acdb8ce5..70729801 100644 --- a/nostr-java-client/pom.xml +++ b/nostr-java-client/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 2.4.0 + 2.4.1 ../pom.xml diff --git a/nostr-java-core/pom.xml b/nostr-java-core/pom.xml index 7ba8bb6b..176578e4 100644 --- a/nostr-java-core/pom.xml +++ b/nostr-java-core/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 2.4.0 + 2.4.1 ../pom.xml diff --git a/nostr-java-event/pom.xml b/nostr-java-event/pom.xml index af4ca2bb..5dbe5df2 100644 --- a/nostr-java-event/pom.xml +++ b/nostr-java-event/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 2.4.0 + 2.4.1 ../pom.xml diff --git a/nostr-java-identity/pom.xml b/nostr-java-identity/pom.xml index 7c257d5e..b0ae7b48 100644 --- a/nostr-java-identity/pom.xml +++ b/nostr-java-identity/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 2.4.0 + 2.4.1 ../pom.xml diff --git a/nostr-java-mcp/pom.xml b/nostr-java-mcp/pom.xml index 7d40545b..a4621c9f 100644 --- a/nostr-java-mcp/pom.xml +++ b/nostr-java-mcp/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 2.4.0 + 2.4.1 ../pom.xml diff --git a/pom.xml b/pom.xml index 4101b78b..e7e31593 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ xyz.tcheeric nostr-java - 2.4.0 + 2.4.1 pom nostr-java