Compare commits

..

3 Commits

Author SHA1 Message Date
fiatjaf
95bed5d5a8 nak req --ids-only 2025-02-12 16:37:17 -03:00
fiatjaf
2e30dfe2eb wallet: fix nutzap error message. 2025-02-12 15:51:00 -03:00
fiatjaf
55c6f75b8a mcp: fix a bunch of stupid bugs. 2025-02-07 16:54:23 -03:00
5 changed files with 75 additions and 28 deletions

4
go.mod
View File

@@ -17,7 +17,8 @@ require (
github.com/mailru/easyjson v0.9.0 github.com/mailru/easyjson v0.9.0
github.com/mark3labs/mcp-go v0.8.3 github.com/mark3labs/mcp-go v0.8.3
github.com/markusmobius/go-dateparser v1.2.3 github.com/markusmobius/go-dateparser v1.2.3
github.com/nbd-wtf/go-nostr v0.49.3 github.com/nbd-wtf/go-nostr v0.49.7
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
) )
require ( require (
@@ -62,7 +63,6 @@ require (
github.com/wasilibs/go-re2 v1.3.0 // indirect github.com/wasilibs/go-re2 v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
golang.org/x/crypto v0.32.0 // indirect golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.21.0 // indirect

4
go.sum
View File

@@ -128,8 +128,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nbd-wtf/go-nostr v0.49.3 h1:7tsEdMZOtJ764JuMLffkbhVUi4yyf688dbqArLvItPs= github.com/nbd-wtf/go-nostr v0.49.7 h1:4D9XCqjTJYqUPMuNJI27W5gaiklnTI12IzzWIAOFepE=
github.com/nbd-wtf/go-nostr v0.49.3/go.mod h1:M50QnhkraC5Ol93v3jqxSMm1aGxUQm5mlmkYw5DJzh8= github.com/nbd-wtf/go-nostr v0.49.7/go.mod h1:M50QnhkraC5Ol93v3jqxSMm1aGxUQm5mlmkYw5DJzh8=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=

52
mcp.go
View File

@@ -27,6 +27,9 @@ var mcpServer = &cli.Command{
s.AddTool(mcp.NewTool("publish_note", s.AddTool(mcp.NewTool("publish_note",
mcp.WithDescription("Publish a short note event to Nostr with the given text content"), mcp.WithDescription("Publish a short note event to Nostr with the given text content"),
mcp.WithString("relay",
mcp.Description("Relay to publish the note to"),
),
mcp.WithString("content", mcp.WithString("content",
mcp.Required(), mcp.Required(),
mcp.Description("Arbitrary string to be published"), mcp.Description("Arbitrary string to be published"),
@@ -38,6 +41,11 @@ var mcpServer = &cli.Command{
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
content, _ := request.Params.Arguments["content"].(string) content, _ := request.Params.Arguments["content"].(string)
mention, _ := request.Params.Arguments["mention"].(string) mention, _ := request.Params.Arguments["mention"].(string)
relayI, ok := request.Params.Arguments["relay"]
var relay string
if ok {
relay, _ = relayI.(string)
}
if mention != "" && !nostr.IsValidPublicKey(mention) { if mention != "" && !nostr.IsValidPublicKey(mention) {
return mcp.NewToolResultError("the given mention isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil return mcp.NewToolResultError("the given mention isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil
@@ -71,16 +79,33 @@ var mcpServer = &cli.Command{
relays = []string{"nos.lol", "relay.damus.io"} relays = []string{"nos.lol", "relay.damus.io"}
} }
for res := range sys.Pool.PublishMany(ctx, []string{"nos.lol"}, evt) { // extra relay specified
relays = append(relays, relay)
result := strings.Builder{}
result.WriteString(
fmt.Sprintf("the event we generated has id '%s', kind '%d' and is signed by pubkey '%s'. ",
evt.ID,
evt.Kind,
evt.PubKey,
),
)
for res := range sys.Pool.PublishMany(ctx, relays, evt) {
if res.Error != nil { if res.Error != nil {
return mcp.NewToolResultError( result.WriteString(
fmt.Sprintf("there was an error publishing the event to the relay %s", fmt.Sprintf("there was an error publishing the event to the relay %s. ",
res.RelayURL), res.RelayURL),
), nil )
} else {
result.WriteString(
fmt.Sprintf("the event was successfully published to the relay %s. ",
res.RelayURL),
)
} }
} }
return mcp.NewToolResultText("event was successfully published with id " + evt.ID), nil return mcp.NewToolResultText(result.String()), nil
}) })
s.AddTool(mcp.NewTool("resolve_nostr_uri", s.AddTool(mcp.NewTool("resolve_nostr_uri",
@@ -152,13 +177,9 @@ var mcpServer = &cli.Command{
mcp.Description("Public key of Nostr user we want to know the relay from where to read"), mcp.Description("Public key of Nostr user we want to know the relay from where to read"),
), ),
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
name, _ := request.Params.Arguments["name"].(string) pubkey, _ := request.Params.Arguments["pubkey"].(string)
re := sys.Pool.QuerySingle(ctx, []string{"relay.nostr.band", "nostr.wine"}, nostr.Filter{Search: name, Kinds: []int{0}}) res := sys.FetchOutboxRelays(ctx, pubkey, 1)
if re == nil { return mcp.NewToolResultText(res[0]), nil
return mcp.NewToolResultError("couldn't find anyone with that name"), nil
}
return mcp.NewToolResultText(re.PubKey), nil
}) })
s.AddTool(mcp.NewTool("read_events_from_relay", s.AddTool(mcp.NewTool("read_events_from_relay",
@@ -182,7 +203,11 @@ var mcpServer = &cli.Command{
relay, _ := request.Params.Arguments["relay"].(string) relay, _ := request.Params.Arguments["relay"].(string)
limit, _ := request.Params.Arguments["limit"].(int) limit, _ := request.Params.Arguments["limit"].(int)
kind, _ := request.Params.Arguments["kind"].(int) kind, _ := request.Params.Arguments["kind"].(int)
pubkey, _ := request.Params.Arguments["pubkey"].(string) pubkeyI, ok := request.Params.Arguments["pubkey"]
var pubkey string
if ok {
pubkey, _ = pubkeyI.(string)
}
if pubkey != "" && !nostr.IsValidPublicKey(pubkey) { if pubkey != "" && !nostr.IsValidPublicKey(pubkey) {
return mcp.NewToolResultError("the given pubkey isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil return mcp.NewToolResultError("the given pubkey isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil
@@ -198,7 +223,6 @@ var mcpServer = &cli.Command{
events := sys.Pool.SubManyEose(ctx, []string{relay}, nostr.Filters{filter}) events := sys.Pool.SubManyEose(ctx, []string{relay}, nostr.Filters{filter})
result := strings.Builder{} result := strings.Builder{}
for ie := range events { for ie := range events {
result.WriteString("author public key: ") result.WriteString("author public key: ")

39
req.go
View File

@@ -9,6 +9,7 @@ import (
"github.com/fiatjaf/cli/v3" "github.com/fiatjaf/cli/v3"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip77"
) )
const ( const (
@@ -32,6 +33,10 @@ example:
DisableSliceFlagSeparator: true, DisableSliceFlagSeparator: true,
Flags: append(defaultKeyFlags, Flags: append(defaultKeyFlags,
append(reqFilterFlags, append(reqFilterFlags,
&cli.BoolFlag{
Name: "ids-only",
Usage: "use nip77 to fetch just a list of ids",
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "stream", Name: "stream",
Usage: "keep the subscription open, printing all events as they are returned", Usage: "keep the subscription open, printing all events as they are returned",
@@ -121,15 +126,33 @@ example:
} }
if len(relayUrls) > 0 { if len(relayUrls) > 0 {
fn := sys.Pool.SubManyEose if c.Bool("ids-only") {
if c.Bool("paginate") { seen := make(map[string]struct{}, max(500, filter.Limit))
fn = paginateWithParams(c.Duration("paginate-interval"), c.Uint("paginate-global-limit")) for _, url := range relayUrls {
} else if c.Bool("stream") { ch, err := nip77.FetchIDsOnly(ctx, url, filter)
fn = sys.Pool.SubMany if err != nil {
} log("negentropy call to %s failed: %s", url, err)
continue
}
for id := range ch {
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
stdout(id)
}
}
} else {
fn := sys.Pool.SubManyEose
if c.Bool("paginate") {
fn = paginateWithParams(c.Duration("paginate-interval"), c.Uint("paginate-global-limit"))
} else if c.Bool("stream") {
fn = sys.Pool.SubMany
}
for ie := range fn(ctx, relayUrls, nostr.Filters{filter}) { for ie := range fn(ctx, relayUrls, nostr.Filters{filter}) {
stdout(ie.Event) stdout(ie.Event)
}
} }
} else { } else {
// no relays given, will just print the filter // no relays given, will just print the filter

View File

@@ -316,8 +316,8 @@ var wallet = &cli.Command{
}, },
Action: func(ctx context.Context, c *cli.Command) error { Action: func(ctx context.Context, c *cli.Command) error {
args := c.Args().Slice() args := c.Args().Slice()
if len(args) >= 2 { if len(args) < 2 {
return fmt.Errorf("must be called as `nak wallet send <amount> <target>...") return fmt.Errorf("must be called as `nak wallet nutzap <amount> <target>...")
} }
w, closew, err := prepareWallet(ctx, c) w, closew, err := prepareWallet(ctx, c)