fix: add get_channel_messages MCP tool and fix search to include channel messages
Agents could send channel messages but had no MCP tool to read them. Also, search_messages only searched DMs (to/from agent), missing channel messages entirely. Now SearchMessages includes channel messages where the agent is a member. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
41b24c3584
commit
d3ff70f6d3
@@ -332,6 +332,11 @@ func (s *Service) GetChannel(ctx context.Context, id int64) (*Channel, error) {
|
||||
return s.store.GetChannel(ctx, id)
|
||||
}
|
||||
|
||||
// IsMember checks if an agent is a member of a channel.
|
||||
func (s *Service) IsMember(ctx context.Context, channelID int64, agentName string) (bool, error) {
|
||||
return s.store.IsMember(ctx, channelID, agentName)
|
||||
}
|
||||
|
||||
// GetChannelByName returns a channel by name.
|
||||
func (s *Service) GetChannelByName(ctx context.Context, name string) (*Channel, error) {
|
||||
return s.store.GetChannelByName(ctx, NormalizeChannelName(name))
|
||||
|
||||
@@ -8,17 +8,20 @@ import (
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
|
||||
"github.com/synapbus/synapbus/internal/channels"
|
||||
"github.com/synapbus/synapbus/internal/messaging"
|
||||
)
|
||||
|
||||
// ChannelToolRegistrar registers channel MCP tools on the server.
|
||||
type ChannelToolRegistrar struct {
|
||||
channelService *channels.Service
|
||||
msgService *messaging.MessagingService
|
||||
}
|
||||
|
||||
// NewChannelToolRegistrar creates a new channel tool registrar.
|
||||
func NewChannelToolRegistrar(channelService *channels.Service) *ChannelToolRegistrar {
|
||||
func NewChannelToolRegistrar(channelService *channels.Service, msgService *messaging.MessagingService) *ChannelToolRegistrar {
|
||||
return &ChannelToolRegistrar{
|
||||
channelService: channelService,
|
||||
msgService: msgService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +33,7 @@ func (ctr *ChannelToolRegistrar) RegisterAll(s *server.MCPServer) {
|
||||
s.AddTool(ctr.listChannelsTool(), ctr.handleListChannels)
|
||||
s.AddTool(ctr.inviteToChannelTool(), ctr.handleInviteToChannel)
|
||||
s.AddTool(ctr.kickFromChannelTool(), ctr.handleKickFromChannel)
|
||||
s.AddTool(ctr.getChannelMessagesTool(), ctr.handleGetChannelMessages)
|
||||
s.AddTool(ctr.sendChannelMessageTool(), ctr.handleSendChannelMessage)
|
||||
s.AddTool(ctr.updateChannelTool(), ctr.handleUpdateChannel)
|
||||
}
|
||||
@@ -87,6 +91,15 @@ func (ctr *ChannelToolRegistrar) kickFromChannelTool() mcp.Tool {
|
||||
)
|
||||
}
|
||||
|
||||
func (ctr *ChannelToolRegistrar) getChannelMessagesTool() mcp.Tool {
|
||||
return mcp.NewTool("get_channel_messages",
|
||||
mcp.WithDescription("Get recent messages from a channel you are a member of"),
|
||||
mcp.WithNumber("channel_id", mcp.Description("ID of the channel")),
|
||||
mcp.WithString("channel_name", mcp.Description("Name of the channel (alternative to channel_id)")),
|
||||
mcp.WithNumber("limit", mcp.Description("Max number of messages to return (default 50, max 200)")),
|
||||
)
|
||||
}
|
||||
|
||||
func (ctr *ChannelToolRegistrar) sendChannelMessageTool() mcp.Tool {
|
||||
return mcp.NewTool("send_channel_message",
|
||||
mcp.WithDescription("Send a message to all members of a channel"),
|
||||
@@ -281,6 +294,58 @@ func (ctr *ChannelToolRegistrar) handleKickFromChannel(ctx context.Context, req
|
||||
})
|
||||
}
|
||||
|
||||
func (ctr *ChannelToolRegistrar) handleGetChannelMessages(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
agentName, ok := extractAgentName(ctx)
|
||||
if !ok {
|
||||
return mcp.NewToolResultError("authentication required"), nil
|
||||
}
|
||||
|
||||
channelID, err := ctr.resolveChannelID(ctx, req)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("get_channel_messages failed: %s", err)), nil
|
||||
}
|
||||
|
||||
// Verify the agent is a member of the channel
|
||||
isMember, err := ctr.channelService.IsMember(ctx, channelID, agentName)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("get_channel_messages failed: %s", err)), nil
|
||||
}
|
||||
if !isMember {
|
||||
return mcp.NewToolResultError("you are not a member of this channel"), nil
|
||||
}
|
||||
|
||||
limit := req.GetInt("limit", 50)
|
||||
if limit > 200 {
|
||||
limit = 200
|
||||
}
|
||||
|
||||
messages, err := ctr.msgService.GetChannelMessages(ctx, channelID, limit)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError(fmt.Sprintf("get_channel_messages failed: %s", err)), nil
|
||||
}
|
||||
|
||||
result := make([]map[string]any, len(messages))
|
||||
for i, msg := range messages {
|
||||
result[i] = map[string]any{
|
||||
"id": msg.ID,
|
||||
"from": msg.FromAgent,
|
||||
"body": msg.Body,
|
||||
"priority": msg.Priority,
|
||||
"status": msg.Status,
|
||||
"created_at": msg.CreatedAt,
|
||||
}
|
||||
if len(msg.Metadata) > 0 {
|
||||
result[i]["metadata"] = msg.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
return resultJSON(map[string]any{
|
||||
"channel_id": channelID,
|
||||
"messages": result,
|
||||
"count": len(result),
|
||||
})
|
||||
}
|
||||
|
||||
func (ctr *ChannelToolRegistrar) handleSendChannelMessage(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
agentName, ok := extractAgentName(ctx)
|
||||
if !ok {
|
||||
|
||||
@@ -31,7 +31,7 @@ func newTestChannelRegistrar(t *testing.T) (*ChannelToolRegistrar, *channels.Ser
|
||||
db.Exec(`INSERT OR IGNORE INTO agents (name, display_name, type, capabilities, owner_id, api_key_hash, status) VALUES ('agent-b', 'Agent B', 'ai', '{}', 1, 'hash', 'active')`)
|
||||
db.Exec(`INSERT OR IGNORE INTO agents (name, display_name, type, capabilities, owner_id, api_key_hash, status) VALUES ('agent-c', 'Agent C', 'ai', '{}', 1, 'hash', 'active')`)
|
||||
|
||||
registrar := NewChannelToolRegistrar(channelService)
|
||||
registrar := NewChannelToolRegistrar(channelService, msgService)
|
||||
return registrar, channelService
|
||||
}
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ func NewMCPServer(
|
||||
|
||||
// Register channel tools
|
||||
if channelService != nil {
|
||||
channelRegistrar := NewChannelToolRegistrar(channelService)
|
||||
channelRegistrar := NewChannelToolRegistrar(channelService, msgService)
|
||||
channelRegistrar.RegisterAll(mcpSrv)
|
||||
}
|
||||
|
||||
|
||||
@@ -332,9 +332,11 @@ func (s *SQLiteMessageStore) SearchMessages(ctx context.Context, agentName, quer
|
||||
var conditions []string
|
||||
var args []any
|
||||
|
||||
// Scope to messages accessible by this agent
|
||||
conditions = append(conditions, "(m.to_agent = ? OR m.from_agent = ?)")
|
||||
args = append(args, agentName, agentName)
|
||||
// Scope to messages accessible by this agent:
|
||||
// - DMs where agent is sender or recipient
|
||||
// - Channel messages where agent is a member
|
||||
conditions = append(conditions, "(m.to_agent = ? OR m.from_agent = ? OR (m.channel_id IS NOT NULL AND m.to_agent = '' AND EXISTS (SELECT 1 FROM channel_members cm WHERE cm.channel_id = m.channel_id AND cm.agent_name = ?)))")
|
||||
args = append(args, agentName, agentName, agentName)
|
||||
|
||||
var joinClause string
|
||||
var orderClause string
|
||||
|
||||
Reference in New Issue
Block a user