peil-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- peil_mcp-0.1.0/.gitignore +3 -0
- peil_mcp-0.1.0/LICENSE +21 -0
- peil_mcp-0.1.0/PKG-INFO +288 -0
- peil_mcp-0.1.0/README.md +261 -0
- peil_mcp-0.1.0/pyproject.toml +48 -0
- peil_mcp-0.1.0/src/peil_mcp/__init__.py +3 -0
- peil_mcp-0.1.0/src/peil_mcp/__main__.py +3 -0
- peil_mcp-0.1.0/src/peil_mcp/api.py +158 -0
- peil_mcp-0.1.0/src/peil_mcp/server.py +553 -0
- peil_mcp-0.1.0/tests/test_tools.py +256 -0
peil_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peil
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
peil_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: peil-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for Peil — log hours, draft invoices and check where you stand from an AI assistant
|
|
5
|
+
Project-URL: Homepage, https://peil.app
|
|
6
|
+
Project-URL: Documentation, https://peil.app/docs/mcp
|
|
7
|
+
Author-email: Peil <studio@jeroenkortekaas.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai,claude,freelance,invoicing,mcp,peil,zzp
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: mcp>=1.2
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Peil MCP server
|
|
29
|
+
|
|
30
|
+
Connect Peil to Claude (or any MCP client): log hours, draft invoices from
|
|
31
|
+
unbilled hours, and check where you stand — from a prompt.
|
|
32
|
+
|
|
33
|
+
The server is a pure client of Peil's public API. It authenticates with a
|
|
34
|
+
**scoped API key** you create in Peil under **Settings → Developer** (Pro).
|
|
35
|
+
|
|
36
|
+
## Draft-by-default
|
|
37
|
+
|
|
38
|
+
`draft_invoice` only ever creates a **draft** — nothing is sent to your
|
|
39
|
+
clients. Sending is a separate tool (`send_invoice`) that also requires the
|
|
40
|
+
separate **invoices:send** permission on your key. A key without that
|
|
41
|
+
permission can never email anything on your behalf.
|
|
42
|
+
|
|
43
|
+
## Tools
|
|
44
|
+
|
|
45
|
+
**Reads** (`read`)
|
|
46
|
+
|
|
47
|
+
| Tool | What it does |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `list_clients` | List your clients |
|
|
50
|
+
| `get_client_details` | One client's details, incl. whether a default rate is set |
|
|
51
|
+
| `list_unbilled` | Unbilled hours per client for a period |
|
|
52
|
+
| `list_invoices` | List invoices, filterable by status / client |
|
|
53
|
+
| `orientation_snapshot` | Outstanding / overdue / drafts / YTD position |
|
|
54
|
+
| `get_reminder_copy` | Your custom reminder email copy + schedule |
|
|
55
|
+
|
|
56
|
+
**Hours** (`timesheet:write`)
|
|
57
|
+
|
|
58
|
+
| Tool | What it does |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `log_hours` | Add a timesheet entry (client default rate unless given) |
|
|
61
|
+
| `edit_hours` | Edit an entry (only the fields you pass change) |
|
|
62
|
+
| `delete_hours` | Delete an entry (blocked if on a sent/paid invoice) |
|
|
63
|
+
|
|
64
|
+
**Clients** (`clients:write`)
|
|
65
|
+
|
|
66
|
+
| Tool | What it does |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `create_client` / `update_client` / `delete_client` | Client CRUD (delete blocked if it has projects/sent invoices) |
|
|
69
|
+
|
|
70
|
+
**Invoices** (`invoices:write`)
|
|
71
|
+
|
|
72
|
+
| Tool | What it does |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `draft_invoice` | Draft an invoice from unbilled hours (summary / by_project / per_day) |
|
|
75
|
+
| `set_invoice_status` | Change status (e.g. mark paid) — does **not** email anyone |
|
|
76
|
+
| `update_invoice` | Edit safe fields (due date, payment date, notes) |
|
|
77
|
+
| `delete_invoice` / `archive_invoice` | Delete (paid ones blocked) / archive |
|
|
78
|
+
| `set_reminder_copy` | Write custom reminder email copy for one tone/language |
|
|
79
|
+
|
|
80
|
+
**Client-facing email** (`invoices:send` — irreversible, always confirm first)
|
|
81
|
+
|
|
82
|
+
| Tool | What it does |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `send_invoice` | Email an invoice to the client now |
|
|
85
|
+
| `schedule_send` | Schedule a draft to be emailed at a future time |
|
|
86
|
+
| `cancel_scheduled_send` | Cancel a scheduled send |
|
|
87
|
+
| `send_reminder` | Email a payment reminder for a sent/overdue invoice |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 1. Create your key
|
|
92
|
+
|
|
93
|
+
In Peil: **Settings → Developer** → create a key with the permissions you want.
|
|
94
|
+
Start with **read + Log hours + Draft invoices**; leave **Send invoices** off
|
|
95
|
+
unless you truly want an assistant emailing clients. You'll paste this key into
|
|
96
|
+
your assistant's config as `PEIL_API_KEY` below.
|
|
97
|
+
|
|
98
|
+
## 2. Install the server
|
|
99
|
+
|
|
100
|
+
**The easy way — no clone, no install** (once `peil-mcp` is published to PyPI):
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
uvx peil-mcp # runs the latest release on demand
|
|
104
|
+
# or, with pipx:
|
|
105
|
+
pipx run peil-mcp
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
→ Your launch command is `uvx peil-mcp` (or `pipx run peil-mcp`). Skip to
|
|
109
|
+
[step 3](#3-connect-your-assistant). Everything below is only needed if you're
|
|
110
|
+
running **from source** (e.g. before the first release, or to hack on it).
|
|
111
|
+
|
|
112
|
+
### From source
|
|
113
|
+
|
|
114
|
+
You need **Python 3.11 or newer** and a local copy of this `mcp-server/` folder.
|
|
115
|
+
Check your Python with `python3 --version`.
|
|
116
|
+
|
|
117
|
+
> **Which method?** Run `which uv pipx` first. If you already have `uv`, use A —
|
|
118
|
+
> it's the least work. If not, `pipx` (B) gives you a clean global command. If
|
|
119
|
+
> you have neither and don't want to install tooling, the plain-`venv` path (C)
|
|
120
|
+
> works with nothing but the Python that's already on your machine.
|
|
121
|
+
|
|
122
|
+
Everywhere below, replace `/ABS/PATH/TO/mcp-server` with the real absolute path
|
|
123
|
+
to this folder (run `pwd` inside it to get it).
|
|
124
|
+
|
|
125
|
+
### A. With `uv` (no install step)
|
|
126
|
+
|
|
127
|
+
`uv` builds and runs on demand — nothing to install first:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
uv run --directory /ABS/PATH/TO/mcp-server peil-mcp
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
→ Your launch command is: `uv run --directory /ABS/PATH/TO/mcp-server peil-mcp`
|
|
134
|
+
|
|
135
|
+
Don't have `uv`? `curl -LsSf https://astral.sh/uv/install.sh | sh` (macOS/Linux)
|
|
136
|
+
or `pip install uv`.
|
|
137
|
+
|
|
138
|
+
### B. With `pipx` (isolated global command)
|
|
139
|
+
|
|
140
|
+
`pipx` installs the server into its own isolated environment and puts a
|
|
141
|
+
`peil-mcp` command on your PATH:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
pipx install /ABS/PATH/TO/mcp-server
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
→ Your launch command is simply: `peil-mcp`
|
|
148
|
+
|
|
149
|
+
Don't have `pipx`? `python3 -m pip install --user pipx && python3 -m pipx ensurepath`.
|
|
150
|
+
|
|
151
|
+
### C. Plain `venv` + `pip` (works with only stock Python)
|
|
152
|
+
|
|
153
|
+
No extra tooling — just the `python3` you already have:
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
cd /ABS/PATH/TO/mcp-server
|
|
157
|
+
python3 -m venv .venv
|
|
158
|
+
.venv/bin/pip install .
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
→ Your launch command is: `/ABS/PATH/TO/mcp-server/.venv/bin/peil-mcp`
|
|
162
|
+
(equivalently `/ABS/PATH/TO/mcp-server/.venv/bin/python -m peil_mcp`).
|
|
163
|
+
|
|
164
|
+
> Use a **plain** `pip install .` (not `-e`/editable) for running. An editable
|
|
165
|
+
> install relies on a `.pth` path hook that can silently fail to load on some
|
|
166
|
+
> setups, giving `ModuleNotFoundError: No module named 'peil_mcp'`. Editable is
|
|
167
|
+
> only needed if you're modifying the server itself — see
|
|
168
|
+
> [Local development](#local-development).
|
|
169
|
+
|
|
170
|
+
## 3. Connect your assistant
|
|
171
|
+
|
|
172
|
+
**The only thing that changes between assistants is where the config lives.**
|
|
173
|
+
Every MCP client needs the same three things:
|
|
174
|
+
|
|
175
|
+
- **command** — your launch command from step 2
|
|
176
|
+
- **env** — `PEIL_API_KEY` set to the key from step 1
|
|
177
|
+
- (optional) **`PEIL_API_URL`** — only if you're pointing at a non-production
|
|
178
|
+
Peil (see [Local development](#local-development)); defaults to
|
|
179
|
+
`https://api.peil.app/api/v1`.
|
|
180
|
+
|
|
181
|
+
The canonical config block (used by Claude Desktop, Cursor, Windsurf, Cline, and
|
|
182
|
+
most others) looks like this — `command` + `args` are just your launch command
|
|
183
|
+
split on spaces:
|
|
184
|
+
|
|
185
|
+
```jsonc
|
|
186
|
+
{
|
|
187
|
+
"mcpServers": {
|
|
188
|
+
"peil": {
|
|
189
|
+
"command": "peil-mcp", // or "uv", or the venv's python path
|
|
190
|
+
"args": [], // e.g. ["run","--directory","/ABS/PATH/TO/mcp-server","peil-mcp"] for uv
|
|
191
|
+
"env": { "PEIL_API_KEY": "your-key" }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Claude Desktop
|
|
198
|
+
|
|
199
|
+
Edit `claude_desktop_config.json`
|
|
200
|
+
(macOS: `~/Library/Application Support/Claude/`,
|
|
201
|
+
Windows: `%APPDATA%\Claude\`), add the block above, and restart Claude Desktop.
|
|
202
|
+
|
|
203
|
+
### Claude Code (CLI)
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
# pipx / venv (single-command launcher):
|
|
207
|
+
claude mcp add peil -e PEIL_API_KEY=your-key -- peil-mcp
|
|
208
|
+
|
|
209
|
+
# uv:
|
|
210
|
+
claude mcp add peil -e PEIL_API_KEY=your-key -- uv run --directory /ABS/PATH/TO/mcp-server peil-mcp
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Anything after `--` is the launch command. Verify with `claude mcp get peil`
|
|
214
|
+
(look for `Status: ✔ Connected`) and use `/mcp` in a session to reconnect.
|
|
215
|
+
|
|
216
|
+
### Cursor
|
|
217
|
+
|
|
218
|
+
Add the canonical block to `.cursor/mcp.json` (this project) or
|
|
219
|
+
`~/.cursor/mcp.json` (all projects), then enable **peil** in
|
|
220
|
+
**Settings → MCP**.
|
|
221
|
+
|
|
222
|
+
### Windsurf
|
|
223
|
+
|
|
224
|
+
Add the canonical block to `~/.codeium/windsurf/mcp_config.json`, then hit
|
|
225
|
+
**Refresh** in the Cascade MCP panel.
|
|
226
|
+
|
|
227
|
+
### Cline / Roo (VS Code)
|
|
228
|
+
|
|
229
|
+
Open the extension's **MCP Servers → Configure** panel and add the canonical
|
|
230
|
+
block to `cline_mcp_settings.json`.
|
|
231
|
+
|
|
232
|
+
### VS Code (native Copilot agent mode)
|
|
233
|
+
|
|
234
|
+
VS Code uses a slightly different shape — `servers` (not `mcpServers`) and an
|
|
235
|
+
explicit `type` — in `.vscode/mcp.json`:
|
|
236
|
+
|
|
237
|
+
```jsonc
|
|
238
|
+
{
|
|
239
|
+
"servers": {
|
|
240
|
+
"peil": {
|
|
241
|
+
"type": "stdio",
|
|
242
|
+
"command": "peil-mcp",
|
|
243
|
+
"args": [],
|
|
244
|
+
"env": { "PEIL_API_KEY": "your-key" }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Any other MCP client
|
|
251
|
+
|
|
252
|
+
Give it the same **command + args + `PEIL_API_KEY` env**. The server speaks MCP
|
|
253
|
+
over stdio; if a client can launch a stdio command, it can run Peil.
|
|
254
|
+
|
|
255
|
+
## First prompts
|
|
256
|
+
|
|
257
|
+
Once connected, try:
|
|
258
|
+
|
|
259
|
+
- *"Where do I stand?"* → `orientation_snapshot`
|
|
260
|
+
- *"Log 6 hours to De Correspondent today for editing work."* → `log_hours`
|
|
261
|
+
- *"Draft an invoice from my unbilled hours for De Correspondent."* → `draft_invoice`
|
|
262
|
+
|
|
263
|
+
With **Send invoices** left off your key, an assistant can prepare everything
|
|
264
|
+
but physically cannot email a client — you send from Peil yourself.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Local development
|
|
269
|
+
|
|
270
|
+
Point the server at a local backend with `PEIL_API_URL`:
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
PEIL_API_URL=http://localhost:8000/api/v1 PEIL_API_KEY=your-local-key peil-mcp
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
If you're modifying the server, an editable install picks up your changes
|
|
277
|
+
without reinstalling:
|
|
278
|
+
|
|
279
|
+
```sh
|
|
280
|
+
.venv/bin/pip install -e ".[dev]"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Tests (mocked HTTP, no backend needed — `pythonpath = ["src"]` in
|
|
284
|
+
`pyproject.toml` makes them independent of the install mechanism):
|
|
285
|
+
|
|
286
|
+
```sh
|
|
287
|
+
.venv/bin/pytest
|
|
288
|
+
```
|
peil_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Peil MCP server
|
|
2
|
+
|
|
3
|
+
Connect Peil to Claude (or any MCP client): log hours, draft invoices from
|
|
4
|
+
unbilled hours, and check where you stand — from a prompt.
|
|
5
|
+
|
|
6
|
+
The server is a pure client of Peil's public API. It authenticates with a
|
|
7
|
+
**scoped API key** you create in Peil under **Settings → Developer** (Pro).
|
|
8
|
+
|
|
9
|
+
## Draft-by-default
|
|
10
|
+
|
|
11
|
+
`draft_invoice` only ever creates a **draft** — nothing is sent to your
|
|
12
|
+
clients. Sending is a separate tool (`send_invoice`) that also requires the
|
|
13
|
+
separate **invoices:send** permission on your key. A key without that
|
|
14
|
+
permission can never email anything on your behalf.
|
|
15
|
+
|
|
16
|
+
## Tools
|
|
17
|
+
|
|
18
|
+
**Reads** (`read`)
|
|
19
|
+
|
|
20
|
+
| Tool | What it does |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `list_clients` | List your clients |
|
|
23
|
+
| `get_client_details` | One client's details, incl. whether a default rate is set |
|
|
24
|
+
| `list_unbilled` | Unbilled hours per client for a period |
|
|
25
|
+
| `list_invoices` | List invoices, filterable by status / client |
|
|
26
|
+
| `orientation_snapshot` | Outstanding / overdue / drafts / YTD position |
|
|
27
|
+
| `get_reminder_copy` | Your custom reminder email copy + schedule |
|
|
28
|
+
|
|
29
|
+
**Hours** (`timesheet:write`)
|
|
30
|
+
|
|
31
|
+
| Tool | What it does |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `log_hours` | Add a timesheet entry (client default rate unless given) |
|
|
34
|
+
| `edit_hours` | Edit an entry (only the fields you pass change) |
|
|
35
|
+
| `delete_hours` | Delete an entry (blocked if on a sent/paid invoice) |
|
|
36
|
+
|
|
37
|
+
**Clients** (`clients:write`)
|
|
38
|
+
|
|
39
|
+
| Tool | What it does |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `create_client` / `update_client` / `delete_client` | Client CRUD (delete blocked if it has projects/sent invoices) |
|
|
42
|
+
|
|
43
|
+
**Invoices** (`invoices:write`)
|
|
44
|
+
|
|
45
|
+
| Tool | What it does |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `draft_invoice` | Draft an invoice from unbilled hours (summary / by_project / per_day) |
|
|
48
|
+
| `set_invoice_status` | Change status (e.g. mark paid) — does **not** email anyone |
|
|
49
|
+
| `update_invoice` | Edit safe fields (due date, payment date, notes) |
|
|
50
|
+
| `delete_invoice` / `archive_invoice` | Delete (paid ones blocked) / archive |
|
|
51
|
+
| `set_reminder_copy` | Write custom reminder email copy for one tone/language |
|
|
52
|
+
|
|
53
|
+
**Client-facing email** (`invoices:send` — irreversible, always confirm first)
|
|
54
|
+
|
|
55
|
+
| Tool | What it does |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `send_invoice` | Email an invoice to the client now |
|
|
58
|
+
| `schedule_send` | Schedule a draft to be emailed at a future time |
|
|
59
|
+
| `cancel_scheduled_send` | Cancel a scheduled send |
|
|
60
|
+
| `send_reminder` | Email a payment reminder for a sent/overdue invoice |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 1. Create your key
|
|
65
|
+
|
|
66
|
+
In Peil: **Settings → Developer** → create a key with the permissions you want.
|
|
67
|
+
Start with **read + Log hours + Draft invoices**; leave **Send invoices** off
|
|
68
|
+
unless you truly want an assistant emailing clients. You'll paste this key into
|
|
69
|
+
your assistant's config as `PEIL_API_KEY` below.
|
|
70
|
+
|
|
71
|
+
## 2. Install the server
|
|
72
|
+
|
|
73
|
+
**The easy way — no clone, no install** (once `peil-mcp` is published to PyPI):
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
uvx peil-mcp # runs the latest release on demand
|
|
77
|
+
# or, with pipx:
|
|
78
|
+
pipx run peil-mcp
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
→ Your launch command is `uvx peil-mcp` (or `pipx run peil-mcp`). Skip to
|
|
82
|
+
[step 3](#3-connect-your-assistant). Everything below is only needed if you're
|
|
83
|
+
running **from source** (e.g. before the first release, or to hack on it).
|
|
84
|
+
|
|
85
|
+
### From source
|
|
86
|
+
|
|
87
|
+
You need **Python 3.11 or newer** and a local copy of this `mcp-server/` folder.
|
|
88
|
+
Check your Python with `python3 --version`.
|
|
89
|
+
|
|
90
|
+
> **Which method?** Run `which uv pipx` first. If you already have `uv`, use A —
|
|
91
|
+
> it's the least work. If not, `pipx` (B) gives you a clean global command. If
|
|
92
|
+
> you have neither and don't want to install tooling, the plain-`venv` path (C)
|
|
93
|
+
> works with nothing but the Python that's already on your machine.
|
|
94
|
+
|
|
95
|
+
Everywhere below, replace `/ABS/PATH/TO/mcp-server` with the real absolute path
|
|
96
|
+
to this folder (run `pwd` inside it to get it).
|
|
97
|
+
|
|
98
|
+
### A. With `uv` (no install step)
|
|
99
|
+
|
|
100
|
+
`uv` builds and runs on demand — nothing to install first:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
uv run --directory /ABS/PATH/TO/mcp-server peil-mcp
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
→ Your launch command is: `uv run --directory /ABS/PATH/TO/mcp-server peil-mcp`
|
|
107
|
+
|
|
108
|
+
Don't have `uv`? `curl -LsSf https://astral.sh/uv/install.sh | sh` (macOS/Linux)
|
|
109
|
+
or `pip install uv`.
|
|
110
|
+
|
|
111
|
+
### B. With `pipx` (isolated global command)
|
|
112
|
+
|
|
113
|
+
`pipx` installs the server into its own isolated environment and puts a
|
|
114
|
+
`peil-mcp` command on your PATH:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
pipx install /ABS/PATH/TO/mcp-server
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
→ Your launch command is simply: `peil-mcp`
|
|
121
|
+
|
|
122
|
+
Don't have `pipx`? `python3 -m pip install --user pipx && python3 -m pipx ensurepath`.
|
|
123
|
+
|
|
124
|
+
### C. Plain `venv` + `pip` (works with only stock Python)
|
|
125
|
+
|
|
126
|
+
No extra tooling — just the `python3` you already have:
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
cd /ABS/PATH/TO/mcp-server
|
|
130
|
+
python3 -m venv .venv
|
|
131
|
+
.venv/bin/pip install .
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
→ Your launch command is: `/ABS/PATH/TO/mcp-server/.venv/bin/peil-mcp`
|
|
135
|
+
(equivalently `/ABS/PATH/TO/mcp-server/.venv/bin/python -m peil_mcp`).
|
|
136
|
+
|
|
137
|
+
> Use a **plain** `pip install .` (not `-e`/editable) for running. An editable
|
|
138
|
+
> install relies on a `.pth` path hook that can silently fail to load on some
|
|
139
|
+
> setups, giving `ModuleNotFoundError: No module named 'peil_mcp'`. Editable is
|
|
140
|
+
> only needed if you're modifying the server itself — see
|
|
141
|
+
> [Local development](#local-development).
|
|
142
|
+
|
|
143
|
+
## 3. Connect your assistant
|
|
144
|
+
|
|
145
|
+
**The only thing that changes between assistants is where the config lives.**
|
|
146
|
+
Every MCP client needs the same three things:
|
|
147
|
+
|
|
148
|
+
- **command** — your launch command from step 2
|
|
149
|
+
- **env** — `PEIL_API_KEY` set to the key from step 1
|
|
150
|
+
- (optional) **`PEIL_API_URL`** — only if you're pointing at a non-production
|
|
151
|
+
Peil (see [Local development](#local-development)); defaults to
|
|
152
|
+
`https://api.peil.app/api/v1`.
|
|
153
|
+
|
|
154
|
+
The canonical config block (used by Claude Desktop, Cursor, Windsurf, Cline, and
|
|
155
|
+
most others) looks like this — `command` + `args` are just your launch command
|
|
156
|
+
split on spaces:
|
|
157
|
+
|
|
158
|
+
```jsonc
|
|
159
|
+
{
|
|
160
|
+
"mcpServers": {
|
|
161
|
+
"peil": {
|
|
162
|
+
"command": "peil-mcp", // or "uv", or the venv's python path
|
|
163
|
+
"args": [], // e.g. ["run","--directory","/ABS/PATH/TO/mcp-server","peil-mcp"] for uv
|
|
164
|
+
"env": { "PEIL_API_KEY": "your-key" }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Claude Desktop
|
|
171
|
+
|
|
172
|
+
Edit `claude_desktop_config.json`
|
|
173
|
+
(macOS: `~/Library/Application Support/Claude/`,
|
|
174
|
+
Windows: `%APPDATA%\Claude\`), add the block above, and restart Claude Desktop.
|
|
175
|
+
|
|
176
|
+
### Claude Code (CLI)
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
# pipx / venv (single-command launcher):
|
|
180
|
+
claude mcp add peil -e PEIL_API_KEY=your-key -- peil-mcp
|
|
181
|
+
|
|
182
|
+
# uv:
|
|
183
|
+
claude mcp add peil -e PEIL_API_KEY=your-key -- uv run --directory /ABS/PATH/TO/mcp-server peil-mcp
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Anything after `--` is the launch command. Verify with `claude mcp get peil`
|
|
187
|
+
(look for `Status: ✔ Connected`) and use `/mcp` in a session to reconnect.
|
|
188
|
+
|
|
189
|
+
### Cursor
|
|
190
|
+
|
|
191
|
+
Add the canonical block to `.cursor/mcp.json` (this project) or
|
|
192
|
+
`~/.cursor/mcp.json` (all projects), then enable **peil** in
|
|
193
|
+
**Settings → MCP**.
|
|
194
|
+
|
|
195
|
+
### Windsurf
|
|
196
|
+
|
|
197
|
+
Add the canonical block to `~/.codeium/windsurf/mcp_config.json`, then hit
|
|
198
|
+
**Refresh** in the Cascade MCP panel.
|
|
199
|
+
|
|
200
|
+
### Cline / Roo (VS Code)
|
|
201
|
+
|
|
202
|
+
Open the extension's **MCP Servers → Configure** panel and add the canonical
|
|
203
|
+
block to `cline_mcp_settings.json`.
|
|
204
|
+
|
|
205
|
+
### VS Code (native Copilot agent mode)
|
|
206
|
+
|
|
207
|
+
VS Code uses a slightly different shape — `servers` (not `mcpServers`) and an
|
|
208
|
+
explicit `type` — in `.vscode/mcp.json`:
|
|
209
|
+
|
|
210
|
+
```jsonc
|
|
211
|
+
{
|
|
212
|
+
"servers": {
|
|
213
|
+
"peil": {
|
|
214
|
+
"type": "stdio",
|
|
215
|
+
"command": "peil-mcp",
|
|
216
|
+
"args": [],
|
|
217
|
+
"env": { "PEIL_API_KEY": "your-key" }
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Any other MCP client
|
|
224
|
+
|
|
225
|
+
Give it the same **command + args + `PEIL_API_KEY` env**. The server speaks MCP
|
|
226
|
+
over stdio; if a client can launch a stdio command, it can run Peil.
|
|
227
|
+
|
|
228
|
+
## First prompts
|
|
229
|
+
|
|
230
|
+
Once connected, try:
|
|
231
|
+
|
|
232
|
+
- *"Where do I stand?"* → `orientation_snapshot`
|
|
233
|
+
- *"Log 6 hours to De Correspondent today for editing work."* → `log_hours`
|
|
234
|
+
- *"Draft an invoice from my unbilled hours for De Correspondent."* → `draft_invoice`
|
|
235
|
+
|
|
236
|
+
With **Send invoices** left off your key, an assistant can prepare everything
|
|
237
|
+
but physically cannot email a client — you send from Peil yourself.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Local development
|
|
242
|
+
|
|
243
|
+
Point the server at a local backend with `PEIL_API_URL`:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
PEIL_API_URL=http://localhost:8000/api/v1 PEIL_API_KEY=your-local-key peil-mcp
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
If you're modifying the server, an editable install picks up your changes
|
|
250
|
+
without reinstalling:
|
|
251
|
+
|
|
252
|
+
```sh
|
|
253
|
+
.venv/bin/pip install -e ".[dev]"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Tests (mocked HTTP, no backend needed — `pythonpath = ["src"]` in
|
|
257
|
+
`pyproject.toml` makes them independent of the install mechanism):
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
.venv/bin/pytest
|
|
261
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "peil-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for Peil — log hours, draft invoices and check where you stand from an AI assistant"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "Peil", email = "studio@jeroenkortekaas.com" }]
|
|
9
|
+
keywords = ["mcp", "peil", "invoicing", "zzp", "freelance", "claude", "ai"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Office/Business :: Financial",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"mcp>=1.2",
|
|
23
|
+
"httpx>=0.27",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://peil.app"
|
|
28
|
+
Documentation = "https://peil.app/docs/mcp"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
peil-mcp = "peil_mcp.server:main"
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=8",
|
|
36
|
+
"respx>=0.21",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["hatchling"]
|
|
41
|
+
build-backend = "hatchling.build"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/peil_mcp"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
# Make tests independent of the editable-install mechanism (src layout).
|
|
48
|
+
pythonpath = ["src"]
|