beeper-mcp

📁 plurigrid/asi 📅 7 days ago
1
总安装量
1
周安装量
#48842
全站排名
安装命令
npx skills add https://github.com/plurigrid/asi --skill beeper-mcp

Agent 安装分布

amp 1
opencode 1
kimi-cli 1
codex 1
gemini-cli 1

Skill 文档

CRITICAL: TOKENS PAY RENT

Every output token must produce actionable value. Violations:

  1. NO PASSIVE SUMMARIES – Regurgitating conversation content without action items, code, or artifacts is FORBIDDEN
  2. NO AGREEMENT WITHOUT IMPLEMENTATION – “I agree with X” must be followed by code/file/commit implementing X
  3. NO RHETORICAL QUESTIONS – Ask only when you cannot proceed without the answer
  4. NO PRAISE/VALIDATION – Skip “great question” / “you’re right” – proceed to work

When reviewing message history:

  • Extract ACTION ITEMS → create files, send messages, write code
  • Extract DECISIONS → update configs, create artifacts documenting the decision
  • Extract BLOCKERS → file issues, send follow-up messages
  • NEVER just summarize what was discussed

Enforcement: If output contains summary without artifact, STOP and create the artifact first.

Beeper MCP Skill

Access all messaging networks through Beeper’s unified interface.

Quick Start

# Search for a chat
mcp__beeper__search "contact name"

# Send a message
mcp__beeper__send_message chatID="..." text="Hello!"

# List recent chats
mcp__beeper__search_chats limit=10

Core Tools

Tool Purpose
search Find chats, groups, or people by name
search_chats List/filter chats by type, inbox, activity
search_messages Find messages by content (literal match)
get_chat Get chat details and participants
list_messages Get messages from a specific chat
send_message Send text message to a chat
archive_chat Archive/unarchive a chat
set_chat_reminder Set reminder for a chat
focus_app Open Beeper Desktop to specific chat

Search Guidelines

CRITICAL: Queries are LITERAL WORD MATCHING, not semantic search.

  • RIGHT: query="dinner" or query="flight"
  • WRONG: query="dinner plans tonight" or query="travel arrangements"

Multiple words = ALL must match. Use single keywords.

User Identity Clarification

IMPORTANT: Beeper/Matrix has TWO identifiers per user:

  1. Matrix userID: @username:beeper.com (permanent, searchable)
  2. Display name: User-chosen name (can differ from userID)

Example: User @jsmith:beeper.com may have display name “John Smith”

When search finds a match:

  • The search matched the userID OR display name
  • Chat participant lists show display names, not userIDs
  • To see userIDs, use list_messages and check senderID field

When reporting search results:

  • Cross-reference list_messages to map senderID ↔ senderName
  • Report as “username (displays as ‘Display Name’)” for clarity

Resource-Aware Message Processing

CRITICAL: Always work backwards from most recent messages to avoid:

  • Re-processing already-handled tasks
  • Repeating fixed issues
  • Heap exhaustion from loading too much history

Default Workflow (Backwards-First)

// 1. Start with most recent (no cursor = newest first)
const recent = await list_messages(chatID, { limit: 20 });

// 2. Check if already processed (use DuckDB tracking)
const unprocessed = recent.items.filter(msg => !isProcessed(msg.id));

// 3. Process only new messages
for (const msg of unprocessed) {
  await processMessage(msg);
  markProcessed(msg.id);
}

// 4. If need more history, paginate backwards
if (needsMoreContext) {
  const older = await list_messages(chatID, {
    cursor: recent.cursor,
    direction: 'before',
    limit: 20
  });
}

DuckDB Tracking Schema

CREATE TABLE IF NOT EXISTS beeper_processed_messages (
  message_id VARCHAR PRIMARY KEY,
  chat_id VARCHAR,
  processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  task_extracted BOOLEAN,
  issue_status VARCHAR  -- 'open', 'fixed', 'duplicate'
);

CREATE INDEX idx_msg_chat ON beeper_processed_messages(chat_id, processed_at DESC);

Check Before Processing

function isAlreadyFixed(messageText) {
  // Query DuckDB for similar fixed issues
  const similar = db.query(`
    SELECT issue_id, description
    FROM fixed_issues
    WHERE description % ?  -- Full-text similarity
    LIMIT 1
  `, [messageText]);

  if (similar.length > 0) {
    console.warn(`⚠️ Similar issue already fixed: ${similar[0].description}`);
    return true;
  }
  return false;
}

Workflow Patterns

Find and Message Someone

  1. search "person name" – Get chatID
  2. Verify identity: list_messages chatID="..." limit=5 – Check recent messages ONLY
  3. send_message chatID="..." text="..."

Identify Users in a Chat

  1. list_messages chatID="..." limit=10 – Get RECENT messages only (not entire history)
  2. Map userID ↔ displayName from the subset

Search Message History (Resource-Aware)

  1. search_chats to get chatIDs of relevant chats
  2. search_messages chatIDs=[...] query="keyword" limit=20 dateAfter="2026-01-08T00:00:00Z"
  3. Never omit dateAfter – prevents loading entire chat history

Monitor Unread

search_chats unreadOnly=true inbox="primary" limit=10

Filter by Network

Use accountIDs parameter after getting accounts via get_accounts.

Message Formatting

Messages support Markdown. Use sparingly for clarity.

Chat Types

  • single – Direct messages (1:1)
  • group – Group chats
  • any – All types

Inbox Filters

  • primary – Non-archived, non-low-priority
  • low-priority – Low priority inbox
  • archive – Archived chats

Resource-Aware Random Walk Pattern

NEVER pull full message history into context. Instead:

1. Query in DuckDB First

-- Store messages incrementally, query locally
CREATE TABLE IF NOT EXISTS beeper_messages (
  id VARCHAR PRIMARY KEY,
  chat_id VARCHAR,
  sender_id VARCHAR,
  sender_name VARCHAR,
  text TEXT,
  timestamp TIMESTAMPTZ,
  processed BOOLEAN DEFAULT FALSE
);

-- Sample recent messages via random walk
SELECT * FROM beeper_messages
WHERE chat_id = ?
ORDER BY RANDOM()  -- Ergodic sampling
LIMIT 5;

2. TreeSitter for Structure Extraction

# Extract code blocks from messages without loading full text
tree-sitter parse --scope source.markdown message.md \
  | grep -E "(fenced_code_block|code_span)"

3. Triadic Walker Pattern

MINUS (-1): Validate message exists in DuckDB before fetching
ERGODIC (0): Random walk sample from local cache
PLUS (+1): Fetch ONLY if not in cache, with strict limit

4. Context Budget Enforcement

CONTEXT_BUDGET = 10000  # chars
current_context = 0

def safe_fetch(chat_id, limit=5):
    # Check DuckDB cache first
    cached = db.query("SELECT * FROM beeper_messages WHERE chat_id = ? LIMIT ?", chat_id, limit)
    if len(cached) >= limit:
        return cached  # Zero network cost

    # Fetch only missing, with limit
    remaining = limit - len(cached)
    fresh = mcp__beeper__list_messages(chatID=chat_id, limit=remaining)

    # Enforce budget
    for msg in fresh.items:
        msg_size = len(msg.get('text', ''))
        if current_context + msg_size > CONTEXT_BUDGET:
            break
        current_context += msg_size
        db.insert("beeper_messages", msg)

    return db.query("SELECT * FROM beeper_messages WHERE chat_id = ? LIMIT ?", chat_id, limit)

5. SICP Lazy Evaluation

;; Don't fetch until actually needed
(define (beeper-messages chat-id)
  (delay
    (mcp__beeper__list_messages chatID: chat-id limit: 5)))

;; Only force when required
(define (get-latest-sender chat-id)
  (let ((msgs (force (beeper-messages chat-id))))
    (cdar msgs)))  ; Just sender from first message

GF(3) Integration

This skill operates as ERGODIC (0) in triadic compositions:

  • Coordinates message flow between networks
  • Synthesizes cross-platform conversations
  • Neutral hub for communication triads

Triadic Fetch Strategy

Trit Role Beeper Action
MINUS (-1) Validator Check DuckDB cache, reject if stale
ERGODIC (0) Coordinator Random walk sample, enforce budget
PLUS (+1) Generator Fetch fresh data, strict limit param

Conversation Branch Awareness (Higher-Order Wiring)

Track conversation threads as wiring diagrams – morphisms between topic states.

Branch Detection Schema

CREATE TABLE IF NOT EXISTS beeper_conversation_branches (
  branch_id VARCHAR PRIMARY KEY,
  chat_id VARCHAR NOT NULL,
  parent_branch_id VARCHAR,  -- NULL for root
  topic VARCHAR NOT NULL,
  first_message_id VARCHAR,
  last_message_id VARCHAR,
  status VARCHAR DEFAULT 'open',  -- 'open', 'resolved', 'merged', 'stale'
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  resolved_at TIMESTAMP,
  FOREIGN KEY (parent_branch_id) REFERENCES beeper_conversation_branches(branch_id)
);

CREATE TABLE IF NOT EXISTS beeper_branch_transitions (
  from_branch VARCHAR,
  to_branch VARCHAR,
  transition_type VARCHAR,  -- 'fork', 'merge', 'abandon', 'resolve'
  message_id VARCHAR,       -- message that triggered transition
  timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (from_branch, to_branch, message_id)
);

Wiring Diagram Structure

Conversation as Category:
- Objects: Topic states (branches)
- Morphisms: Message sequences that transform one topic to another
- Composition: Thread continuation

           ┌─────────────────┐
           │  patents (open) │◄──── current focus
           └────────┬────────┘
                    │ fork @ msg:838941332
    ┌───────────────┼───────────────┐
    ▼               ▼               ▼
┌────────┐   ┌───────────┐   ┌──────────┐
│ GF(3)  │   │bisimulation│   │ toad OOM │
│resolved│   │ resolved   │   │  open    │
└────────┘   └───────────┘   └──────────┘

Branch Detection Heuristics

def detect_branch(messages: list) -> list[Branch]:
    branches = []
    current_topic = None

    for msg in messages:
        # Topic markers
        if msg.text.startswith('**Re:') or msg.text.startswith('Re:'):
            # Explicit reply = potential branch
            topic = extract_topic(msg.text)
            if topic != current_topic:
                branches.append(Branch(
                    topic=topic,
                    fork_message=msg.id,
                    parent=current_topic
                ))

        # Numbered lists often indicate parallel threads
        if re.match(r'^\d+\.', msg.text):
            items = extract_numbered_items(msg.text)
            for item in items:
                branches.append(Branch(topic=item, parent=current_topic))

        # Questions create potential branches
        if msg.text.strip().endswith('?'):
            branches.append(Branch(
                topic=f"Q: {msg.text[:50]}",
                status='awaiting_response'
            ))

    return branches

Zigger Chat Branch State

Track active branches in zigger conversation:

-- Query current branch state for a chat
SELECT
  b.branch_id,
  b.topic,
  b.status,
  COUNT(t.to_branch) as child_count
FROM beeper_conversation_branches b
LEFT JOIN beeper_branch_transitions t ON b.branch_id = t.from_branch
WHERE b.chat_id = '!NhltGRLZWLUeHEBiFT:beeper.com'  -- zigger
GROUP BY b.branch_id
ORDER BY b.created_at DESC;

Before Responding: Check Branch Context

def get_branch_context(chat_id: str) -> dict:
    """Always call before responding to understand conversation topology."""

    # Get open branches
    open_branches = db.query("""
        SELECT topic, status, first_message_id
        FROM beeper_conversation_branches
        WHERE chat_id = ? AND status = 'open'
    """, chat_id)

    # Get unresolved questions
    questions = db.query("""
        SELECT topic FROM beeper_conversation_branches
        WHERE chat_id = ? AND topic LIKE 'Q:%' AND status = 'awaiting_response'
    """, chat_id)

    return {
        'open_branches': open_branches,
        'unanswered_questions': questions,
        'should_address': questions[0] if questions else open_branches[0]
    }

Wiring Composition Rules

  1. Fork: One message spawns multiple topics → create child branches
  2. Merge: Response addresses multiple branches → mark as merged
  3. Resolve: Explicit closure (“done”, “fixed”, “shipped”) → mark resolved
  4. Abandon: No activity for 7 days → mark stale

Integration with Tokens Pay Rent

When reviewing messages, branch tracking prevents:

  • Re-answering resolved questions
  • Missing open threads
  • Losing context on forked discussions

Update branch state as side effect of every beeper interaction.

Sending Files via CLI

The MCP send_message tool only supports text. For file attachments, use matrix-commander CLI.

Prerequisites

# Install matrix-commander via nix (requires olm exception)
mkdir -p ~/.config/nixpkgs
echo '{ permittedInsecurePackages = [ "olm-3.2.16" ]; }' > ~/.config/nixpkgs/config.nix
nix-env -iA nixpkgs.matrix-commander

Setup Credentials (One-Time)

Extract Matrix credentials from Beeper’s local database:

# Get credentials from Beeper's account.db
sqlite3 ~/Library/Application\ Support/BeeperTexts/account.db "SELECT * FROM account;"
# Output format: user_id|device_id|access_token|homeserver

# Create matrix-commander credentials
mkdir -p ~/.config/matrix-commander/store
cat > ~/.config/matrix-commander/credentials.json << 'EOF'
{
    "homeserver": "https://matrix.beeper.com",
    "user_id": "@YOUR_USER:beeper.com",
    "device_id": "YOUR_DEVICE_ID",
    "access_token": "syt_YOUR_TOKEN",
    "room_id": ""
}
EOF

Find Chat Room ID

# Use Beeper MCP to search for chat
BEEPER_ACCESS_TOKEN=$(/Users/alice/.cargo/bin/fnox get BEEPER_ACCESS_TOKEN --age-key-file /Users/alice/.age/key.txt) \
  curl -s -H "Authorization: Bearer $BEEPER_ACCESS_TOKEN" \
  "http://[::1]:23373/v1/chats?limit=100" | \
  python3 -c "import json,sys; [print(f\"{c['title']}: {c['id']}\") for c in json.load(sys.stdin)['items'] if 'TARGET' in c.get('title','').lower()]"

Send Files

# Send single file with message
~/.nix-profile/bin/matrix-commander \
  -c ~/.config/matrix-commander/credentials.json \
  -s ~/.config/matrix-commander/store \
  --room '!ROOM_ID:beeper.com' \
  -f /path/to/file.ext \
  -m "Description of file"

# Send multiple files
~/.nix-profile/bin/matrix-commander \
  -c ~/.config/matrix-commander/credentials.json \
  -s ~/.config/matrix-commander/store \
  --room '!ROOM_ID:beeper.com' \
  -f /path/to/file1.pdf /path/to/file2.db \
  -m "Attached files"

Quick Send Function

Add to shell config (~/.bashrc or ~/.zshrc):

beeper-send-file() {
  local room_id="$1"
  local file="$2"
  local message="${3:-Sent via CLI}"
  
  ~/.nix-profile/bin/matrix-commander \
    -c ~/.config/matrix-commander/credentials.json \
    -s ~/.config/matrix-commander/store \
    --room "$room_id" \
    -f "$file" \
    -m "$message" 2>&1 | tail -3
}

# Usage: beeper-send-file '!NhltGRLZWLUeHEBiFT:beeper.com' /tmp/data.db "Database backup"

Common Room IDs

Query and cache frequently-used room IDs:

# Save to ~/.config/beeper-rooms.txt
cat > ~/.config/beeper-rooms.txt << 'EOF'
barton-matrix=!NhltGRLZWLUeHEBiFT:beeper.com
# Add more as needed
EOF

# Helper to lookup
beeper-room() {
  grep "^$1=" ~/.config/beeper-rooms.txt | cut -d= -f2
}

# Usage: beeper-send-file "$(beeper-room barton-matrix)" /tmp/file.db

Troubleshooting

  • “Missing session” warnings: Normal for encrypted rooms with many devices, file still sends
  • E147 errors: Check file exists and is readable
  • M_UNKNOWN_TOKEN: Re-extract token from Beeper’s account.db (it may have rotated)

MCP Server Config

{
  "beeper": {
    "command": "/bin/sh",
    "args": [
      "-c",
      "BEEPER_ACCESS_TOKEN=$(/Users/alice/.cargo/bin/fnox get BEEPER_ACCESS_TOKEN --age-key-file /Users/alice/.age/key.txt) exec npx -y @beeper/desktop-mcp"
    ]
  }
}

Requires:

  • fnox at /Users/alice/.cargo/bin/fnox
  • Age key at /Users/alice/.age/key.txt
  • npx in PATH
  • Beeper Desktop running
  • For file sending: matrix-commander via nix