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:
Algis Dumbris
2026-03-14 14:48:26 +02:00
co-authored by Claude Opus 4.6
parent 41b24c3584
commit d3ff70f6d3
5 changed files with 78 additions and 6 deletions
+5
View File
@@ -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))
+66 -1
View File
@@ -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 {
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -146,7 +146,7 @@ func NewMCPServer(
// Register channel tools
if channelService != nil {
channelRegistrar := NewChannelToolRegistrar(channelService)
channelRegistrar := NewChannelToolRegistrar(channelService, msgService)
channelRegistrar.RegisterAll(mcpSrv)
}
+5 -3
View File
@@ -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