Bird — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Bird (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A personal assistant MCP (Model Context Protocol) server that integrates with Todoist, Anki, and Obsidian to help you learn, organize, and stay productive. Built with Python and FastMCP, fully packaged with uv and Docker-compatible.
# Quick start with uv
uv venv && source .venv/bin/activate
uv pip install -e .
cp .env.example .env # Edit with your API tokens
python -m bird_mcp.server
# Add to Claude Code
claude mcp add --transport stdio --scope local bird-personal-assistant -- python -m bird_mcp.server
# Build Docker image
docker build -t bird-mcp .
# Run with Docker
docker run -i --rm --env-file .env bird-mcpTodoist Integration (11 tools):
Anki Integration (14 tools):
Obsidian Integration (8 tools):
Google Calendar Integration (10 tools):
Health Check:
Todoist API Token:
AnkiConnect Setup (Optional):
2055492159http://localhost:8765 by defaultGoogle Calendar Setup (Optional): See the Google Calendar Setup Guide section below for detailed OAuth2 configuration instructions.
Create a .env file in the project root:
# Required
TODOIST_API_TOKEN=your_todoist_token_here
# Optional
ANKI_CONNECT_URL=http://localhost:8765
OBSIDIAN_VAULT_PATH=/path/to/your/obsidian/vault
GOOGLE_CALENDAR_CREDENTIALS_PATH=/path/to/credentials.jsonuv is a fast Python package installer and resolver. The project is fully configured to work with uv.
#### Install uv
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
# Or with pip
pip install uv#### Install Dependencies
# Install all dependencies (including the package itself)
uv sync
# Or install in editable mode
uv pip install -e .#### Run the Server
# Direct execution
uv run python -m bird_mcp.server
# Or using mcp CLI (for development with inspector)
uv run mcp dev src/bird_mcp/server.pyThe mcp dev command will start the MCP Inspector on http://localhost:6274 for testing and debugging.
#### Create Virtual Environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate#### Install Dependencies
pip install -r requirements.txt
pip install -e .#### Run the Server
python -m bird_mcp.serverImportant for Obsidian Integration: If you want to use Obsidian features with Docker, you'll need to mount your vault as a volume (see below).
#### Build and Run with Docker Compose
# 1. Create .env file with your configuration
cp .env.example .env
# 2. Edit .env and configure:
# - TODOIST_API_TOKEN (required)
# - ANKI_CONNECT_URL (optional, default: http://host.docker.internal:8765)
# - OBSIDIAN_VAULT_PATH (optional, for Obsidian integration)
# 3. Build and start the production container
docker-compose up -d
# Or start the development container with MCP Inspector
docker-compose --profile dev up -d bird-mcp-dev
# View logs
docker-compose logs -f
# Stop the container
docker-compose down#### Mounting Obsidian Vault (Required for Obsidian Integration)
If you want to use Obsidian features, you need to mount your vault into the container. Edit docker-compose.yml:
services:
bird-mcp:
# ... existing configuration
volumes:
# Mount your Obsidian vault (read-write access)
- /path/to/your/obsidian/vault:/app/vault:rw
environment:
- TODOIST_API_TOKEN=${TODOIST_API_TOKEN}
- ANKI_CONNECT_URL=${ANKI_CONNECT_URL:-http://host.docker.internal:8765}
- OBSIDIAN_VAULT_PATH=/app/vault # Path inside containerThen update your .env:
# .env
OBSIDIAN_VAULT_PATH=/app/vault # This matches the container pathNote: The host.docker.internal hostname allows the Docker container to connect to AnkiConnect running on your host machine.
#### Build and Run with Docker Directly
# Build production image
docker build -t bird-mcp .
# Run production container (basic, no Obsidian)
docker run -d --name bird-mcp --env-file .env bird-mcp
# Run with Obsidian vault mounted
docker run -d --name bird-mcp \
--env-file .env \
-v /path/to/your/obsidian/vault:/app/vault:rw \
-e OBSIDIAN_VAULT_PATH=/app/vault \
bird-mcp
# Build development image (with MCP Inspector)
docker build -f Dockerfile.dev -t bird-mcp-dev .
# Run development container with all features
docker run -d --name bird-mcp-dev \
--env-file .env \
-p 6274:6274 -p 6277:6277 \
-v /path/to/your/obsidian/vault:/app/vault:rw \
-e OBSIDIAN_VAULT_PATH=/app/vault \
bird-mcp-dev
# Access MCP Inspector at http://localhost:6274#### Using Docker with Claude Desktop
To use the Dockerized MCP server with Claude Desktop, configure it in your claude_desktop_config.json:
{
"mcpServers": {
"bird": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"--env-file",
"/absolute/path/to/bird/.env",
"-v",
"/absolute/path/to/obsidian/vault:/app/vault:rw",
"bird-mcp"
]
}
}
}Important: Use absolute paths for --env-file and volume mounts.
The project is properly configured for packaging with both uv and standard Python tools, following PEP 621 standards.
The project uses a src layout for better packaging practices:
bird/
├── src/
│ └── bird_mcp/
│ ├── __init__.py # Package initialization with version
│ ├── server.py # Main MCP server with tool registrations
│ ├── todoist_tools.py # Todoist API integration (11 tools)
│ ├── anki_tools.py # AnkiConnect API integration (14 tools)
│ ├── obsidian_tools.py # Obsidian vault integration (8 tools)
│ ├── google_calendar_tools.py # Google Calendar API integration (10 tools)
│ └── utils.py # Error handling and retry decorators
├── pyproject.toml # PEP 621 package configuration
├── requirements.txt # Dependencies list (for pip)
├── uv.lock # Locked dependencies (for uv reproducibility)
├── .dockerignore # Files excluded from Docker builds
├── Dockerfile # Production Docker image (uses uv)
├── Dockerfile.dev # Development Docker image (with MCP Inspector)
├── docker-compose.yml # Multi-service Docker configuration
├── .env.example # Example environment variables
└── README.md # This file#### Method 1: With uv (Recommended)
uv is a fast Python package installer and resolver that provides better dependency management and faster builds.
# Install the package in editable mode (for development)
uv pip install -e .
# This allows you to modify source code and see changes immediately
# The package is installed but points to your source directory
# Build distribution packages
uv build
# This creates two distribution formats:
# - dist/bird_mcp-0.1.0.tar.gz (source distribution)
# - dist/bird_mcp-0.1.0-py3-none-any.whl (wheel - faster to install)What's in the wheel?
.pyc files)src/bird_mcp/pyproject.toml#### Method 2: With pip and setuptools
# Install the package in editable mode
pip install -e .
# Install build tool
pip install build
# Build distribution packages
python -m build
# This creates the same dist/ files as uv build#### Install from Local Directory
# For development (editable install - changes reflect immediately)
cd /path/to/bird
pip install -e .
# For production (regular install - creates a copy)
pip install /path/to/bird
# With uv
uv pip install -e /path/to/bird#### Install from Git Repository
# Direct from git (requires git URL)
pip install git+https://github.com/yourusername/bird.git
# With uv
uv pip install git+https://github.com/yourusername/bird.git
# Specific branch or tag
pip install git+https://github.com/yourusername/bird.git@main#### Install from Built Wheel
# After building with 'uv build' or 'python -m build'
pip install dist/bird_mcp-0.1.0-py3-none-any.whl
# With uv
uv pip install dist/bird_mcp-0.1.0-py3-none-any.whlWhen ready to publish to PyPI for public distribution:
# 1. Ensure version is updated in pyproject.toml
# 2. Build fresh distribution packages
uv build
# 3. Check the distribution
twine check dist/*
# 4. Upload to TestPyPI first (for testing)
uv publish --publish-url https://test.pypi.org/legacy/
# 5. Test installation from TestPyPI
pip install -i https://test.pypi.org/simple/ bird-mcp
# 6. If everything works, publish to real PyPI
uv publish
# Users can then install with:
# pip install bird-mcpAfter installing the package, verify it's working:
# Check package is installed
pip list | grep bird-mcp
# Check version
python -c "import bird_mcp; print(bird_mcp.__version__)"
# Run the MCP server
python -m bird_mcp.server
# You should see:
# INFO - Initializing Todoist integration...
# INFO - Todoist integration initialized successfully
# ...The project uses multiple dependency files for different tools:
#### Updating Dependencies
# With uv (recommended - updates uv.lock automatically)
uv add package-name
uv add --dev package-name # for dev dependencies
# With pip (requires manual updates)
# 1. Edit pyproject.toml dependencies list
# 2. Reinstall
pip install -e .
# 3. Update requirements.txt
pip freeze > requirements.txt#### Syncing Dependencies
# Install exact versions from uv.lock (reproducible)
uv sync --frozen
# Update all dependencies to latest compatible versions
uv sync
# Update a specific package
uv add package-name --upgradeTo use this MCP server with Claude Desktop or Cursor, add the following to your MCP settings file:
Location:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json~/.config/Claude/claude_desktop_config.jsonConfiguration (using Python directly):
{
"mcpServers": {
"bird": {
"command": "python",
"args": ["-m", "bird_mcp.server"],
"env": {
"TODOIST_API_TOKEN": "your_token_here",
"ANKI_CONNECT_URL": "http://localhost:8765",
"OBSIDIAN_VAULT_PATH": "/path/to/vault"
}
}
}
}Configuration (using uv):
{
"mcpServers": {
"bird": {
"command": "uv",
"args": ["run", "python", "-m", "bird_mcp.server"],
"env": {
"TODOIST_API_TOKEN": "your_token_here",
"ANKI_CONNECT_URL": "http://localhost:8765",
"OBSIDIAN_VAULT_PATH": "/path/to/vault"
}
}
}
}Configuration (using Docker):
{
"mcpServers": {
"bird": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"--env-file",
"/path/to/.env",
"bird-mcp"
]
}
}
}Note: After updating the configuration file, restart Claude Desktop or Cursor for changes to take effect.
To use this MCP server with Claude Code (the CLI tool), use the claude mcp add command:
#### Quick Setup
# Add the server to Claude Code with local scope
claude mcp add --transport stdio --scope local bird-personal-assistant -- python -m bird_mcp.server
# Or with uv
claude mcp add --transport stdio --scope local bird-personal-assistant -- uv run python -m bird_mcp.server#### Verify Connection
# List all configured MCP servers
claude mcp list
# You should see:
# bird-personal-assistant: python -m bird_mcp.server - ✓ Connected#### Environment Variables for Claude Code
Claude Code will use the .env file in your project directory. Ensure it's properly configured:
# Navigate to your project directory
cd /path/to/bird
# Create .env from example
cp .env.example .env
# Edit .env with your credentials
# Required:
TODOIST_API_TOKEN=your_token_here
# Optional:
ANKI_CONNECT_URL=http://localhost:8765
OBSIDIAN_VAULT_PATH=/path/to/your/obsidian/vaultImportant: Make sure you're in the project directory when running claude commands, or the server won't be able to find the .env file.
#### Testing the Integration
Once configured, you can test the server with Claude Code:
# Start a conversation
claude
# Try a command like:
"Check the health of my MCP server"
"Create a Todoist task named 'Test from Claude Code'"
"Show me my Obsidian vault statistics"#### Removing the Server
If you need to remove the server from Claude Code:
# Remove the MCP server
claude mcp remove bird-personal-assistantThe Bird MCP server is built using a modular architecture:
Core Components:
@mcp.tool() decoratorstodoist-api-python libraryasyncio.to_thread() for sync API callsDesign Patterns:
{"success": bool, "error": str} responsesbird/
├── src/
│ └── bird_mcp/
│ ├── __init__.py # Package initialization with version
│ ├── server.py # Main MCP server (44 tools total)
│ ├── todoist_tools.py # Todoist API integration (11 tools)
│ ├── anki_tools.py # AnkiConnect API integration (14 tools)
│ ├── obsidian_tools.py # Obsidian vault integration (8 tools)
│ ├── google_calendar_tools.py # Google Calendar API integration (10 tools)
│ └── utils.py # Error handling and retry decorators
├── Dockerfile # Production Docker image (uses uv)
├── Dockerfile.dev # Development Docker image (uses uv)
├── docker-compose.yml # Docker Compose configuration
├── .dockerignore # Files excluded from Docker builds
├── pyproject.toml # Package configuration (PEP 621)
├── requirements.txt # Dependencies list
├── uv.lock # Locked dependencies (uv)
├── .env.example # Example environment variables
└── README.md # This file# Clone the repository
git clone https://github.com/yourusername/bird.git
cd bird
# Create virtual environment with uv (recommended)
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Or with Python's venv
python -m venv venv
source venv/bin/activate
# Install dependencies in editable mode
uv pip install -e ".[dev]"
# Or with pip
pip install -e ".[dev]"
# Set up environment variables
cp .env.example .env
# Edit .env with your API tokens
# Run the server
python -m bird_mcp.serverTo add a new MCP tool to the server, follow these steps:
#### 1. Create or Update Tool Implementation
Create a new tool class in a separate file (e.g., src/bird_mcp/my_service_tools.py) or add methods to an existing tool class:
"""My Service integration tools."""
from typing import Any
import httpx
class MyServiceTools:
"""Tools for interacting with My Service API."""
def __init__(self, api_key: str):
self.api_key = api_key
self.client = httpx.AsyncClient()
async def do_something(self, param: str) -> dict[str, Any]:
"""Do something with the service.
Args:
param: Some parameter
Returns:
Dictionary with success status and result/error
"""
try:
# Your implementation here
response = await self.client.get(
"https://api.example.com/endpoint",
headers={"Authorization": f"Bearer {self.api_key}"},
params={"param": param}
)
response.raise_for_status()
return {"success": True, "data": response.json()}
except Exception as e:
return {"success": False, "error": str(e)}#### 2. Initialize the Tool in server.py
Add initialization code in src/bird_mcp/server.py:
# At the top with other imports
from bird_mcp.my_service_tools import MyServiceTools
# In the initialization section (around line 40-67)
my_service_key = os.getenv("MY_SERVICE_API_KEY")
my_service = None
if my_service_key:
try:
logger.info("Initializing My Service integration...")
my_service = MyServiceTools(my_service_key)
logger.info("My Service integration initialized successfully")
except Exception as e:
logger.warning(f"My Service integration disabled: {e}")
else:
logger.info("My Service integration disabled (MY_SERVICE_API_KEY not set)")#### 3. Register the Tool with MCP
Add tool registration functions in src/bird_mcp/server.py:
# My Service Tools
@mcp.tool()
async def my_service_do_something(param: str) -> dict[str, Any]:
"""Do something with My Service.
Args:
param: Some parameter description
Returns:
Dictionary with success status and result
"""
if not my_service:
return {"success": False, "error": "My Service integration not configured"}
return await my_service.do_something(param=param)#### 4. Add Health Check (Optional)
Update the health_check tool to include your new service:
# In the health_check function (around line 73-148)
# Check My Service
if my_service:
try:
logger.info("Performing My Service health check...")
result = await my_service.do_something("test")
results["services"]["my_service"] = {
"status": "connected" if result["success"] else "error",
"message": (
"Successfully connected to My Service"
if result["success"]
else result.get("error")
),
}
except Exception as e:
logger.error(f"My Service health check failed: {e}")
results["services"]["my_service"] = {"status": "error", "message": str(e)}
else:
results["services"]["my_service"] = {
"status": "disabled",
"message": "My Service integration not configured (set MY_SERVICE_API_KEY)",
}#### 5. Update Dependencies (if needed)
If your new tool requires additional dependencies:
For uv:
uv add package-nameFor pip:
pip install package-name
# Then update requirements.txt
pip freeze > requirements.txtOr manually add to pyproject.toml:
dependencies = [
# ... existing dependencies
"package-name>=1.0.0",
]#### 6. Update Environment Variables
Add your new service's API key to .env.example and document it:
# .env.example
MY_SERVICE_API_KEY=your_api_key_here#### 7. Test Your Tool
# Start the MCP server with inspector
uv run mcp dev src/bird_mcp/server.py
# Or run directly
uv run python -m bird_mcp.serverOpen http://localhost:6274 (if using mcp dev) to test your tool in the MCP Inspector.
#### 8. Update Documentation
# Install dev dependencies
uv pip install -e ".[dev]"
# Run tests
pytest
# With coverage
pytest --cov=src/bird_mcp --cov-report=html# Format code with black
black src/
# Lint code with ruff
ruff check src/
# Auto-fix linting issues
ruff check --fix src/The MCP Inspector is a web-based tool for testing and debugging MCP servers:
# Start server with inspector
uv run mcp dev src/bird_mcp/server.py
# Or with Docker
docker-compose --profile dev up bird-mcp-devThen open http://localhost:6274 in your browser. You'll need the authentication token shown in the terminal output.
The Google Calendar integration requires OAuth2 authentication. Follow these steps to set it up:
https://www.googleapis.com/auth/calendarcredentials.json in a secure locationUpdate your .env file with the path to your credentials:
# Google Calendar Integration
GOOGLE_CALENDAR_CREDENTIALS_PATH=/absolute/path/to/credentials.json
# Optional: Custom token storage location
# GOOGLE_CALENDAR_TOKEN_PATH=/path/to/token.pickleImportant: Use absolute paths, not relative paths like ~/ or ./
The first time you use the Google Calendar integration, you'll need to authenticate:
python -m bird_mcp.server~/.bird_mcp/google_calendar_token.pickle~/.bird_mcp/google_calendar_token.pickle"Credentials file not found"
GOOGLE_CALENDAR_CREDENTIALS_PATH points to the correct file/Users/username/credentials.json not ~/credentials.json"Access blocked: This app's request is invalid"
"Invalid grant" or "Token has been expired or revoked"
rm ~/.bird_mcp/google_calendar_token.pickleBrowser doesn't open during authentication
credentials.json secure - it contains your OAuth2 client secretWhen using Docker, you need to mount both the credentials and token files:
# Create directory for credentials
mkdir -p ~/.bird_mcp
# Copy credentials file
cp /path/to/credentials.json ~/.bird_mcp/
# Run with Docker, mounting the credentials directory
docker run -i --rm \
--env-file .env \
-v ~/.bird_mcp:/root/.bird_mcp:rw \
-e GOOGLE_CALENDAR_CREDENTIALS_PATH=/root/.bird_mcp/credentials.json \
-e GOOGLE_CALENDAR_TOKEN_PATH=/root/.bird_mcp/google_calendar_token.pickle \
bird-mcpNote: The first time you run with Docker, the OAuth flow may not work automatically. You'll need to:
Symptoms: Server fails to start or crashes immediately
Solutions:
# Ensure .env file exists
ls -la .env
# Check TODOIST_API_TOKEN is set
cat .env | grep TODOIST_API_TOKEN # Requires Python 3.10+
python --version # With uv
uv sync
# With pip
pip install -r requirements.txt # Run server directly to see errors
python -m bird_mcp.server
# Look for error messages in output # Check if package is installed
pip list | grep bird-mcp
# Reinstall if missing
pip install -e .Symptoms: Claude Code can't find or connect to the MCP server
Solutions:
# List configured servers
claude mcp list
# Should show: bird-personal-assistant: python -m bird_mcp.server - ✓ Connected # The server needs to find .env file
pwd # Should show /path/to/bird
ls .env # Should exist # Run server to check for errors
python -m bird_mcp.server
# Should see initialization logs:
# INFO - Initializing Todoist integration...
# INFO - Todoist integration initialized successfully # Remove and re-add
claude mcp remove bird-personal-assistant
claude mcp add --transport stdio --scope local bird-personal-assistant -- python -m bird_mcp.server # Print environment variable (should show your token)
echo $TODOIST_API_TOKEN
# If empty, source .env
export $(cat .env | xargs)Symptoms: Claude Desktop shows server as disconnected or tools don't appear
Solutions:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json~/.config/Claude/claude_desktop_config.json # Validate JSON syntax
cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | python -m json.tool # Check which Python is being used
which python
# Use absolute path in config if needed
/full/path/to/python -m bird_mcp.serverTODOIST_API_TOKEN is set in the env sectionOBSIDIAN_VAULT_PATHpython -m bird_mcp.server directly.env filepip install -e . or uv pip install -e .)Symptoms: Docker container won't start or crashes
Solutions:
# Check if ports are in use
lsof -i :6274
lsof -i :6277
# Kill process using port if needed
kill -9 <PID> # Ensure .env file exists
ls -la .env
# Check format (no quotes around values)
cat .env # Rebuild without cache
docker-compose build --no-cache
# Or with docker directly
docker build --no-cache -t bird-mcp . # Check vault path exists
ls -la /path/to/obsidian/vault
# Ensure path is absolute in docker-compose.yml
# Correct: /Users/username/obsidian_vault:/app/vault:rw
# Wrong: ~/obsidian_vault:/app/vault:rw # With docker-compose
docker-compose logs -f bird-mcp
# With docker
docker logs bird-mcpSymptoms: Todoist tools return errors or fail
Solutions:
# Get new token from https://todoist.com/app/settings/integrations/developer
# Update .env file with new token # Test with curl
curl https://api.todoist.com/rest/v2/projects \
-H "Authorization: Bearer YOUR_TOKEN_HERE"Symptoms: Anki tools fail or return connection errors
Solutions:
# Open Anki → Tools → Add-ons
# Should see "AnkiConnect" in the list
# Code: 2055492159 # Anki must be open for AnkiConnect to work
# Start Anki application # Test connection
curl http://localhost:8765
# Should return AnkiConnect API info # Use host.docker.internal instead of localhost
# Already configured in docker-compose.yml # Run health check to test all services
# In Claude Code:
"Check the health of my MCP server"Symptoms: Obsidian tools return "vault not found" errors
Solutions:
# Check path exists
ls -la /path/to/obsidian/vault
# Update .env
OBSIDIAN_VAULT_PATH=/absolute/path/to/vault # Correct: /Users/username/obsidian_vault
# Wrong: ~/obsidian_vault
# Wrong: ../obsidian_vault # Check read/write permissions
ls -ld /path/to/obsidian/vault
# Should show drwxr-xr-x or similarOBSIDIAN_VAULT_PATH=/app/vault inside container"ModuleNotFoundError: No module named 'bird_mcp'"
pip install -e . or uv pip install -e ."Todoist integration disabled"
TODOIST_API_TOKEN in .env file"AnkiConnect error: Connection refused"
"Vault path does not exist"
OBSIDIAN_VAULT_PATH is correct absolute path"'list' object has no attribute 'id'"
If you're still experiencing issues:
# Run with verbose output
python -m bird_mcp.server 2>&1 | tee server.log # In Claude Code, ask:
"Run a health check on the MCP server"MIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.