I recently built a Bluesky MCP server using Python. Here are the key lessons that I learned.

1. Testing MCP Tools with Pytest

Use Pytest to test the MCP tool:

@pytest.mark.asyncio
async def test_create_and_delete_post():
    """Test creating a post and then deleting it."""
    # Create client session
    async with client_session(mcp._mcp_server) as client:
        # Create a post with a unique identifier to avoid duplicate posts
        unique_id = str(uuid.uuid4())[:8]
        test_text = f"Test post from Bluesky MCP test suite - {unique_id}"
        create_params = {"text": test_text}

        # Call the send_post tool
        result = await client.call_tool("send_post", create_params)

⭐ During development with Claude Code Opus v4 I noticed that it was putting debug prints into the code to help itself troubleshoot! It was then removing the debug prints once it figured out the problem. I didn’t notice the behavior with earlier versions of Claude, I love this improvement!

2. Claude Code configuration with CLAUDE.md

I like to keep my CLAUDE.md file brief. I also use Context7 MCP to load docs about packages, and by adding a Required Context section to this file Claude knows to load the context from Context7 before starting to work. Here’s is what I used for my CLAUDE.md file:

# CLAUDE.md

## Primary Directives
1. Design from broad structures to specific details.
2. Keep design simple but not simpler.
3. Check with user often.

## Secondary Directives
- Test often.
- Follow Git Conventions and Style Guide.

## Required Context
- context7 gwbischof/python-mcp
- context7 marshalx/atproto
- @<https://github.com/MarshalX/atproto/blob/main/packages/atproto_client/client/client.py>

## Commands
- Lint: `uv run ruff check .`- Tests: `uv run pytest`- Format: `uv run black .`## Style Guide- Format: Black
- Imports: isort (stdlib → third-party → first-party)
- Naming: snake_case, PascalCase, UPPER_SNAKE_CASE
- Errors: Specific exceptions with context

Modern syntax: CLAUDE.md supports importing files with @path/to/file and loads from ~/.claude/CLAUDE.md globally.

This video was really helpful for me to understand CLAUDE.md better:

Taking Claude to the Next Level

3. UVX Makes Installation Simple

Traditional Python package distribution creates some friction for installing MCP servers. UVX eliminates the setup hassle because it allows installation from a GitHub repo:

<aside> <img src="/icons/skull_gray.svg" alt="/icons/skull_gray.svg" width="40px" />

Pin MCP versions in your config file. Without version pinning, MCPs auto-update on restart, creating a security risk where maintainers could inject malicious code that executes when you restart your Agent.

</aside>

{
  "mcpServers": {
    "bluesky-social": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/gwbischof/[email protected]", "bluesky-social-mcp"],
      "env": {
        "BLUESKY_IDENTIFIER": "your-handle.bsky.social",
        "BLUESKY_APP_PASSWORD": "your-app-password"
      }
    }
  }
}

UVX allows users to install your MCP with only 2 steps: