Encrypted, ephemeral, private memos on atproto

@cistern/mcp#

Model Context Protocol (MCP) server for Cistern, enabling AI assistants to retrieve and manage encrypted memos.

Features#

  • Dual Transport Support: stdio for local integrations (Claude Desktop) and HTTP for remote deployments
  • Automatic Keypair Management: Generates and persists keypairs in Deno KV on first launch
  • Two MCP Tools:
    • next_memo: Retrieve the next outstanding memo
    • delete_memo: Delete a memo after handling it

Installation#

Prerequisites#

  • Deno 2.0+
  • AT Protocol account with app password
  • Bluesky handle (e.g., yourname.bsky.social)

Environment Variables#

Required:

CISTERN_MCP_HANDLE=yourname.bsky.social
CISTERN_MCP_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx

Optional (for existing keypair):

CISTERN_MCP_PRIVATE_KEY=base64-encoded-private-key
CISTERN_MCP_PUBLIC_KEY_URI=at://did:plc:abc.../app.cistern.pubkey/xyz

Required for HTTP mode:

CISTERN_MCP_BEARER_TOKEN=your-secret-bearer-token

Usage#

stdio Mode (Claude Desktop)#

Run the server in stdio mode for local integrations:

cd packages/mcp
deno task stdio

Add to Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "cistern": {
      "command": "deno",
      "args": [
        "task",
        "--cwd",
        "/path/to/cistern/packages/mcp",
        "stdio"
      ],
      "env": {
        "CISTERN_MCP_HANDLE": "yourname.bsky.social",
        "CISTERN_MCP_APP_PASSWORD": "xxxx-xxxx-xxxx-xxxx"
      }
    }
  }
}

HTTP Mode (Remote Deployment)#

Run the server in HTTP mode for remote access:

cd packages/mcp
deno task http

The server listens on port 8000 by default. Configure your MCP client to connect via HTTP:

{
  "url": "http://localhost:8000/mcp",
  "headers": {
    "Authorization": "Bearer your-secret-bearer-token"
  }
}

Keypair Management#

On first launch without CISTERN_MCP_PRIVATE_KEY and CISTERN_MCP_PUBLIC_KEY_URI, the server will:

  1. Check Deno KV for a stored keypair (keyed by handle)
  2. If not found, generate a new X-Wing keypair
  3. Upload the public key to your PDS as an app.cistern.pubkey record
  4. Store the keypair in Deno KV at ./cistern-mcp.db
  5. Log the public key URI for reference

The keypair persists across restarts and is isolated per handle.

Example First Launch Log#

[cistern:mcp] starting in stdio mode
[cistern:mcp] no keypair found; generating new keypair for yourname.bsky.social
[cistern:mcp] generated new keypair with public key URI: at://did:plc:abc123.../app.cistern.pubkey/xyz789
[cistern:mcp] stored keypair for yourname.bsky.social

Example Subsequent Launch Log#

[cistern:mcp] starting in stdio mode
[cistern:mcp] using stored keypair for yourname.bsky.social

MCP Tools#

next_memo#

Retrieves the next outstanding memo from your PDS.

Output:

{
  "key": "3kbxyz789abc",
  "tid": "3kbxyz789abc",
  "text": "Remember to buy milk"
}

Returns "no memos remaining" when all memos have been retrieved.

delete_memo#

Deletes a memo by record key after it has been handled.

Input:

{
  "key": "3kbxyz789abc"
}

Output:

{
  "success": true
}

Development#

Testing with MCP Inspector#

deno task stdio:inspect

This launches the MCP Inspector UI for interactive testing of the stdio server.

Logs#

The server uses LogTape for structured logging:

  • [cistern:mcp]: Server lifecycle, keypair operations
  • [cistern:http]: HTTP request/response logs (HTTP mode only)

Security#

  • Bearer Authentication: Required for HTTP mode
  • Private Keys: Never transmitted; stored locally in Deno KV
  • Session Isolation: Each HTTP session gets its own Consumer instance
  • CORS: Configured for MCP protocol headers

Limitations#

  • No Keypair Deletion: The Consumer SDK doesn't currently support deleting public keys from the PDS. If you want to use a different keypair, you can either set CISTERN_MCP_PRIVATE_KEY and CISTERN_MCP_PUBLIC_KEY_URI environment variables, or delete the cistern-mcp.db SQLite files to force regeneration. You'll need to manually delete the old public key record from your PDS using a tool like pdsls.dev.