@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 memodelete_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:
- Check Deno KV for a stored keypair (keyed by handle)
- If not found, generate a new X-Wing keypair
- Upload the public key to your PDS as an
app.cistern.pubkeyrecord - Store the keypair in Deno KV at
./cistern-mcp.db - 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_KEYandCISTERN_MCP_PUBLIC_KEY_URIenvironment variables, or delete thecistern-mcp.dbSQLite files to force regeneration. You'll need to manually delete the old public key record from your PDS using a tool like pdsls.dev.