ourpr-mcp-server 0.3.0 → 0.4.0
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 +132 -11
- package/build/api.js +32 -0
- package/build/index.js +621 -382
- 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
|
@@ -25,34 +25,97 @@ recovered. A token lasts 90 days.
|
|
|
25
25
|
|
|
26
26
|
**2. Point a client at it.** The package runs from npm; nothing to clone.
|
|
27
27
|
|
|
28
|
+
**Keep the token out of any file inside a project.** A project's `.mcp.json`,
|
|
29
|
+
`.cursor/mcp.json` or `.vscode/mcp.json` is often committed, and a committed
|
|
30
|
+
token can be read by anyone who can read the repository. Every setup below
|
|
31
|
+
keeps the token in a user-level place or in your system's secret store.
|
|
32
|
+
|
|
33
|
+
**Pin the version.** Each setup names an exact version, so a new release never
|
|
34
|
+
runs with your token until you choose it. Change the number to update.
|
|
35
|
+
|
|
28
36
|
### Claude Code
|
|
29
37
|
|
|
38
|
+
Read the token without echoing it, so it stays out of your shell history, then
|
|
39
|
+
add the server for your user, not for a project:
|
|
40
|
+
|
|
30
41
|
```bash
|
|
31
|
-
|
|
42
|
+
printf 'Token: '; read -rs OURPR_TOKEN; echo
|
|
43
|
+
claude mcp add --scope user --env OURPR_TOKEN="$OURPR_TOKEN" --transport stdio \
|
|
44
|
+
ourpr -- npx -y ourpr-mcp-server@0.4.0
|
|
45
|
+
unset OURPR_TOKEN
|
|
32
46
|
```
|
|
33
47
|
|
|
48
|
+
Claude Code keeps it in `~/.claude.json`, which belongs to your user.
|
|
49
|
+
|
|
34
50
|
### Claude Desktop
|
|
35
51
|
|
|
36
52
|
Download `ourpr.mcpb` from the latest release and open it. Claude Desktop asks
|
|
37
|
-
for the token and
|
|
53
|
+
for the token and keeps it in your system's secret store. This is the
|
|
54
|
+
recommended way.
|
|
38
55
|
|
|
39
|
-
Or edit `claude_desktop_config.json
|
|
56
|
+
Or edit `claude_desktop_config.json`, which lives in your user folder, not in
|
|
57
|
+
a project. The token is then plain text in that file:
|
|
40
58
|
|
|
41
59
|
```json
|
|
42
60
|
{
|
|
43
61
|
"mcpServers": {
|
|
44
62
|
"ourpr": {
|
|
45
63
|
"command": "npx",
|
|
46
|
-
"args": ["-y", "ourpr-mcp-server"],
|
|
64
|
+
"args": ["-y", "ourpr-mcp-server@0.4.0"],
|
|
47
65
|
"env": { "OURPR_TOKEN": "ourpr_pat_..." }
|
|
48
66
|
}
|
|
49
67
|
}
|
|
50
68
|
}
|
|
51
69
|
```
|
|
52
70
|
|
|
53
|
-
###
|
|
71
|
+
### VS Code
|
|
72
|
+
|
|
73
|
+
Run **MCP: Open User Configuration** and add the server there. VS Code asks
|
|
74
|
+
for the token once, masks it, and keeps it in its secret store:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"inputs": [
|
|
79
|
+
{
|
|
80
|
+
"type": "promptString",
|
|
81
|
+
"id": "ourpr-token",
|
|
82
|
+
"description": "ourpr personal access token",
|
|
83
|
+
"password": true
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"servers": {
|
|
87
|
+
"ourpr": {
|
|
88
|
+
"type": "stdio",
|
|
89
|
+
"command": "npx",
|
|
90
|
+
"args": ["-y", "ourpr-mcp-server@0.4.0"],
|
|
91
|
+
"env": { "OURPR_TOKEN": "${input:ourpr-token}" }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Cursor
|
|
98
|
+
|
|
99
|
+
Set `OURPR_TOKEN` in your shell profile or your system's secret manager, and
|
|
100
|
+
add the server to the global `~/.cursor/mcp.json`. The file holds only a
|
|
101
|
+
reference to the variable:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"mcpServers": {
|
|
106
|
+
"ourpr": {
|
|
107
|
+
"command": "npx",
|
|
108
|
+
"args": ["-y", "ourpr-mcp-server@0.4.0"],
|
|
109
|
+
"env": { "OURPR_TOKEN": "${env:OURPR_TOKEN}" }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### If a token leaks
|
|
54
116
|
|
|
55
|
-
|
|
117
|
+
Revoke it in **Profile → Settings → ourpr. mcp**, then make a new one.
|
|
118
|
+
Revocation is immediate.
|
|
56
119
|
|
|
57
120
|
### From source
|
|
58
121
|
|
|
@@ -62,6 +125,12 @@ cd ourpr-mcp-server
|
|
|
62
125
|
npm install && npm run build && npm test
|
|
63
126
|
```
|
|
64
127
|
|
|
128
|
+
## Protocol
|
|
129
|
+
|
|
130
|
+
The server speaks MCP `2026-07-28`, and it still answers a client that opens
|
|
131
|
+
with the older `initialize` handshake. It is built on the official TypeScript
|
|
132
|
+
SDK v2 (`@modelcontextprotocol/server`), and the tests run each version.
|
|
133
|
+
|
|
65
134
|
## Environment
|
|
66
135
|
|
|
67
136
|
| | |
|
|
@@ -136,6 +205,49 @@ done that resembles a race you are training for.
|
|
|
136
205
|
"What have I run that's like Boston — 26 miles, 800 feet of climb?"
|
|
137
206
|
```
|
|
138
207
|
|
|
208
|
+
### `ourpr_training_blocks`
|
|
209
|
+
|
|
210
|
+
Your goal race and its Block: race day, distance, goal time, the week of the
|
|
211
|
+
Block today falls in, and the miles for each week so far. A week that has not
|
|
212
|
+
begun shows no miles, not zero. Also the Blocks before your past races, each
|
|
213
|
+
with its result, its weeks and its peak week.
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
"What week of my Dallas block am I in, and how is my mileage building?"
|
|
217
|
+
"How does this block compare with the one before my last marathon?"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`past`: how many past Blocks, newest first. Three by default.
|
|
221
|
+
|
|
222
|
+
### `ourpr_list_races`
|
|
223
|
+
|
|
224
|
+
Every race ourpr finds in your history, tune-ups included, through the same
|
|
225
|
+
detector the app's Blocks use. Each row carries the run id for
|
|
226
|
+
`ourpr_get_run`. The answer also names the fastest result at 5K, 10K, half
|
|
227
|
+
marathon and marathon.
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
"What is my half marathon PR?"
|
|
231
|
+
"List every marathon I have run, with the times"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`distance`, `limit`.
|
|
235
|
+
|
|
236
|
+
### `ourpr_list_plans`
|
|
237
|
+
|
|
238
|
+
The plans already on your week between two dates, two weeks from today by
|
|
239
|
+
default: the day, the name, the miles or time, the tag, your note, whether a
|
|
240
|
+
run you logged fulfilled it, and whether ourpr create wrote it. An agent reads
|
|
241
|
+
this before `ourpr_plan_week`, so a new plan does not land on a day that
|
|
242
|
+
already holds one.
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
"What do I have planned for the next two weeks?"
|
|
246
|
+
"Did I do the runs I planned last week?"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`start_date`, `end_date` (YYYY-MM-DD).
|
|
250
|
+
|
|
139
251
|
### `ourpr_plan_week`
|
|
140
252
|
|
|
141
253
|
The one write. One planned run, or a week of them, onto days still ahead.
|
|
@@ -188,7 +300,9 @@ numbers the agent would only reduce anyway.
|
|
|
188
300
|
|
|
189
301
|
## Security
|
|
190
302
|
|
|
191
|
-
The token grants read access to your own
|
|
303
|
+
The token grants read access to your own runs, your goal race and your plans.
|
|
304
|
+
On ourpr, every query a token makes is scoped to its owner by construction, and
|
|
305
|
+
a test refuses a token route that could reach past it. A token made with the
|
|
192
306
|
write scope, with ourpr create, may also put plans on your own week through
|
|
193
307
|
one route, and nothing else. No token can log a run, issue another token, revoke
|
|
194
308
|
your existing ones, or widen its own scope.
|
|
@@ -265,10 +379,17 @@ bundle's entry point; `npx @anthropic-ai/mcpb pack` builds `ourpr.mcpb` for
|
|
|
265
379
|
the release. `server.json` registers the package in the MCP Registry with
|
|
266
380
|
`mcp-publisher publish`.
|
|
267
381
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
382
|
+
Trusted publishing is the only way in. The package's npm settings require
|
|
383
|
+
two-factor authentication and disallow tokens, so no stolen npm token can
|
|
384
|
+
publish a version, and the OIDC workflow still can. `npm-shrinkwrap.json` ships
|
|
385
|
+
in the package, so every install resolves the same dependency tree that the
|
|
386
|
+
release tested. Run `npm install` and commit the shrinkwrap after any
|
|
387
|
+
dependency change.
|
|
388
|
+
|
|
389
|
+
A release changes the version in five places: `package.json`,
|
|
390
|
+
`npm-shrinkwrap.json` (through `npm install`), `manifest.json`, `server.json`
|
|
391
|
+
and the `McpServer` in `src/index.ts`. Every setup command in this README pins
|
|
392
|
+
the version, and so does `AGENT_VERSION` in ourpr's `lib/agent-connect.ts`.
|
|
272
393
|
|
|
273
394
|
## License
|
|
274
395
|
|
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";
|