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 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
- **Profile → Settings → ourpr. mcp → New token**. Name it after the machine it
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
- claude mcp add ourpr --env OURPR_TOKEN=ourpr_pat_... -- npx -y ourpr-mcp-server
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 stores it as a secret.
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
- ### Cursor, VS Code
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
- `.cursor/mcp.json` or `.vscode/mcp.json`, same shape as above.
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 activity data. A token made with the
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
- The tag run answers npm's `E404` on the PUT until the package lists this
269
- repository and `publish.yml` as a trusted publisher on npmjs.com (package
270
- settings, Trusted publisher). Until then the release is by hand: `npm login`,
271
- then `npm publish` from this directory, which builds and tests first.
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";