Compare commits

...

2 Commits

Author SHA1 Message Date
fiatjaf
55c6f75b8a mcp: fix a bunch of stupid bugs. 2025-02-07 16:54:23 -03:00
fiatjaf
1f2492c9b1 fix multiline handler thing for when we're don't have any stdin. 2025-02-05 20:42:55 -03:00
2 changed files with 56 additions and 25 deletions

View File

@@ -49,7 +49,7 @@ func getJsonsOrBlank() iter.Seq[string] {
var curr strings.Builder var curr strings.Builder
return func(yield func(string) bool) { return func(yield func(string) bool) {
for stdinLine := range getStdinLinesOrBlank() { hasStdin := writeStdinLinesOrNothing(func(stdinLine string) bool {
// we're look for an event, but it may be in multiple lines, so if json parsing fails // we're look for an event, but it may be in multiple lines, so if json parsing fails
// we'll try the next line until we're successful // we'll try the next line until we're successful
curr.WriteString(stdinLine) curr.WriteString(stdinLine)
@@ -57,24 +57,34 @@ func getJsonsOrBlank() iter.Seq[string] {
var dummy any var dummy any
if err := json.Unmarshal([]byte(stdinEvent), &dummy); err != nil { if err := json.Unmarshal([]byte(stdinEvent), &dummy); err != nil {
continue return true
} }
if !yield(stdinEvent) { if !yield(stdinEvent) {
return return false
} }
curr.Reset() curr.Reset()
return true
})
if !hasStdin {
yield("{}")
} }
} }
} }
func getStdinLinesOrBlank() iter.Seq[string] { func getStdinLinesOrBlank() iter.Seq[string] {
return func(yield func(string) bool) { return func(yield func(string) bool) {
if hasStdinLines := writeStdinLinesOrNothing(yield); !hasStdinLines { hasStdin := writeStdinLinesOrNothing(func(stdinLine string) bool {
return if !yield(stdinLine) {
} else { return false
return }
return true
})
if !hasStdin {
yield("")
} }
} }
} }
@@ -107,10 +117,7 @@ func writeStdinLinesOrNothing(yield func(string) bool) (hasStdinLines bool) {
} }
hasEmittedAtLeastOne = true hasEmittedAtLeastOne = true
} }
if !hasEmittedAtLeastOne { return hasEmittedAtLeastOne
yield("")
}
return true
} else { } else {
// not piped // not piped
return false return false

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: ")