Skip to content

feat: add collab tool#206

Closed
sammcj wants to merge 5 commits intomainfrom
tasks
Closed

feat: add collab tool#206
sammcj wants to merge 5 commits intomainfrom
tasks

Conversation

@sammcj
Copy link
Owner

@sammcj sammcj commented Feb 20, 2026

  • feat: add new experimental collab tool for cross-agent communication tracking

This pull request introduces a new cross-agent collaboration tool, updates documentation and environment configuration to support it, and makes minor dependency and formatting changes. The collaboration tool enables session-based message exchange between AI agents via a shared filesystem mailbox, supporting workflows like API design coordination and bug reporting across projects.

Collaboration Tool Integration

  • Added new collab and collab_wait tools for cross-agent communication, including session-based message exchange with UUID addressing, file locking, atomic writes, participant name resolution, and MCP notifications for message events. (CHANGELOG.md, internal/imports/tools.go, docs/tools/collab.md) [1] [2] [3]
  • Updated documentation to explain the collaboration workflow, usage scenarios, and tool actions, including example interactions and configuration options. (docs/tools/collab.md, docs/tools/overview.md) [1] [2] [3]

Configuration and Documentation Updates

  • Added collab to the list of additional tools in environment variable examples and tool tables, ensuring agents can enable it via ENABLE_ADDITIONAL_TOOLS. (README.md, docs/tools/overview.md) [1] [2]
  • Improved guidance for updating the changelog after non-trivial changes, specifying formatting and grouping rules. (CLAUDE.md)

Dependency and Formatting Changes

  • Updated github.com/mark3labs/mcp-go dependency to v0.44.0. (go.mod)
  • Minor formatting fixes to tool tables in README.md. [1] [2]

These changes collectively enable robust multi-agent coordination workflows and ensure clear documentation and configuration for users.

@sammcj sammcj self-assigned this Feb 20, 2026
Copilot AI review requested due to automatic review settings February 20, 2026 23:07
@socket-security
Copy link

socket-security bot commented Feb 20, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedgolang/​github.com/​mark3labs/​mcp-go@​v0.43.2 ⏵ v0.44.077 -1100100100100

View full report

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an experimental collab / collab_wait tool to support cross-agent coordination via a shared filesystem “mailbox”, plus documentation and enablement wiring so it can be turned on via ENABLE_ADDITIONAL_TOOLS.

Changes:

  • Introduces new collab and collab_wait tools with filesystem-backed sessions, messages, and optional MCP notifications.
  • Wires tool registration/imports and enablement aliasing so collab_wait is enabled when collab is enabled.
  • Adds docs/tests and bumps github.com/mark3labs/mcp-go to v0.44.0.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
internal/tools/collab/collab.go Implements collab tool actions (create/join/post/check/read/list/close) and extended help.
internal/tools/collab/wait.go Implements collab_wait polling tool.
internal/tools/collab/storage.go Filesystem storage layer (sessions/messages, locking, atomic writes).
internal/tools/collab/types.go Shared types and validation limits.
internal/tools/collab/notifications.go Best-effort MCP client notification on new messages.
internal/imports/tools.go Imports the new tool package for registration.
internal/registry/registry.go Adds enablement alias so collab_wait is enabled when collab is enabled.
tests/tools/collab_test.go Adds unit tests for collab + wait behaviours and filesystem permissions.
docs/tools/collab.md Adds user documentation for the new tools and workflows.
docs/tools/overview.md Adds collab workflow and enablement example update.
README.md Adds collab to the additional tools table and tweaks table formatting.
go.mod / go.sum Updates mcp-go dependency to v0.44.0.
CHANGELOG.md Adds changelog entry for the new tool and related behaviour.
CLAUDE.md Adds guidance about updating CHANGELOG.md.

Comment on lines +103 to +107
baseline, err := w.storage.GetMessageCount(sessionID)
if err != nil {
return nil, fmt.Errorf("failed to load session: %w", err)
}

Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collab_wait sets baseline to the current total message count and only returns when the count increases. If the other agent has already posted messages before collab_wait starts, this will block until another message arrives (even though messages are already available). Consider accepting an optional participant name (or since_message_id) and returning immediately when there are unread messages (similar to collab action=check).

Suggested change
baseline, err := w.storage.GetMessageCount(sessionID)
if err != nil {
return nil, fmt.Errorf("failed to load session: %w", err)
}
currentCount, err := w.storage.GetMessageCount(sessionID)
if err != nil {
return nil, fmt.Errorf("failed to load session: %w", err)
}
// By default we wait for messages newer than the current count, but allow
// callers to specify a since_message_id to wait relative to a prior point
// in the conversation. This ensures we do not ignore messages that were
// already posted before collab_wait started.
baseline := currentCount
if rawSince, ok := args["since_message_id"]; ok && rawSince != nil {
switch v := rawSince.(type) {
case int:
if v >= 0 && v <= currentCount {
baseline = v
}
case int64:
if v >= 0 && int(v) <= currentCount {
baseline = int(v)
}
case float64:
since := int(v)
if since >= 0 && since <= currentCount {
baseline = since
}
case string:
if since, errConv := strconv.Atoi(v); errConv == nil {
if since >= 0 && since <= currentCount {
baseline = since
}
}
}
}

Copilot uses AI. Check for mistakes.
// Limits for input validation
const (
maxTopicLength = 500
maxContentLength = 100000
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maxContentLength is set to 100000, but the new tests (e.g. TestCollabTool_ContentTooLong) and docs/tools/collab.md state a 50,000 character limit. As written, posting 50,001 characters will succeed and the test will fail. Align the constant, tests, and documentation to the same limit.

Suggested change
maxContentLength = 100000
maxContentLength = 50000

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +68
// NewStorage creates a new storage instance
func NewStorage(logger *logrus.Logger) (*Storage, error) {
basePath, err := getCollabBasePath()
if err != nil {
return nil, fmt.Errorf("failed to determine collab base path: %w", err)
}

sessionsDir := filepath.Join(basePath, "sessions")
if err := ensureDir(sessionsDir); err != nil {
return nil, fmt.Errorf("failed to create sessions directory: %w", err)
}

return &Storage{
basePath: basePath,
logger: logger,
}, nil
}

// getCollabBasePath returns the base directory for collaboration data
func getCollabBasePath() (string, error) {
if envPath := os.Getenv("COLLAB_DIR"); envPath != "" {
absPath, err := filepath.Abs(envPath)
if err != nil {
return "", fmt.Errorf("failed to resolve COLLAB_DIR path: %w", err)
}
return absPath, nil
}

usr, err := user.Current()
if err != nil {
return "", fmt.Errorf("failed to get current user: %w", err)
}

return filepath.Join(usr.HomeDir, ".mcp-devtools", "collab"), nil
}

// ensureDir creates a directory with 0700 permissions if it doesn't exist
func ensureDir(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return os.MkdirAll(dir, 0700)
}
return nil
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tool performs filesystem reads/writes directly (session metadata and message files) without integrating with the security framework (e.g. security.CheckFileAccess / security.NewOperations(...).SafeFileRead/SafeFileWrite). That bypasses the repository’s access-control and content-analysis protections for file operations. Please route file IO through the security helpers (or at least check access for both final and temp paths used in atomic writes).

Copilot uses AI. Check for mistakes.

- YOU MUST ALWAYS run `make lint && make test && make build` etc... to build the project rather than gofmt, go build or test directly, and you MUST always do this before stating you've completed your changes!
- CRITICAL: If the serena tool is available to you, you must use serena for your semantic code retrieval and editing tools
- CHANGELOG: After making anything other than minor or documentation changes update `CHANGELOG.md` to add entries under today's ## [YYYY.MM.DD] header, use date +'%F %H:%M' e.g. ## [2026.2.71]. Do NOT add version numbers. Do NOT duplicate headings or dates - simply update the existing date if today's date already exists as a heading. If the file gets over 1000 lines long, truncate the oldest releases, keep items concise, grouped under headings (Added/Changed/Fixed/Removed). Combine or update items refined within the same session.
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new CHANGELOG guidance is internally inconsistent: it says to use date +'%F %H:%M' but then gives an example ## [2026.2.71] (not a valid output format/date) and also conflicts with the dot-separated date format used in CHANGELOG.md (e.g. ## [2026.02.21]). Please pick one canonical heading format and update the example accordingly.

Suggested change
- CHANGELOG: After making anything other than minor or documentation changes update `CHANGELOG.md` to add entries under today's ## [YYYY.MM.DD] header, use date +'%F %H:%M' e.g. ## [2026.2.71]. Do NOT add version numbers. Do NOT duplicate headings or dates - simply update the existing date if today's date already exists as a heading. If the file gets over 1000 lines long, truncate the oldest releases, keep items concise, grouped under headings (Added/Changed/Fixed/Removed). Combine or update items refined within the same session.
- CHANGELOG: After making anything other than minor or documentation changes update `CHANGELOG.md` to add entries under today's ## [YYYY.MM.DD] header, use `date +'%Y.%m.%d'` e.g. `## [2026.02.21]`. Do NOT add version numbers. Do NOT duplicate headings or dates - simply update the existing date if today's date already exists as a heading. If the file gets over 1000 lines long, truncate the oldest releases, keep items concise, grouped under headings (Added/Changed/Fixed/Removed). Combine or update items refined within the same session.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 20, 2026 23:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.

@@ -0,0 +1,13 @@
# Changelog

<!-- AI agents: add entries under an ## [YYYY.MM.DD] header, use date +'%F %H:%M' e.g. ## [2026.2.71]. Do NOT add version numbers. Do NOT duplicate headings or dates - simply update the existing date if you're adding to it. If the file gets over 1000 lines long, truncate the oldest releases, keep items concise and leave this comment as-is-->
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CHANGELOG comment includes an example heading ## [2026.2.71], which is not a valid date and conflicts with the stated ## [YYYY.MM.DD] format. Please update the example to a valid date matching the intended heading format.

Suggested change
<!-- AI agents: add entries under an ## [YYYY.MM.DD] header, use date +'%F %H:%M' e.g. ## [2026.2.71]. Do NOT add version numbers. Do NOT duplicate headings or dates - simply update the existing date if you're adding to it. If the file gets over 1000 lines long, truncate the oldest releases, keep items concise and leave this comment as-is-->
<!-- AI agents: add entries under an ## [YYYY.MM.DD] header, use date +'%F %H:%M' e.g. ## [2026.02.21]. Do NOT add version numbers. Do NOT duplicate headings or dates - simply update the existing date if you're adding to it. If the file gets over 1000 lines long, truncate the oldest releases, keep items concise and leave this comment as-is-->

Copilot uses AI. Check for mistakes.
}
return toToolResult(resp)
}
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the caller provides a name but it fails validateParticipantName, the tool silently ignores it and falls back to baseline polling. This makes invalid input hard to diagnose and can lead to surprising behaviour. Consider returning a validation error when name is present but invalid (or at least logging a warning and documenting the fallback explicitly).

Suggested change
}
}
} else {
logger.WithFields(logrus.Fields{
"session_id": sessionID,
"name": name,
}).Warn("collab_wait received invalid participant name")
return nil, fmt.Errorf("invalid participant name %q: %w", name, valErr)

Copilot uses AI. Check for mistakes.
Comment on lines +266 to +271
name := c.resolveParticipantName(ctx, logger, args)
participant, err := validateParticipantName(name)
if err != nil {
// Fall back to reading all messages
participant = ""
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check currently falls back to participant = "" when name is invalid, which then causes sinceID to remain 0 and returns all messages. This can be unexpectedly heavy and is likely not what the caller intended. Consider returning an error for invalid participant names (and/or for names that are not joined to the session) rather than treating it as an anonymous read-all.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 4 comments.

// sendMessageNotification sends a best-effort MCP notification when a new message is posted.
// This is useful when both agents connect to the same MCP server instance (HTTP transport).
// For separate instances (stdio), the filesystem remains the cross-instance transport.
func sendMessageNotification(ctx context.Context, logger *logrus.Logger, sessionID, from string, _ int) {
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter _ int (message ID) is named but unused. Consider removing the parameter name to make it clear it's intentionally ignored, or use the message ID in the notification data for better traceability.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +124
type postResponse struct {
MessageID int `json:"message_id"`
SessionID string `json:"session_id"`
}

// checkResponse is the response for checking new messages
type checkResponse struct {
SessionID string `json:"session_id"`
NewMessages []Message `json:"new_messages"`
HasNew bool `json:"has_new"`
}

// readResponse is the response for reading all messages
type readResponse struct {
SessionID string `json:"session_id"`
Topic string `json:"topic"`
Messages []Message `json:"messages"`
Total int `json:"total"`
}

// sessionSummary is a summary of a session for listing
type sessionSummary struct {
SessionID string `json:"session_id"`
Topic string `json:"topic"`
Status string `json:"status"`
Participants []string `json:"participants"`
MessageCount int `json:"message_count"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

// listSessionsResponse is the response for listing sessions
type listSessionsResponse struct {
Sessions []sessionSummary `json:"sessions"`
Total int `json:"total"`
}

// closeResponse is the response for closing a session
type closeResponse struct {
SessionID string `json:"session_id"`
Status string `json:"status"`
Summary string `json:"summary,omitempty"`
}

// waitResponse is the response for the collab_wait tool
type waitResponse struct {
SessionID string `json:"session_id"`
Status string `json:"status"` // "new_messages" or "timeout"
NewCount int `json:"new_count"`
Message string `json:"message"`
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response includes session_id which was provided as input by the agent. According to the coding guidelines, tool responses should avoid returning information that the agent already provided. Consider removing this field from responses where the session_id was required as input (postResponse, checkResponse, readResponse, closeResponse, waitResponse). The session context should be clear from the request.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +51 to 63
desc := `A short scratchpad for reasoning when you're stuck on a problem or decision after attempting it normally. Does not retrieve information or modify anything - just records the thought. Only use for complex problems or persistent issues, not routine decisions or first-pass reasoning.

State what you need to reason about and why. 2-4 concise sentences, no code.`
State what you need to reason about and why. 1-3 concise sentences, no code.`

if _, ok := registry.GetTool("sequential_thinking"); ok {
desc += "\n\nFor multi-step reasoning, revision, or branching analysis, use sequential_thinking instead."
}

// Build thought parameter description, conditionally referencing sequential_thinking
thoughtDesc := "Brief reasoning note: 2-4 sentences. What you're stuck on and your conclusion."
thoughtDesc := "Brief reasoning note: 1-3 sentences. What you're stuck on and your conclusion."
if _, ok := registry.GetTool("sequential_thinking"); ok {
thoughtDesc += " For lengthy analysis, use sequential_thinking."
}
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description change from "2-4 concise sentences" to "1-3 concise sentences" makes the think tool more concise, which aligns with the coding guidelines for token efficiency. However, this is a breaking change in behavior that could affect existing workflows. Consider whether this change should be documented in the CHANGELOG.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +44
mcp.WithDescription(`Cross-agent collaboration tool for session-based message exchange between AI coding agents.

Enables two agents working on related projects to exchange structured messages (feature requests, implementation summaries, questions, feedback, bug reports, API changes) via a shared filesystem mailbox.

Workflow:
1. Agent A: create_session (gets UUID) -> human relays UUID to Agent B
2. Agent B: join_session (by UUID) -> sees topic and existing messages
3. Both agents: post and check messages within the session
4. Either agent: close the session when done`),
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tool description is approximately 550 characters, which exceeds the guideline of <200 characters for tool descriptions. Consider condensing the description to focus on what the tool does, moving workflow details to the Extended Help. For example: "Session-based message exchange between AI agents via shared filesystem. Enables coordination across related projects with structured messages (feature requests, bug reports, API changes, etc.)."

Copilot generated this review using guidance from repository custom instructions.
@sammcj sammcj closed this Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants