ourpr-mcp-server 0.3.0 → 0.4.1
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.
- package/README.md +149 -13
- package/build/api.js +32 -0
- package/build/index.js +627 -382
- package/build/safe.js +9 -5
- package/manifest.json +5 -2
- package/npm-shrinkwrap.json +1903 -0
- package/package.json +6 -3
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -8,6 +8,16 @@ Every tool reads. One tool writes, and only that one: `ourpr_plan_week` puts
|
|
|
8
8
|
a plan on a day still ahead. It cannot log a run, edit history, or issue
|
|
9
9
|
another credential, and it needs a token made with the write scope.
|
|
10
10
|
|
|
11
|
+
In Claude you do not need this package. Add ourpr. from
|
|
12
|
+
https://ourpr.app/your-runs: it signs you in once, with no token and nothing
|
|
13
|
+
to install. This package is for a script or an agent on your own computer.
|
|
14
|
+
|
|
15
|
+
## Price
|
|
16
|
+
|
|
17
|
+
Reading your own runs is free. Planning is part of **ourpr create**, $10 a
|
|
18
|
+
month, cancel any time: `ourpr_plan_week` answers only on an account that has
|
|
19
|
+
it. Nothing else in ourpr costs money.
|
|
20
|
+
|
|
11
21
|
## Why it exists
|
|
12
22
|
|
|
13
23
|
Every authenticated read in ourpr is gated by a session that lives about an
|
|
@@ -17,42 +27,105 @@ credential you issued to yourself and can revoke.
|
|
|
17
27
|
|
|
18
28
|
## Setup
|
|
19
29
|
|
|
20
|
-
**1. Make a token.** In ourpr, go to
|
|
21
|
-
|
|
30
|
+
**1. Make a token.** In ourpr, go to **Settings → ourpr. mcp → Tokens for a
|
|
31
|
+
script or a local server → New token**. Name it after the machine it
|
|
22
32
|
will live on, and choose **Write** if you want the agent to plan your week;
|
|
23
33
|
**Read** otherwise. Copy it — it is shown once and cannot be
|
|
24
34
|
recovered. A token lasts 90 days.
|
|
25
35
|
|
|
26
36
|
**2. Point a client at it.** The package runs from npm; nothing to clone.
|
|
27
37
|
|
|
38
|
+
**Keep the token out of any file inside a project.** A project's `.mcp.json`,
|
|
39
|
+
`.cursor/mcp.json` or `.vscode/mcp.json` is often committed, and a committed
|
|
40
|
+
token can be read by anyone who can read the repository. Every setup below
|
|
41
|
+
keeps the token in a user-level place or in your system's secret store.
|
|
42
|
+
|
|
43
|
+
**Pin the version.** Each setup names an exact version, so a new release never
|
|
44
|
+
runs with your token until you choose it. Change the number to update.
|
|
45
|
+
|
|
28
46
|
### Claude Code
|
|
29
47
|
|
|
48
|
+
Read the token without echoing it, so it stays out of your shell history, then
|
|
49
|
+
add the server for your user, not for a project:
|
|
50
|
+
|
|
30
51
|
```bash
|
|
31
|
-
|
|
52
|
+
printf 'Token: '; read -rs OURPR_TOKEN; echo
|
|
53
|
+
claude mcp add --scope user --env OURPR_TOKEN="$OURPR_TOKEN" --transport stdio \
|
|
54
|
+
ourpr -- npx -y ourpr-mcp-server@0.4.1
|
|
55
|
+
unset OURPR_TOKEN
|
|
32
56
|
```
|
|
33
57
|
|
|
58
|
+
Claude Code keeps it in `~/.claude.json`, which belongs to your user.
|
|
59
|
+
|
|
34
60
|
### Claude Desktop
|
|
35
61
|
|
|
36
62
|
Download `ourpr.mcpb` from the latest release and open it. Claude Desktop asks
|
|
37
|
-
for the token and
|
|
63
|
+
for the token and keeps it in your system's secret store. This is the
|
|
64
|
+
recommended way.
|
|
38
65
|
|
|
39
|
-
Or edit `claude_desktop_config.json
|
|
66
|
+
Or edit `claude_desktop_config.json`, which lives in your user folder, not in
|
|
67
|
+
a project. The token is then plain text in that file:
|
|
40
68
|
|
|
41
69
|
```json
|
|
42
70
|
{
|
|
43
71
|
"mcpServers": {
|
|
44
72
|
"ourpr": {
|
|
45
73
|
"command": "npx",
|
|
46
|
-
"args": ["-y", "ourpr-mcp-server"],
|
|
74
|
+
"args": ["-y", "ourpr-mcp-server@0.4.1"],
|
|
47
75
|
"env": { "OURPR_TOKEN": "ourpr_pat_..." }
|
|
48
76
|
}
|
|
49
77
|
}
|
|
50
78
|
}
|
|
51
79
|
```
|
|
52
80
|
|
|
53
|
-
###
|
|
81
|
+
### VS Code
|
|
82
|
+
|
|
83
|
+
Run **MCP: Open User Configuration** and add the server there. VS Code asks
|
|
84
|
+
for the token once, masks it, and keeps it in its secret store:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"inputs": [
|
|
89
|
+
{
|
|
90
|
+
"type": "promptString",
|
|
91
|
+
"id": "ourpr-token",
|
|
92
|
+
"description": "ourpr personal access token",
|
|
93
|
+
"password": true
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"servers": {
|
|
97
|
+
"ourpr": {
|
|
98
|
+
"type": "stdio",
|
|
99
|
+
"command": "npx",
|
|
100
|
+
"args": ["-y", "ourpr-mcp-server@0.4.1"],
|
|
101
|
+
"env": { "OURPR_TOKEN": "${input:ourpr-token}" }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Cursor
|
|
108
|
+
|
|
109
|
+
Set `OURPR_TOKEN` in your shell profile or your system's secret manager, and
|
|
110
|
+
add the server to the global `~/.cursor/mcp.json`. The file holds only a
|
|
111
|
+
reference to the variable:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"mcpServers": {
|
|
116
|
+
"ourpr": {
|
|
117
|
+
"command": "npx",
|
|
118
|
+
"args": ["-y", "ourpr-mcp-server@0.4.1"],
|
|
119
|
+
"env": { "OURPR_TOKEN": "${env:OURPR_TOKEN}" }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### If a token leaks
|
|
54
126
|
|
|
55
|
-
|
|
127
|
+
Revoke it in **Profile → Settings → ourpr. mcp**, then make a new one.
|
|
128
|
+
Revocation is immediate.
|
|
56
129
|
|
|
57
130
|
### From source
|
|
58
131
|
|
|
@@ -62,6 +135,12 @@ cd ourpr-mcp-server
|
|
|
62
135
|
npm install && npm run build && npm test
|
|
63
136
|
```
|
|
64
137
|
|
|
138
|
+
## Protocol
|
|
139
|
+
|
|
140
|
+
The server speaks MCP `2026-07-28`, and it still answers a client that opens
|
|
141
|
+
with the older `initialize` handshake. It is built on the official TypeScript
|
|
142
|
+
SDK v2 (`@modelcontextprotocol/server`), and the tests run each version.
|
|
143
|
+
|
|
65
144
|
## Environment
|
|
66
145
|
|
|
67
146
|
| | |
|
|
@@ -75,6 +154,11 @@ credential into the agent's context, its transcript, and any log of either.
|
|
|
75
154
|
|
|
76
155
|
## Tools
|
|
77
156
|
|
|
157
|
+
Every tool reads what you imported into ourpr, and what you logged or planned
|
|
158
|
+
there. Runs that reach ourpr through the Strava sync stay out: Strava's API
|
|
159
|
+
Policy (2026) keeps its API data out of MCP servers and AI tools. Your Strava
|
|
160
|
+
bulk export is yours to use anywhere, so import it and every run is here.
|
|
161
|
+
|
|
78
162
|
### `ourpr_list_runs`
|
|
79
163
|
|
|
80
164
|
Your history between two dates, as a compact table: date, name, type, miles,
|
|
@@ -136,6 +220,49 @@ done that resembles a race you are training for.
|
|
|
136
220
|
"What have I run that's like Boston — 26 miles, 800 feet of climb?"
|
|
137
221
|
```
|
|
138
222
|
|
|
223
|
+
### `ourpr_training_blocks`
|
|
224
|
+
|
|
225
|
+
Your goal race and its Block: race day, distance, goal time, the week of the
|
|
226
|
+
Block today falls in, and the miles for each week so far. A week that has not
|
|
227
|
+
begun shows no miles, not zero. Also the Blocks before your past races, each
|
|
228
|
+
with its result, its weeks and its peak week.
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
"What week of my Dallas block am I in, and how is my mileage building?"
|
|
232
|
+
"How does this block compare with the one before my last marathon?"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`past`: how many past Blocks, newest first. Three by default.
|
|
236
|
+
|
|
237
|
+
### `ourpr_list_races`
|
|
238
|
+
|
|
239
|
+
Every race ourpr finds in your history, tune-ups included, through the same
|
|
240
|
+
detector the app's Blocks use. Each row carries the run id for
|
|
241
|
+
`ourpr_get_run`. The answer also names the fastest result at 5K, 10K, half
|
|
242
|
+
marathon and marathon.
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
"What is my half marathon PR?"
|
|
246
|
+
"List every marathon I have run, with the times"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`distance`, `limit`.
|
|
250
|
+
|
|
251
|
+
### `ourpr_list_plans`
|
|
252
|
+
|
|
253
|
+
The plans already on your week between two dates, two weeks from today by
|
|
254
|
+
default: the day, the name, the miles or time, the tag, your note, whether a
|
|
255
|
+
run you logged fulfilled it, and whether ourpr create wrote it. An agent reads
|
|
256
|
+
this before `ourpr_plan_week`, so a new plan does not land on a day that
|
|
257
|
+
already holds one.
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
"What do I have planned for the next two weeks?"
|
|
261
|
+
"Did I do the runs I planned last week?"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`start_date`, `end_date` (YYYY-MM-DD).
|
|
265
|
+
|
|
139
266
|
### `ourpr_plan_week`
|
|
140
267
|
|
|
141
268
|
The one write. One planned run, or a week of them, onto days still ahead.
|
|
@@ -188,7 +315,9 @@ numbers the agent would only reduce anyway.
|
|
|
188
315
|
|
|
189
316
|
## Security
|
|
190
317
|
|
|
191
|
-
The token grants read access to your own
|
|
318
|
+
The token grants read access to your own runs, your goal race and your plans.
|
|
319
|
+
On ourpr, every query a token makes is scoped to its owner by construction, and
|
|
320
|
+
a test refuses a token route that could reach past it. A token made with the
|
|
192
321
|
write scope, with ourpr create, may also put plans on your own week through
|
|
193
322
|
one route, and nothing else. No token can log a run, issue another token, revoke
|
|
194
323
|
your existing ones, or widen its own scope.
|
|
@@ -265,10 +394,17 @@ bundle's entry point; `npx @anthropic-ai/mcpb pack` builds `ourpr.mcpb` for
|
|
|
265
394
|
the release. `server.json` registers the package in the MCP Registry with
|
|
266
395
|
`mcp-publisher publish`.
|
|
267
396
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
397
|
+
Trusted publishing is the only way in. The package's npm settings require
|
|
398
|
+
two-factor authentication and disallow tokens, so no stolen npm token can
|
|
399
|
+
publish a version, and the OIDC workflow still can. `npm-shrinkwrap.json` ships
|
|
400
|
+
in the package, so every install resolves the same dependency tree that the
|
|
401
|
+
release tested. Run `npm install` and commit the shrinkwrap after any
|
|
402
|
+
dependency change.
|
|
403
|
+
|
|
404
|
+
A release changes the version in five places: `package.json`,
|
|
405
|
+
`npm-shrinkwrap.json` (through `npm install`), `manifest.json`, `server.json`
|
|
406
|
+
and the `McpServer` in `src/index.ts`. Every setup command in this README pins
|
|
407
|
+
the version, and so does `AGENT_VERSION` in ourpr's `lib/agent-connect.ts`.
|
|
272
408
|
|
|
273
409
|
## License
|
|
274
410
|
|
package/build/api.js
CHANGED
|
@@ -152,8 +152,40 @@ export function duration(seconds) {
|
|
|
152
152
|
const m = Math.round((seconds % 3600) / 60);
|
|
153
153
|
return h ? `${h}h ${m}m` : `${m}m`;
|
|
154
154
|
}
|
|
155
|
+
/** "3:05:12" or "21:40". A race result reads to the second. */
|
|
156
|
+
export function clock(seconds) {
|
|
157
|
+
if (!seconds)
|
|
158
|
+
return null;
|
|
159
|
+
const whole = Math.round(seconds);
|
|
160
|
+
const h = Math.floor(whole / 3600);
|
|
161
|
+
const m = Math.floor((whole % 3600) / 60);
|
|
162
|
+
const s = String(whole % 60).padStart(2, "0");
|
|
163
|
+
return h ? `${h}:${String(m).padStart(2, "0")}:${s}` : `${m}:${s}`;
|
|
164
|
+
}
|
|
155
165
|
/** A day key, YYYY-MM-DD, out of whatever the API stored. */
|
|
156
166
|
export const day = (iso) => (iso ?? "").slice(0, 10);
|
|
167
|
+
/** Today on this machine's calendar, which is the runner's own. */
|
|
168
|
+
export function localDay(now) {
|
|
169
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
170
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
171
|
+
return `${now.getFullYear()}-${m}-${d}`;
|
|
172
|
+
}
|
|
173
|
+
// Day keys are walked as UTC dates, so a clock change never moves a day.
|
|
174
|
+
const DAY_MS = 86_400_000;
|
|
175
|
+
const dayMs = (key) => Date.parse(`${key}T00:00:00Z`);
|
|
176
|
+
export function addDays(key, days) {
|
|
177
|
+
const ms = dayMs(key);
|
|
178
|
+
if (Number.isNaN(ms))
|
|
179
|
+
throw new ApiError(`"${key}" is not a date. Use YYYY-MM-DD.`);
|
|
180
|
+
return new Date(ms + days * DAY_MS).toISOString().slice(0, 10);
|
|
181
|
+
}
|
|
182
|
+
/** Which week of a Block `today` falls in, or 0 before week 1's Monday. */
|
|
183
|
+
export function weekOf(start, weeks, today) {
|
|
184
|
+
const walked = Math.floor((dayMs(today) - dayMs(start)) / DAY_MS);
|
|
185
|
+
if (Number.isNaN(walked) || walked < 0)
|
|
186
|
+
return 0;
|
|
187
|
+
return Math.min(weeks, Math.floor(walked / 7) + 1);
|
|
188
|
+
}
|
|
157
189
|
/** A calendar date to a Unix second; "end" reaches the end of the day. */
|
|
158
190
|
export function stamp(date, edge) {
|
|
159
191
|
const time = edge === "start" ? "T00:00:00Z" : "T23:59:59Z";
|