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 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
- claude mcp add ourpr --env OURPR_TOKEN=ourpr_pat_... -- npx -y ourpr-mcp-server
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 stores it as a secret.
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
- ### Cursor, VS Code
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
- `.cursor/mcp.json` or `.vscode/mcp.json`, same shape as above.
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 activity data. A token made with the
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
- 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.
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";