Connecting Claude to Tinderbox using MCP

I have been able to create an MCP server using home-brew, python, Claude and lots of patience

First I will upload the test server and the actual server and then install guide. I will attach quick reference to new post
Test server

!/opt/homebrew/bin/python3
"""Minimal test MCP server to verify basic functionality"""

import asyncio
import sys
from mcp.server import Server
from mcp.server.stdio import stdio_server

print("Test MCP server starting...", file=sys.stderr)

# Create server
server = Server("test-tinderbox")

# Define a simple tool handler
@server.list_tools()
async def list_tools():
    print("list_tools called!", file=sys.stderr)
    return [
        {
            "name": "test_tool",
            "description": "A simple test tool",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "message": {"type": "string", "description": "Test message"}
                },
                "required": ["message"]
            }
        }
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    print(f"call_tool called with {name}: {arguments}", file=sys.stderr)
    if name == "test_tool":
        return [{"type": "text", "text": f"Test response: {arguments.get('message', 'no message')}"}]
    return [{"type": "text", "text": f"Unknown tool: {name}"}]

# Run the server
async def main():
    print("Starting server main loop...", file=sys.stderr)
    async with stdio_server() as (read, write):
        print("Stdio streams ready", file=sys.stderr)
        await server.run(read, write, server.create_initialization_options())

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nServer stopped", file=sys.stderr)
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        import traceback
        traceback.print_exc(file=sys.stderr)

MCP Server

#!/opt/homebrew/bin/python3
"""
Tinderbox MCP Server - Full Read/Write Access
A Model Context Protocol server for Tinderbox 10 on macOS
Compatible with MCP 1.9.1
"""

import asyncio
import json
import subprocess
import os
import sys
from typing import Any, Dict, List, Optional
from datetime import datetime

# Debug output
print("Starting Tinderbox MCP Server...", file=sys.stderr)
print(f"Python: {sys.executable}", file=sys.stderr)
print(f"Version: {sys.version}", file=sys.stderr)

# MCP SDK imports
try:
    from mcp.server import Server
    from mcp.server.stdio import stdio_server
    print("MCP SDK imported successfully", file=sys.stderr)
except ImportError as e:
    print(f"ERROR: MCP SDK import failed. {e}", file=sys.stderr)
    print("Please run: pip install mcp", file=sys.stderr)
    sys.exit(1)

# Create server instance with proper initialization
server = Server("tinderbox-mcp")

# Store tools list for reuse
TOOLS_LIST = [
    {
        "name": "get_note",
        "description": "Get content and attributes of a specific note by path or ID",
        "inputSchema": {
            "type": "object",
            "properties": {
                "note_path": {"type": "string", "description": "Path to note (e.g., '/Container/Note Name') or note ID"},
                "document": {"type": "string", "description": "Document name (optional, uses frontmost if not specified)"}
            },
            "required": ["note_path"]
        }
    },
    {
        "name": "search_notes",
        "description": "Search for notes containing specific text",
        "inputSchema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search text"},
                "in_text": {"type": "boolean", "description": "Search in note text (default: true)", "default": True},
                "in_names": {"type": "boolean", "description": "Search in note names (default: true)", "default": True},
                "document": {"type": "string", "description": "Document name (optional)"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "list_notes",
        "description": "List all notes in a container or document",
        "inputSchema": {
            "type": "object",
            "properties": {
                "container_path": {"type": "string", "description": "Path to container (empty for root)"},
                "recursive": {"type": "boolean", "description": "Include notes in subcontainers (default: false)", "default": False},
                "document": {"type": "string", "description": "Document name (optional)"}
            }
        }
    },
    {
        "name": "create_note",
        "description": "Create a new note in Tinderbox",
        "inputSchema": {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Note name"},
                "text": {"type": "string", "description": "Note text content"},
                "container_path": {"type": "string", "description": "Path to container (empty for root)"},
                "attributes": {"type": "object", "description": "Additional attributes as key-value pairs"},
                "document": {"type": "string", "description": "Document name (optional)"}
            },
            "required": ["name"]
        }
    },
    {
        "name": "update_note",
        "description": "Update an existing note's content or attributes",
        "inputSchema": {
            "type": "object",
            "properties": {
                "note_path": {"type": "string", "description": "Path to note or note ID"},
                "text": {"type": "string", "description": "New text content (optional)"},
                "attributes": {"type": "object", "description": "Attributes to update as key-value pairs"},
                "document": {"type": "string", "description": "Document name (optional)"}
            },
            "required": ["note_path"]
        }
    },
    {
        "name": "delete_note",
        "description": "Delete a note from Tinderbox",
        "inputSchema": {
            "type": "object",
            "properties": {
                "note_path": {"type": "string", "description": "Path to note or note ID"},
                "document": {"type": "string", "description": "Document name (optional)"}
            },
            "required": ["note_path"]
        }
    },
    {
        "name": "create_link",
        "description": "Create a link between two notes",
        "inputSchema": {
            "type": "object",
            "properties": {
                "from_note": {"type": "string", "description": "Path or ID of source note"},
                "to_note": {"type": "string", "description": "Path or ID of destination note"},
                "link_type": {"type": "string", "description": "Type of link (optional)"},
                "document": {"type": "string", "description": "Document name (optional)"}
            },
            "required": ["from_note", "to_note"]
        }
    },
    {
        "name": "run_agent",
        "description": "Execute a Tinderbox agent",
        "inputSchema": {
            "type": "object",
            "properties": {
                "agent_path": {"type": "string", "description": "Path to agent"},
                "document": {"type": "string", "description": "Document name (optional)"}
            },
            "required": ["agent_path"]
        }
    }
]

class TinderboxHelper:
    """Helper class for Tinderbox AppleScript operations"""
    
    @staticmethod
    def run_applescript(script: str) -> str:
        """Execute AppleScript and return result"""
        try:
            result = subprocess.run(
                ['osascript', '-e', script],
                capture_output=True,
                text=True,
                check=True
            )
            return result.stdout.strip()
        except subprocess.CalledProcessError as e:
            raise Exception(f"AppleScript error: {e.stderr}")

    @staticmethod
    def escape_quotes(text: str) -> str:
        """Escape quotes for AppleScript"""
        return text.replace('"', '\\"').replace('\n', '\\n')

# Set up handlers using decorator style
print("Registering handlers with decorators...", file=sys.stderr)

@server.list_tools()
async def handle_list_tools():
    """Return list of available tools"""
    print("handle_list_tools called", file=sys.stderr)
    return TOOLS_LIST  # Return just the list, not wrapped in dict

@server.call_tool()
async def handle_call_tool(name: str, arguments: Dict[str, Any] = None) -> List[Dict[str, Any]]:
    """Handle tool calls"""
    print(f"Tool called: {name} with args: {arguments}", file=sys.stderr)
    
    if arguments is None:
        arguments = {}
    
    if name == "get_note":
        return await get_note(**arguments)
    elif name == "search_notes":
        return await search_notes(**arguments)
    elif name == "list_notes":
        return await list_notes(**arguments)
    elif name == "create_note":
        return await create_note(**arguments)
    elif name == "update_note":
        return await update_note(**arguments)
    elif name == "delete_note":
        return await delete_note(**arguments)
    elif name == "create_link":
        return await create_link(**arguments)
    elif name == "run_agent":
        return await run_agent(**arguments)
    else:
        return [{"type": "text", "text": f"Unknown tool: {name}"}]

# Tool implementations
async def get_note(note_path: str, document: Optional[str] = None) -> List[Dict[str, Any]]:
    """Get note content and attributes"""
    try:
        doc_specifier = f'document "{document}"' if document else 'front document'
        
        script = f'''
        tell application "Tinderbox 10"
            tell {doc_specifier}
                set theNote to note "{note_path}"
                set noteText to value of attribute "Text" of theNote
                set noteName to value of attribute "Name" of theNote
                set noteID to value of attribute "ID" of theNote
                set notePath to value of attribute "Path" of theNote
                set noteModified to value of attribute "Modified" of theNote
                set noteCreated to value of attribute "Created" of theNote
                
                return "{{" & ¬
                    "\\"name\\": \\"" & noteName & "\\"," & ¬
                    "\\"id\\": \\"" & noteID & "\\"," & ¬
                    "\\"path\\": \\"" & notePath & "\\"," & ¬
                    "\\"text\\": \\"" & noteText & "\\"," & ¬
                    "\\"created\\": \\"" & noteCreated & "\\"," & ¬
                    "\\"modified\\": \\"" & noteModified & "\\"" & ¬
                    "}}"
            end tell
        end tell
        '''
        
        result = TinderboxHelper.run_applescript(script)
        return [{"type": "text", "text": result}]
        
    except Exception as e:
        return [{"type": "text", "text": f"Error: {str(e)}"}]

async def search_notes(query: str, in_text: bool = True, 
                      in_names: bool = True, document: Optional[str] = None) -> List[Dict[str, Any]]:
    """Search for notes containing query text"""
    try:
        doc_specifier = f'document "{document}"' if document else 'front document'
        
        script = f'''
        tell application "Tinderbox 10"
            tell {doc_specifier}
                set foundNotes to {{}}
                set allNotes to notes
                
                repeat with aNote in allNotes
                    set noteText to value of attribute "Text" of aNote
                    set noteName to value of attribute "Name" of aNote
                    set notePath to value of attribute "Path" of aNote
                    
                    set foundInText to false
                    set foundInName to false
                    
                    if "{query}" is in noteText then set foundInText to true
                    if "{query}" is in noteName then set foundInName to true
                    
                    if (foundInText and {str(in_text).lower()}) or (foundInName and {str(in_names).lower()}) then
                        set end of foundNotes to "\\"" & notePath & "\\""
                    end if
                end repeat
                
                return "[" & (foundNotes as string) & "]"
            end tell
        end tell
        '''
        
        result = TinderboxHelper.run_applescript(script)
        return [{"type": "text", "text": f"Found notes: {result}"}]
        
    except Exception as e:
        return [{"type": "text", "text": f"Error: {str(e)}"}]

async def list_notes(container_path: str = "", recursive: bool = False, 
                    document: Optional[str] = None) -> List[Dict[str, Any]]:
    """List notes in a container"""
    try:
        doc_specifier = f'document "{document}"' if document else 'front document'
        container_spec = f'note "{container_path}"' if container_path else 'it'
        
        script = f'''
        tell application "Tinderbox 10"
            tell {doc_specifier}
                set noteList to {{}}
                if "{container_path}" is "" then
                    set containerNotes to notes
                else
                    set containerNotes to notes of {container_spec}
                end if
                
                repeat with aNote in containerNotes
                    set noteName to value of attribute "Name" of aNote
                    set notePath to value of attribute "Path" of aNote
                    set noteID to value of attribute "ID" of aNote
                    set end of noteList to "{{\\"name\\": \\"" & noteName & "\\", \\"path\\": \\"" & notePath & "\\", \\"id\\": \\"" & noteID & "\\"}}"
                end repeat
                
                return "[" & (my listToString(noteList, ", ")) & "]"
            end tell
        end tell
        
        on listToString(lst, delim)
            set AppleScript's text item delimiters to delim
            set str to lst as string
            set AppleScript's text item delimiters to ""
            return str
        end listToString
        '''
        
        result = TinderboxHelper.run_applescript(script)
        return [{"type": "text", "text": result}]
        
    except Exception as e:
        return [{"type": "text", "text": f"Error: {str(e)}"}]

async def create_note(name: str, text: str = "", container_path: str = "",
                     attributes: Optional[Dict[str, Any]] = None, 
                     document: Optional[str] = None) -> List[Dict[str, Any]]:
    """Create a new note"""
    try:
        doc_specifier = f'document "{document}"' if document else 'front document'
        name_escaped = TinderboxHelper.escape_quotes(name)
        text_escaped = TinderboxHelper.escape_quotes(text)
        
        # Build attribute setting commands
        attr_commands = []
        if attributes:
            for key, value in attributes.items():
                value_escaped = TinderboxHelper.escape_quotes(str(value))
                attr_commands.append(f'set value of attribute "{key}" of newNote to "{value_escaped}"')
        
        attr_script = "\n".join(attr_commands) if attr_commands else ""
        
        if container_path:
            container_spec = f'note "{container_path}" of'
        else:
            container_spec = ""
        
        script = f'''
        tell application "Tinderbox 10"
            tell {doc_specifier}
                set newNote to make new note at {container_spec} it
                set value of attribute "Name" of newNote to "{name_escaped}"
                set value of attribute "Text" of newNote to "{text_escaped}"
                {attr_script}
                set noteID to value of attribute "ID" of newNote
                set notePath to value of attribute "Path" of newNote
                return "{{\\"id\\": \\"" & noteID & "\\", \\"path\\": \\"" & notePath & "\\"}}"
            end tell
        end tell
        '''
        
        result = TinderboxHelper.run_applescript(script)
        return [{"type": "text", "text": f"Created note: {result}"}]
        
    except Exception as e:
        return [{"type": "text", "text": f"Error: {str(e)}"}]

async def update_note(note_path: str, text: Optional[str] = None,
                     attributes: Optional[Dict[str, Any]] = None,
                     document: Optional[str] = None) -> List[Dict[str, Any]]:
    """Update an existing note"""
    try:
        doc_specifier = f'document "{document}"' if document else 'front document'
        
        updates = []
        if text is not None:
            text_escaped = TinderboxHelper.escape_quotes(text)
            updates.append(f'set value of attribute "Text" of theNote to "{text_escaped}"')
        
        if attributes:
            for key, value in attributes.items():
                value_escaped = TinderboxHelper.escape_quotes(str(value))
                updates.append(f'set value of attribute "{key}" of theNote to "{value_escaped}"')
        
        if not updates:
            return [{"type": "text", "text": "No updates specified"}]
        
        update_script = "\n".join(updates)
        
        script = f'''
        tell application "Tinderbox 10"
            tell {doc_specifier}
                set theNote to note "{note_path}"
                {update_script}
                return "Updated successfully"
            end tell
        end tell
        '''
        
        result = TinderboxHelper.run_applescript(script)
        return [{"type": "text", "text": result}]
        
    except Exception as e:
        return [{"type": "text", "text": f"Error: {str(e)}"}]

async def delete_note(note_path: str, document: Optional[str] = None) -> List[Dict[str, Any]]:
    """Delete a note"""
    try:
        doc_specifier = f'document "{document}"' if document else 'front document'
        
        script = f'''
        tell application "Tinderbox 10"
            tell {doc_specifier}
                set theNote to note "{note_path}"
                delete theNote
                return "Deleted successfully"
            end tell
        end tell
        '''
        
        result = TinderboxHelper.run_applescript(script)
        return [{"type": "text", "text": result}]
        
    except Exception as e:
        return [{"type": "text", "text": f"Error: {str(e)}"}]

async def create_link(from_note: str, to_note: str, 
                     link_type: Optional[str] = None,
                     document: Optional[str] = None) -> List[Dict[str, Any]]:
    """Create a link between notes"""
    try:
        doc_specifier = f'document "{document}"' if document else 'front document'
        link_type_spec = f' with type "{link_type}"' if link_type else ""
        
        script = f'''
        tell application "Tinderbox 10"
            tell {doc_specifier}
                set sourceNote to note "{from_note}"
                set destNote to note "{to_note}"
                make new link from sourceNote to destNote{link_type_spec}
                return "Link created successfully"
            end tell
        end tell
        '''
        
        result = TinderboxHelper.run_applescript(script)
        return [{"type": "text", "text": result}]
        
    except Exception as e:
        return [{"type": "text", "text": f"Error: {str(e)}"}]

async def run_agent(agent_path: str, document: Optional[str] = None) -> List[Dict[str, Any]]:
    """Execute a Tinderbox agent"""
    try:
        doc_specifier = f'document "{document}"' if document else 'front document'
        
        script = f'''
        tell application "Tinderbox 10"
            tell {doc_specifier}
                set theAgent to note "{agent_path}"
                update theAgent
                return "Agent executed successfully"
            end tell
        end tell
        '''
        
        result = TinderboxHelper.run_applescript(script)
        return [{"type": "text", "text": result}]
        
    except Exception as e:
        return [{"type": "text", "text": f"Error: {str(e)}"}]

# Main execution
async def main():
    """Run the MCP server"""
    print("Starting MCP server main loop...", file=sys.stderr)
    
    try:
        async with stdio_server() as (read_stream, write_stream):
            print("Server streams established", file=sys.stderr)
            options = server.create_initialization_options()
            print(f"Server info: {options}", file=sys.stderr)
            await server.run(read_stream, write_stream, options)
    except Exception as e:
        print(f"Server error: {e}", file=sys.stderr)
        import traceback
        traceback.print_exc(file=sys.stderr)
        raise

if __name__ == "__main__":
    try:
        print("Running async event loop...", file=sys.stderr)
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nServer stopped by user", file=sys.stderr)
    except Exception as e:
        print(f"Fatal error: {e}", file=sys.stderr)
        import traceback
        traceback.print_exc(file=sys.stderr)
        sys.exit(1)

Install

Tinderbox MCP Server Setup Guide (Updated)

This guide will help you set up the MCP (Model Context Protocol) server for Tinderbox 10, allowing Claude Desktop to read and write your Tinderbox notes directly.

What This Does

Once set up, you’ll be able to ask Claude to:

  • Search and read your Tinderbox notes
  • Create new notes with specific content
  • Update existing notes
  • Create links between notes
  • Run Tinderbox agents
  • Work with your Tinderbox data naturally in conversation

Prerequisites

  1. Tinderbox 10 installed on your Mac
  2. Claude Desktop app installed
  3. Python 3 (we’ll verify which version you have)
  4. Homebrew (recommended for easier Python management)

Step 1: Check Your Python Setup

macOS often has multiple Python installations. Let’s find out what you have:

  1. Open Terminal (press Cmd + Space, type “Terminal”, press Enter)
  2. Check your default Python:
which python3
python3 --version

Common locations:

  • /opt/homebrew/bin/python3 - Homebrew Python (recommended)
  • /usr/bin/python3 - System Python
  • /Library/Developer/CommandLineTools/usr/bin/python3 - Command Line Tools Python

Write down your Python path and version - you’ll need it later.

Step 2: Install MCP SDK

The MCP SDK needs to be installed for your specific Python version:

If you have Homebrew Python (recommended):

/opt/homebrew/bin/python3 -m pip install --user mcp

If you get “externally managed environment” error:

/opt/homebrew/bin/python3 -m pip install --user --break-system-packages mcp

Verify installation:

/opt/homebrew/bin/python3 -c "import mcp; print('MCP version:', mcp.__version__)"

You should see something like “MCP version: 1.9.1”

Step 3: Create the MCP Server Directory

  1. Create a directory for the MCP server:
mkdir -p ~/mcp-tinderbox
cd ~/mcp-tinderbox

Step 4: Save the Server Script

  1. Copy the entire Python code from the “Clean Tinderbox MCP Server” artifact
  2. Save it as tinderbox_mcp_server.py in your ~/mcp-tinderbox directory
  3. Make sure the first line (shebang) matches your Python path:
    4. If using Homebrew: #!/opt/homebrew/bin/python3
    5. If using system Python: #!/usr/bin/python3

Step 5: Test the Server

Before configuring Claude, test that the server runs:

cd ~/mcp-tinderbox
/opt/homebrew/bin/python3 tinderbox_mcp_server.py

You should see:

  • “Starting Tinderbox MCP Server…”
  • “MCP SDK imported successfully”
  • “Registering handlers with decorators…”
  • “Server streams established”

If it stays running without errors, press Ctrl+C to stop it.

Step 6: Configure Claude Desktop

  1. Find Claude’s configuration directory:
cd ~/Library/Application\ Support/Claude/
  1. Create or edit claude_desktop_config.json:
nano claude_desktop_config.json
  1. Add this configuration (adjust paths as needed):
{
  "mcpServers": {
    "tinderbox": {
      "command": "/opt/homebrew/bin/python3",
      "args": ["/Users/YOUR_USERNAME/mcp-tinderbox/tinderbox_mcp_server.py"]
    }
  }
}
  1. Replace YOUR_USERNAME with your actual username (find it with whoami)

If you already have other MCP servers:

Add Tinderbox to your existing configuration:

{
  "mcpServers": {
    "existing-server": {
      "command": "...",
      "args": [...]
    },
    "tinderbox": {
      "command": "/opt/homebrew/bin/python3",
      "args": ["/Users/YOUR_USERNAME/mcp-tinderbox/tinderbox_mcp_server.py"]
    }
  }
}

Important: Add a comma after the previous server’s closing brace!

Step 7: Grant Permissions

The first time you use the MCP server, macOS will ask for permissions:

  1. You’ll see “Terminal wants to control Tinderbox” → Click OK
  2. If it doesn’t work:
    3. Go to System Settings → Privacy & Security → Accessibility
    4. Click the lock to make changes
    5. Add Terminal (or Python) and check the box
    6. Also check Automation settings for Tinderbox

Step 8: Start Using It!

  1. Make sure Tinderbox 10 is running with a document open
  2. Completely quit Claude Desktop (Cmd+Q)
  3. Restart Claude Desktop
  4. Check the tools icon - you should see “tinderbox-mcp” with 8 available tools

Test Commands

Try these in Claude:

  • “List all notes in my Tinderbox document”
  • “Create a note called ‘Test Note’ with the text ‘Hello from Claude’”
  • “Search for notes containing the word ‘project’”
  • “Get the content of note ‘Test Note’”

Daily Usage

The MCP server connects to your frontmost Tinderbox document. Make sure:

  1. Tinderbox is running
  2. The document you want to work with is in front
  3. Claude Desktop is restarted after any config changes

Troubleshooting Appendix: Journey from v1 to Working

Issues Encountered and Solutions

1. Python Environment Confusion

Problem: Multiple Python installations causing import errors

  • Homebrew Python at /opt/homebrew/bin/python3
  • Command Line Tools Python at /Library/Developer/CommandLineTools/usr/bin/python3
  • System Python at /usr/bin/python3

Solution:

  • Always use full Python paths in configurations
  • Install MCP for the specific Python you’ll use
  • Homebrew Python is recommended for easier package management

2. MCP Installation Issues

Problem: “externally managed environment” error with pip

error: externally-managed-environment
× This environment is externally managed

Solution: Use the --user flag and if needed, --break-system-packages:

python3 -m pip install --user --break-system-packages mcp

3. Path and Directory Issues

Problem: Initial guide assumed Desktop location, but better practice is home directory

  • Wrong: /Users/username/Desktop/TinderboxMCP/
  • Better: /Users/username/mcp-tinderbox/

Solution: Use consistent paths throughout setup and config

4. MCP API Version Incompatibility

Problem: Server showed as “disabled” in Claude due to API mismatches

Evolution of fixes:

  1. First attempt: Used incorrect decorator syntax
  2. Second attempt: Tried manual handler registration
  3. Final solution: Used proper @server.list_tools() and @server.call_tool() decorators

Key discovery: MCP 1.9.1 uses decorator-based registration:

@server.list_tools()
async def handle_list_tools():
    return TOOLS_LIST  # Return list directly, not wrapped in dict

5. Import Errors

Problem: NameError: name 'types' is not defined

  • Initial code tried to import from mcp.types import Tool, TextContent
  • These types don’t exist in MCP 1.9.1

Solution:

  • Remove all type imports
  • Use plain dictionaries for return values
  • Change return type hints from List[types.TextContent] to List[Dict[str, Any]]

6. Server Lifecycle Issues

Problem: Server exiting immediately after start

  • Launcher creation logic caused early exit

Solution: Remove conditional launcher creation, always run server:

if __name__ == "__main__":
    server = TinderboxMCP()
    asyncio.run(server.run())

7. JSON Configuration Syntax

Problem: Claude Desktop is strict about JSON formatting

  • Tabs vs spaces matter
  • Trailing commas cause failures
  • Missing commas between servers break parsing

Solution: Always validate JSON and use consistent spacing

Debug Commands That Helped

  1. Check Python and MCP:
which python3
python3 -c "import mcp; print(mcp.__version__)"
python3 -m pip show mcp
  1. Test AppleScript permissions:
osascript -e 'tell application "Tinderbox 10" to return name of front document'
  1. View Claude logs:
tail -f ~/Library/Logs/Claude/mcp*.log | grep tinderbox
  1. Test server directly:
cd ~/mcp-tinderbox
/opt/homebrew/bin/python3 tinderbox_mcp_server.py 2>&1

Key Lessons Learned

  1. Always check logs - Claude’s MCP logs show the real errors
  2. Python paths matter - Use full paths everywhere
  3. MCP versions matter - Different versions have different APIs
  4. Start simple - Test with minimal functionality first
  5. Permissions are crucial - Both Accessibility and Automation needed

Final Working Configuration

  • Python: Homebrew Python 3.13 at /opt/homebrew/bin/python3
  • MCP: Version 1.9.1 installed with --user flag
  • Directory: ~/mcp-tinderbox/
  • Handlers: Decorator-based registration
  • Returns: Plain dictionaries, no special types
3 Likes