WorkPulse MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@WorkPulse MCP ServerLog a $45.20 taxi expense and attach the receipt."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
WorkPulse MCP Server
A Model Context Protocol server that lets Claude (Desktop, Code, or any MCP client) enter and query expenses, invoices, and contracts in a WorkPulse instance through its REST API.
14 tools across expenses, invoices, contracts, lookups, batches, and OCR document processing
Batch rollback — group bulk inserts and undo them in one call
Two transports —
stdiofor local desktop use (default), or Streamable HTTP protected by OAuth bearer tokens for network deploymentCredentials stay server-side — WorkPulse username/password live only in the server's environment; the model and MCP client never see them
Optional OCR pipeline — turn bank statements, receipts and bills (PDF/images) into expenses via a DriftMoney DocTR + Ollama pipeline
Claude / MCP client ⇄ WorkPulse MCP server ⇄ WorkPulse REST API
⇣ (optional)
OCR pipelineContents
Related MCP server: life-agent-mcp
Quick start
Requirements: Python 3.10+ and a running WorkPulse server.
git clone https://github.com/aliasdhacker/workpulse-mcp.git
cd workpulse-mcp
python -m venv .venv
# Windows: .venv\Scripts\activate macOS/Linux: source .venv/bin/activate
pip install -e .Set the WorkPulse credentials (these are read by the server process only):
export WORKPULSE_API_URL=http://localhost:8080
export WORKPULSE_USERNAME=<your-username>
export WORKPULSE_PASSWORD=<your-password>Run it:
workpulse-mcp # stdio, for Claude Desktop / Claude Code
# or
python -m workpulse_mcp.serverThen add it to your client — see Client configuration. Copy .env.example to .env if you prefer a file; it is git-ignored.
Architecture
┌────────────────────────────────┐
│ Claude Desktop / Claude Code / │
│ any MCP client │
└──────────────┬─────────────────┘
│ MCP JSON-RPC
│ stdio ─or─ Streamable HTTP (+ OAuth bearer token)
▼
┌────────────────────────────────────────────────────────────┐
│ WorkPulse MCP server (Python, mcp SDK 2.x) │
│ │
│ http_auth.py bearer-token verifier for HTTP transports │
│ server.py MCPServer + lifespan (logs in to WorkPulse)│
│ auth.py WorkPulse JWT login / refresh / re-login │
│ client.py httpx client w/ bearer + retry/backoff │
│ batch.py batch tracker → ~/.workpulse-mcp/batches.json
│ tools/ expenses · invoices · contracts · lookups │
│ batches · receipts │
└──────────────┬──────────────────────────┬──────────────────┘
│ HTTPS + WorkPulse JWT │ HTTP (optional)
▼ ▼
┌──────────────────────────┐ ┌────────────────────────────┐
│ WorkPulse REST API │ │ DriftMoney OCR pipeline │
│ /api/expenses … │ │ POST /parse │
│ /api/auth/login|refresh │ │ (DocTR + Ollama) │
└──────────────────────────┘ └────────────────────────────┘Every tool call follows the same path: the tool calls auth.ensure_authenticated() (refreshing or re-logging-in as needed), then makes one or more REST calls through client.py, and — for create_* tools — records the new entity's ID in the active batch so it can be rolled back.
Credential model
There are two independent credential domains, and they never cross.
Domain | Who holds it | How it is used |
WorkPulse login ( | The MCP server process only, via environment variables | Exchanged at startup for a WorkPulse JWT ( |
MCP endpoint access (OAuth bearer token) | The MCP client (Claude) | Only for the HTTP transports. Presented to this server, which validates it and, if valid, serves the request using its own WorkPulse session. |
Consequences:
The model never sees, and cannot request, the WorkPulse password or JWT. No tool exposes them; they are not in any tool output.
Anyone who can reach the HTTP endpoint with a valid bearer token acts as the configured WorkPulse user. Scope your WorkPulse account accordingly.
In stdio mode there is no network endpoint; the client launches the server as a child process and passes the env vars in its config (see below). Keep that config file private.
Transports
Select with MCP_TRANSPORT. stdio is the default.
stdio (default)
MCP_TRANSPORT=stdio workpulse-mcpStandard for Claude Desktop and Claude Code. All logging goes to stderr; stdout is the JSON-RPC channel.
Streamable HTTP + OAuth
MCP_TRANSPORT=streamable-http MCP_HOST=0.0.0.0 MCP_PORT=8888 workpulse-mcp
# endpoint: http://<host>:8888/mcpThe HTTP endpoint is always protected; the server refuses to start unless an auth mode is configured (or MCP_AUTH_MODE=none is set explicitly). Auth is implemented with the mcp SDK's built-in resource-server support: a TokenVerifier is passed to MCPServer(token_verifier=…, auth=AuthSettings(…)), and the SDK's BearerAuthBackend / RequireAuthMiddleware wrap the /mcp route and publish RFC 9728 protected-resource metadata at /.well-known/oauth-protected-resource/mcp, per the MCP authorization spec.
Unauthenticated requests get 401 with a WWW-Authenticate: Bearer … resource_metadata="…" header so spec-compliant clients can discover the authorization server; tokens missing a required scope get 403 insufficient_scope.
Three verifier modes (src/workpulse_mcp/http_auth.py):
Mode |
| When to use | Required env |
Static token |
| Local development, a single trusted client |
|
JWT (JWKS) |
| Production with an OAuth 2.1 / OIDC authorization server issuing signed JWT access tokens |
|
Introspection |
| Production with opaque tokens (RFC 7662) |
|
None |
| Isolated lab networks only | — |
Development (static token):
export MCP_TRANSPORT=streamable-http
export MCP_BEARER_TOKEN="$(python -c 'import secrets;print(secrets.token_urlsafe(32))')"
export WORKPULSE_USERNAME=<your-username> WORKPULSE_PASSWORD=<your-password>
workpulse-mcpClients send Authorization: Bearer <that token> (see examples/mcp.http.json.example).
Production (JWT):
export MCP_TRANSPORT=streamable-http
export MCP_HOST=0.0.0.0 MCP_PORT=8888
export MCP_RESOURCE_URL=https://mcp.example.com/mcp # public URL of this endpoint = expected `aud`
export MCP_OAUTH_ISSUER=https://auth.example.com # your authorization server
export MCP_OAUTH_REQUIRED_SCOPES=workpulse:write # optional
workpulse-mcpThe verifier discovers jwks_uri from the issuer's /.well-known/oauth-authorization-server (or /.well-known/openid-configuration), caches keys, and validates signature, iss, aud, and exp with PyJWT. Put TLS termination (nginx, Caddy, a cloud load balancer) in front of it; the server itself speaks plain HTTP.
Register https://mcp.example.com/mcp as a resource / audience in your authorization server and let clients obtain tokens for it through the normal OAuth 2.1 + PKCE flow. Because this server publishes protected-resource metadata, MCP clients that implement the authorization spec (Claude's remote-MCP connectors, MCP Inspector, etc.) find your authorization server automatically from the 401.
sse is also accepted for legacy clients and is protected identically.
Client configuration
Claude Code (.mcp.json in your project, or claude mcp add)
{
"mcpServers": {
"workpulse": {
"type": "stdio",
"command": "workpulse-mcp",
"env": {
"WORKPULSE_API_URL": "http://localhost:8080",
"WORKPULSE_USERNAME": "<your-username>",
"WORKPULSE_PASSWORD": "<your-password>",
"OCR_PIPELINE_URL": "http://localhost:8000"
}
}
}
}Remote server over Streamable HTTP:
{
"mcpServers": {
"workpulse": {
"type": "http",
"url": "http://localhost:8888/mcp",
"headers": { "Authorization": "Bearer <your-mcp-bearer-token>" }
}
}
}Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"workpulse": {
"command": "python",
"args": ["-m", "workpulse_mcp.server"],
"env": {
"WORKPULSE_API_URL": "http://localhost:8080",
"WORKPULSE_USERNAME": "<your-username>",
"WORKPULSE_PASSWORD": "<your-password>",
"OCR_PIPELINE_URL": "http://localhost:8000"
}
}
}
}If python on your PATH is not the interpreter where you installed the package, use the venv's interpreter (.../.venv/bin/python or ...\.venv\Scripts\python.exe) as command.
Ready-to-copy versions live in examples/.
Tools
All tools return JSON text. IDs referenced below come from the lookup tools.
Expenses
create_expense
Parameter | Type | Required | Description |
| string | yes | YYYY-MM-DD |
| float | yes | Positive dollar amount |
| string | yes | SUPPLIES, SOFTWARE, TRAVEL, EQUIPMENT, SUBCONTRACTOR, OTHER |
| string | no | Merchant name |
| string | no | What was purchased |
| string | no | Receipt number or file reference |
| int | no | Associate with a project |
| string | no | DEDUCTIBLE (default), PARTIALLY_DEDUCTIBLE, NON_DEDUCTIBLE |
| string | no | Batch to track this in (uses active batch if omitted) |
Create an expense for $127.50 at Office Depot on 2026-02-20 for supplies, tax deductible
list_expenses
Parameter | Type | Required | Description |
| string | no | Start date YYYY-MM-DD |
| string | no | End date YYYY-MM-DD |
| string | no | Filter by category |
Show me all travel expenses from January 2026
Invoices
create_invoice
Parameter | Type | Required | Description |
| int | yes | Client ID (use |
| string | yes | e.g. "INV-2026-003" |
| string | yes | YYYY-MM-DD |
| float | no | Subtotal before adjustments |
| float | no | Total after adjustments/discounts |
| string | no | DRAFT (default), SENT, PAID |
| string | no | Billing period start YYYY-MM-DD |
| string | no | Billing period end YYYY-MM-DD |
| float | no | Adjustment amount (can be negative) |
| string | no | Reason for adjustment |
| float | no | Fixed discount |
| float | no | Percentage discount (0-100) |
| string | no | Reason for discount |
| string | no | Payment due date YYYY-MM-DD |
| string | no | Additional notes |
| string | no | Batch for rollback tracking |
Create a draft invoice INV-2026-003 for client Acme Corp dated today, subtotal $3200, total $3200, for the period Feb 1-28
generate_invoice
Generates an invoice from billable time entries; the API pulls matching entries and creates line items.
Parameter | Type | Required | Description |
| int | yes | Client ID |
| string | yes | YYYY-MM-DD |
| string | yes | YYYY-MM-DD |
| string | no | Override invoice number |
| string | no | Override invoice date |
| float | no | Adjustment amount |
| string | no | Reason for adjustment |
| string | no | Additional notes |
| string | no | Batch for rollback tracking |
Generate an invoice for Acme Corp covering January 2026
list_invoices
Parameter | Type | Required | Description |
| int | no | Filter by client |
| string | no | DRAFT, SENT, PAID |
Show me all unpaid invoices for client 3
Contracts
create_contract
Parameter | Type | Required | Description |
| int | yes | Client ID |
| string | yes | Contract title |
| string | no | HOURLY (default), FIXED_PRICE, RETAINER |
| string | no | DRAFT (default), SENT, ACTIVE, COMPLETED, EXPIRED, TERMINATED |
| string | no | Contract description |
| string | no | Payment terms |
| string | no | Scope of work |
| float | no | Rate for HOURLY contracts |
| float | no | Estimated hours |
| float | no | Estimated total for FIXED_PRICE |
| string | no | YYYY-MM-DD |
| string | no | YYYY-MM-DD |
| string | no | Batch for rollback tracking |
Create a fixed price contract for Acme Corp titled "Website Redesign" for $15,000, starting March 1 through June 30
list_contracts
Parameter | Type | Required | Description |
| int | no | Filter by client |
| string | no | DRAFT, SENT, ACTIVE, COMPLETED, EXPIRED, TERMINATED |
Show me all active contracts
Lookups
list_clients
Parameter | Type | Required | Description |
| bool | no | Only active clients (default: true) |
List all my clients
list_projects
Parameter | Type | Required | Description |
| int | no | Filter by client |
| bool | no | Only active projects (default: true) |
What projects does Acme Corp have?
Batches
start_batch
Parameter | Type | Required | Description |
| string | yes | Descriptive name for the batch |
Start a batch called "February 2026 expenses"
list_batches
No parameters. Shows all batches with items, counts, active flag, and rollback status.
rollback_batch
Parameter | Type | Required | Description |
| string | yes | Batch ID from |
Roll back the February expenses batch
Receipts / OCR
process_document
Parameter | Type | Required | Description |
| string | yes | Path to the document (PDF, PNG, JPG, JPEG, GIF, BMP, TIFF, WEBP) |
| string | no | Batch for rollback tracking |
| string | no | DEDUCTIBLE (default), PARTIALLY_DEDUCTIBLE, NON_DEDUCTIBLE |
| bool | no | Extract only; do not create expenses |
Dry run on C:\receipts\receipt.png so I can review before creating
process_documents_folder
Parameter | Type | Required | Description |
| string | yes | Folder containing documents |
| string | no | Batch name (defaults to folder name) |
| string | no | DEDUCTIBLE (default) |
| bool | no | Extract only; do not create expenses |
Automatically starts a batch (unless dry_run).
Process all the documents in C:\statements\2026-Q1 and call the batch "Q1 2026 statements"
Batch rollback
Bulk data entry with an LLM needs an undo button. Batches provide it:
start_batch("week of Feb 24")— becomes the active batch.Every subsequent
create_expense/create_invoice/generate_invoice/create_contract(and OCR-created expense) records{type, id, endpoint}in the active batch, unless an explicitbatch_idis given.rollback_batch(batch_id)issuesDELETEon each recorded endpoint in reverse creation order, reports per-item success/failure, and marks the batch rolled back (it cannot be rolled back twice).
State is persisted to WORKPULSE_MCP_DATA_DIR/batches.json (default ~/.workpulse-mcp/) so batches survive server restarts. Rollback deletes data in WorkPulse — it is not reversible.
Example session:
Start a batch called "week of Feb 24". Then create these expenses:
Feb 24: $45.99 at Staples for supplies
Feb 25: $89.00 for Adobe Creative Cloud, software
Feb 26: $234.00 at Delta Airlines, travel
…
That last one was personal — roll back the "week of Feb 24" batch
OCR document processing
Optional. The process_document* tools send files to a DriftMoney OCR pipeline (POST /parse, DocTR + Ollama) and map its output to WorkPulse expenses:
PDF / image → DocTR OCR → Ollama LLM → DriftMoney DSL → WorkPulse expensesSet OCR_PIPELINE_URL (default http://localhost:8000). Income and credit transactions are filtered out; debt information from card/loan statements is returned as informational data but not inserted.
DriftMoney category | WorkPulse category |
Food, Groceries, Dining, Shopping, Retail, Office | SUPPLIES |
Software, Subscription, SaaS, Digital | SOFTWARE |
Travel, Transportation, Gas, Fuel, Uber, Lyft, Airline, Hotel | TRAVEL |
Equipment, Hardware, Electronics | EQUIPMENT |
Contractor, Subcontractor, Freelance | SUBCONTRACTOR |
Utilities, Insurance, Medical, Entertainment, and anything else | OTHER |
Use dry_run=true first and review before creating; folder processing always wraps creates in a batch.
Environment reference
Variable | Default | Purpose |
|
| WorkPulse REST base URL |
| — | Required. WorkPulse login |
| — | Required. WorkPulse password |
|
| Batch state directory |
|
| DriftMoney OCR pipeline |
|
|
|
|
| Bind address for HTTP transports |
|
| Streamable HTTP endpoint path |
|
| Stateless Streamable HTTP mode |
| auto |
|
| — | Pre-shared token (static mode) |
|
| Public URL of this endpoint; OAuth resource identifier |
| — | Authorization server issuer URL |
|
| Expected |
| discovered | Explicit JWKS URL |
| RS*/ES*/PS256 | Allowed JWT algorithms |
| — | RFC 7662 endpoint |
| — | Credentials for the introspection call |
| — | Scopes every token must carry |
See .env.example for an annotated template.
Development
pip install -e ".[dev]"
pytest # 45 tests: AuthManager, verifiers, SDK middleware, end-to-end
./run-inspector.sh # MCP Inspector against stdio (reads .env)
mcp dev src/workpulse_mcp/server.py # alternative Inspector launcherThe test suite needs no WorkPulse instance: REST calls are mocked with respx, and the end-to-end test drives the real server through the SDK's Streamable HTTP app in-process.
Layout:
src/workpulse_mcp/
server.py MCPServer construction, transport selection, lifespan
http_auth.py TokenVerifier implementations + build_auth_from_env()
auth.py WorkPulse JWT session (login / refresh / re-login)
client.py httpx wrapper with bearer header, retries, error mapping
batch.py BatchTracker (persistent, rollback in reverse order)
ocr_pipeline.py OCR client + DriftMoney → WorkPulse mapping
tools/ one module per tool group
tests/ pytest (+ pytest-asyncio, respx)
examples/ client config templatesRequires mcp>=2.0 (the 2.x SDK renamed FastMCP to MCPServer and moved host/port to run()).
License
MIT — see LICENSE. Copyright (c) 2026 Andrew Carr.
This server cannot be deployed
Maintenance
Related MCP Connectors
One MCP endpoint for Claude, GPT & Gemini: 100+ tools + no-code connectors + agent workers.
A paid remote MCP for CLI tool MCP, built to return verdicts, receipts, usage logs, and audit-ready
Build, validate, deploy — HTTP APIs, cron jobs, webhooks and MCP tools — from your AI client.
The Ramp MCP server enables users to securely connect Ramp with AI assistants like ChatGPT and Claude to query financial data and take actions using natural language. It transforms Ramp's developer API into a SQL interface that LLMs can query, allowing admins to analyze spend trends, identify cost savings, and run complex SQL analyses on comprehensive datasets (transactions, purchase orders, vendors, users), while all users can manage cards, view transactions, request reimbursements, and get expense policy answers.
Related MCP Servers
- FlicenseAqualityDmaintenanceMCP server for EZ@Work — manage clients, projects, time entries, and invoices from Claude through natural conversation. OAuth 2.1 with Google sign-in, multi-currency support.5-
- AlicenseNot gradedqualityCmaintenanceEnables turning any personal-assistant REST backend into Claude-ready tools via a single MCP server, providing 38 tools for communications, finance, health, and more.MIT
- FlicenseBqualityCmaintenanceA local, zero-dependency MCP server to author, lint, deploy, and debug Workato recipes from Claude. It wraps the Workato Developer REST API, RLCM package API, and Data Tables record API with 56 tools.56-
- FlicenseBqualityCmaintenanceA standalone MCP server that enables Claude Desktop to manage Clio legal practice matters, documents, billing, and more via ~46 tools, with secure OAuth and audit logging.46-