loading…
Search for a command to run...
loading…
An MCP server for the Paymo platform that enables AI assistants to manage time entries, projects, and tasks through natural language. It supports tracking unbil
An MCP server for the Paymo platform that enables AI assistants to manage time entries, projects, and tasks through natural language. It supports tracking unbilled revenue and generating detailed invoice timesheets for efficient project management.
A Model Context Protocol (MCP) server for Paymo time tracking and invoicing. Enables Claude Desktop to manage time entries, projects, tasks, and generate invoice timesheets.
git clone https://github.com/feamster/paymo-mcp.git
cd paymo-mcp
pip install -r requirements.txt
Configuration is split between non-sensitive settings and auth:
~/.mcp-config/paymo/config.json (non-sensitive, can be in dotfiles):
{
"timezone": "America/Chicago",
"projects": {
"Client Matter Name": {
"project_id": 12345,
"task_id": 67890
}
}
}
~/.mcp-auth/paymo/auth.json (sensitive, sync separately):
{
"api_key": "your-paymo-api-key-here"
}
# List projects
python3 paymo_timesheet.py list-projects
# List tasks for a project
python3 paymo_timesheet.py list-tasks --project-id 12345
# Create a single entry
python3 paymo_timesheet.py create-entry \
--task-id 67890 \
--date 2025-12-10 \
--hours 3.5 \
--description "Document review and analysis"
# Export invoice timesheet by invoice number
python3 paymo_timesheet.py export-invoice-timesheets \
--invoice-number INV-20260331-241 \
--output-dir ./invoices
# List unbilled entries
python3 paymo_timesheet.py list-entries \
--start 2025-11-01 \
--end 2025-11-30 \
--unbilled
python3 paymo_timesheet.py mcp
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"paymo": {
"command": "python3",
"args": ["/path/to/paymo-mcp/paymo_timesheet.py", "mcp"]
}
}
}
The Paymo tools will now be available in Claude Desktop.
list_paymo_projects()List all active Paymo projects.
Returns: List of projects with IDs, names, and client information.
list_paymo_tasks(project_id: int)List all tasks for a specific project.
Args:
project_id: The Paymo project IDReturns: List of tasks with IDs, names, and billing information.
create_paymo_entry(task_id, date, duration_hours, description)Create a single time entry.
Args:
task_id (int): Task ID to log time againstdate (str): Date in YYYY-MM-DD formatduration_hours (float): Hours worked (e.g., 3.5)description (str): Description of work performedReturns: Created entry details.
Example:
create_paymo_entry(
task_id=31450618,
date="2025-12-10",
duration_hours=6.0,
description="Expert report drafting and analysis"
)
submit_paymo_timesheet(yaml_content: str)Submit multiple entries from YAML format.
Args:
yaml_content: YAML string with timesheet entriesReturns: Summary of created entries.
Example YAML:
matter: "Patent Litigation Matter"
client: "Law Firm Client"
rate: 650
entries:
- date: "2025-12-02"
start_time: "09:00"
end_time: "12:30"
timezone: "America/Chicago"
task_id: 31450618
description: "Case strategy meeting"
- date: "2025-12-03"
duration_hours: 5.0
task_id: 31450740
description: "Expert witness report preparation"
list_paymo_entries(start_date, end_date, project_id=None, billed=None)List time entries with optional filters.
Args:
start_date (str): Start date (YYYY-MM-DD)end_date (str): End date (YYYY-MM-DD)project_id (int, optional): Filter by projectbilled (bool, optional): Filter by billing status (True=billed, False=unbilled, None=all)Returns: List of entries with task names, durations, descriptions, and billing status.
list_paymo_invoices(client_id=None, status=None)List Paymo invoices with optional filters.
Args:
client_id (int, optional): Filter by clientstatus (str, optional): Filter by status ("draft", "sent", "viewed", "paid")Returns: List of invoices with numbers, amounts, dates, and statuses.
get_outstanding_invoices_last_week()Get outstanding invoices from the last 7 days.
Returns: List of recent invoices with status "sent" or "viewed".
export_invoice_timesheet(invoice_number: str, strict: bool = True)Export a formatted, billing-ready timesheet CSV for a specific invoice. This is the primary tool for generating invoice timesheets.
Args:
invoice_number (str): The invoice number as shown on the invoice (e.g., "INV-20260331-241")strict (bool): If True (default), validate that calculated totals match invoice. If False, skip validation.Returns: Formatted CSV content with:
Features:
invoice_item_id, and validates that calculated fees match invoice totals within 5%Example:
export_invoice_timesheet("INV-20260331-241") # strict validation (default)
export_invoice_timesheet("INV-20260331-241", False) # skip validation
When to use:
If validation fails: Use export_paymo_timesheet(start_date, end_date, project_id) to export by date range instead.
export_invoice_paymo_format(invoice_number: str, strict: bool = True)Export timesheet in exact Paymo native format with all standard columns. Use this when you need the export to match Paymo's own export format exactly.
Args:
invoice_number (str): The invoice number (e.g., "INV-20260331-241")strict (bool): If True (default), validate totals match invoiceReturns: CSV with exact Paymo columns:
User, Internal User Id, Project, Internal Project Id, Project Description,
Tasklist, Internal Tasklist Id, Task, Internal Task Id, Start Time, End Time,
Worked Time, Decimal Hours, Time In Seconds
When to use:
Example:
export_invoice_paymo_format("INV-20260331-241")
export_paymo_timesheet(start_date, end_date, project_id=None, format="csv")Export timesheet for a date range.
Args:
start_date (str): Start date (YYYY-MM-DD)end_date (str): End date (YYYY-MM-DD)project_id (int, optional): Filter by projectformat (str): Export format ("csv" or "xls")Returns: Path to exported file.
When you run export_invoice_timesheet("INV-20260331-241"), you get a billing-ready CSV:
Matter,DivX vs. Netflix
Invoice,INV-20260331-241
Period,2026-03-04 to 2026-03-25
Total Hours,239.01
Fees,$143406.00
Expenses,$1016.95
Total Due,$144422.95
Date,Start Time,End Time,Duration (hours),Task,Description
2026-03-04,09:00,10:30,1.50,Trial Prep,Trial prep
2026-03-04,11:00,13:00,2.00,Trial Prep,"Post outline review session: incorporated feedback on outline flow"
2026-03-06,01:00,04:00,3.00,Trial Prep,"Solo trial prep: reviewing patent materials and invalidity case outline"
2026-03-06,16:00,17:30,1.50,Trial Prep,"Trial prep run-through with counsel re: patent technical benefits section"
Expenses,$1016.95
Key features:
When you ask "How much unbilled time do I have?", Claude might respond:
You have 47.5 unbilled hours across 3 projects:
Patent Litigation Matter: 30.75 hours ($19,987.50)
Corporate Advisory: 12.00 hours ($7,800.00)
Expert Witness Case: 4.75 hours ($3,087.50)
Total unbilled: $30,875.00
The MCP server enables Claude to automatically translate natural language to Paymo API calls:
You say: "Create a 6 hour entry for the litigation project on Dec 10"
Claude automatically:
list_paymo_projects() to find projectslist_paymo_tasks(project_id) to get taskscreate_paymo_entry()You say: "Which projects have unbilled time?"
Claude automatically:
list_paymo_projects() to get all projectslist_paymo_entries() with billed=FalseThe export_invoice_timesheet() function provides billing-ready timesheets with strict validation:
invoice_item_id)This ensures you get only the entries actually billed on that specific invoice, with validation that the totals match.
Alternative - Date Range Export:
If you need entries by date range regardless of invoice linkage (or if strict validation fails), use export_paymo_timesheet(start_date, end_date, project_id) instead.
The script automatically handles Paymo's API rate limits:
X-Ratelimit-Remaining headersCreate ~/.mcp-auth/paymo/auth.json with your API key (see Configuration section).
Install the MCP server dependency:
pip install fastmcp
The script will automatically wait and retry. If you see this frequently, reduce batch operation sizes.
Some invoices may not have time entries (flat fee or expense-only invoices). Verify the invoice includes time entries in Paymo.
paymo-mcp/
├── paymo_timesheet.py # Main script (CLI + MCP server)
├── requirements.txt # Python dependencies
└── README.md # This file
PaymoClient: API wrapper with rate limiting and retry logicTimesheetProcessor: YAML parsing and batch entry creation@mcp.tool() decoratorExample:
@mcp.tool()
def my_new_tool(arg1: str, arg2: int) -> Dict[str, Any]:
"""
Brief description of what this tool does
Args:
arg1: Description of first argument
arg2: Description of second argument
Returns:
Description of return value
"""
config = load_config()
api_key = config.get('api_key')
client = PaymoClient(api_key)
# Implementation here
return {"result": "data"}
MIT
Issues and pull requests welcome! Please ensure:
Built with FastMCP for Model Context Protocol support.
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"paymo-mcp-server": {
"command": "npx",
"args": []
}
}
}