@cg3/prior-mcp 0.6.3 → 0.7.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/dist/client.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  /**
3
- * Prior API client shared between local MCP (stdio) and remote MCP server.
3
+ * Prior API client shared between local MCP (stdio) and remote MCP server.
4
4
  *
5
- * Requires an API key via PRIOR_API_KEY env var or ~/.prior/config.json.
6
- * Get your key at https://prior.cg3.io/account
5
+ * Supports:
6
+ * - API keys for durable machine auth
7
+ * - first-party OIDC browser login for interactive human auth
7
8
  */
8
9
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
10
  if (k2 === undefined) k2 = k;
@@ -39,16 +40,159 @@ var __importStar = (this && this.__importStar) || (function () {
39
40
  };
40
41
  })();
41
42
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.PriorApiClient = exports.CONFIG_PATH = void 0;
43
- const fs = __importStar(require("fs"));
44
- const path = __importStar(require("path"));
45
- const os = __importStar(require("os"));
43
+ exports.PriorApiClient = exports.OIDC_CLIENT_ID = exports.CONFIG_PATH = void 0;
44
+ const childProcess = __importStar(require("node:child_process"));
45
+ const crypto = __importStar(require("node:crypto"));
46
+ const fs = __importStar(require("node:fs"));
47
+ const http = __importStar(require("node:http"));
48
+ const os = __importStar(require("node:os"));
49
+ const path = __importStar(require("node:path"));
46
50
  exports.CONFIG_PATH = path.join(os.homedir(), ".prior", "config.json");
47
- const VERSION = "0.5.0";
51
+ exports.OIDC_CLIENT_ID = "prior-mcp";
52
+ const VERSION = "0.7.0";
53
+ const DEFAULT_TIMEOUT_MS = 180_000;
54
+ // Renamed prior:read/prior:write → account:read/account:write on 2026-04-26 per
55
+ // the oauth-scope-namespace-overhaul initiative (operations/initiatives/...).
56
+ // Breaking change for callers using DEFAULT_OIDC_SCOPES; bump npm minor.
57
+ const DEFAULT_OIDC_SCOPES = "openid profile email account:read account:write offline_access";
58
+ function compactConfig(config) {
59
+ return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== undefined && value !== null && value !== ""));
60
+ }
61
+ function escapeHtml(input) {
62
+ return input
63
+ .replace(/&/g, "&")
64
+ .replace(/</g, "&lt;")
65
+ .replace(/>/g, "&gt;")
66
+ .replace(/"/g, "&quot;")
67
+ .replace(/'/g, "&#39;");
68
+ }
69
+ function decodeJwtPayload(token) {
70
+ const parts = token.split(".");
71
+ if (parts.length !== 3)
72
+ return null;
73
+ try {
74
+ const padded = parts[1] + "=".repeat((4 - (parts[1].length % 4)) % 4);
75
+ return JSON.parse(Buffer.from(padded, "base64url").toString("utf-8"));
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ function deriveExpiresAt(accessToken, expiresIn) {
82
+ if (typeof expiresIn === "number" && expiresIn > 0) {
83
+ return new Date(Date.now() + expiresIn * 1000).toISOString();
84
+ }
85
+ const payload = decodeJwtPayload(accessToken);
86
+ if (typeof payload?.exp === "number") {
87
+ return new Date(payload.exp * 1000).toISOString();
88
+ }
89
+ return undefined;
90
+ }
91
+ function isExpired(expiresAt, accessToken) {
92
+ if (expiresAt) {
93
+ const expiryMs = Date.parse(expiresAt);
94
+ if (!Number.isNaN(expiryMs)) {
95
+ return expiryMs <= (Date.now() + 30_000);
96
+ }
97
+ }
98
+ const payload = accessToken ? decodeJwtPayload(accessToken) : null;
99
+ if (typeof payload?.exp === "number") {
100
+ return (payload.exp * 1000) <= (Date.now() + 30_000);
101
+ }
102
+ return false;
103
+ }
104
+ function launchDetached(command, args) {
105
+ const child = childProcess.spawn(command, args, {
106
+ detached: true,
107
+ stdio: "ignore",
108
+ windowsHide: true,
109
+ });
110
+ child.unref();
111
+ }
112
+ function generateCodeVerifier() {
113
+ return crypto.randomBytes(32).toString("base64url");
114
+ }
115
+ function generateCodeChallenge(verifier) {
116
+ return crypto.createHash("sha256").update(verifier).digest("base64url");
117
+ }
118
+ function openBrowser(url) {
119
+ const platform = process.platform;
120
+ try {
121
+ if (platform === "darwin") {
122
+ launchDetached("open", [url]);
123
+ }
124
+ else if (platform === "win32") {
125
+ launchDetached("rundll32.exe", ["url.dll,FileProtocolHandler", url]);
126
+ }
127
+ else {
128
+ launchDetached("xdg-open", [url]);
129
+ }
130
+ }
131
+ catch {
132
+ // Best effort only. We always print the URL for manual fallback.
133
+ }
134
+ }
135
+ function extractData(value) {
136
+ if (value && typeof value === "object" && "data" in value) {
137
+ return value.data;
138
+ }
139
+ return value;
140
+ }
141
+ function buildMissingAuthMessage() {
142
+ return [
143
+ "No Prior auth configured.",
144
+ "Run `prior-mcp --login` for browser OIDC auth,",
145
+ "configure PRIOR_API_KEY for durable machine auth,",
146
+ "or use PRIOR_ACCESS_TOKEN only for advanced manual token overrides.",
147
+ ].join(" ");
148
+ }
149
+ function hardenConfigPermissions(filePath) {
150
+ const configDir = path.dirname(filePath);
151
+ if (process.platform === "win32") {
152
+ try {
153
+ childProcess.execSync(`icacls "${configDir}" /inheritance:r /grant:r "%USERNAME%:(OI)(CI)F"`, {
154
+ stdio: "ignore",
155
+ shell: "cmd.exe",
156
+ });
157
+ }
158
+ catch {
159
+ // Best effort only.
160
+ }
161
+ try {
162
+ childProcess.execSync(`icacls "${filePath}" /inheritance:r /grant:r "%USERNAME%:F"`, {
163
+ stdio: "ignore",
164
+ shell: "cmd.exe",
165
+ });
166
+ }
167
+ catch {
168
+ // Best effort only.
169
+ }
170
+ return;
171
+ }
172
+ try {
173
+ fs.chmodSync(configDir, 0o700);
174
+ }
175
+ catch {
176
+ // Best effort only.
177
+ }
178
+ try {
179
+ fs.chmodSync(filePath, 0o600);
180
+ }
181
+ catch {
182
+ // Best effort only.
183
+ }
184
+ }
48
185
  class PriorApiClient {
49
186
  apiUrl;
187
+ _authType;
50
188
  _apiKey;
51
189
  _agentId;
190
+ _accessToken;
191
+ _refreshToken;
192
+ _expiresAt;
193
+ _accountId;
194
+ _displayName;
195
+ _email;
52
196
  persistConfig;
53
197
  userAgent;
54
198
  traceId;
@@ -56,26 +200,36 @@ class PriorApiClient {
56
200
  this.apiUrl = options.apiUrl || process.env.PRIOR_API_URL || "https://api.cg3.io";
57
201
  this._apiKey = options.apiKey || process.env.PRIOR_API_KEY;
58
202
  this._agentId = options.agentId;
203
+ this._accessToken = options.accessToken || process.env.PRIOR_ACCESS_TOKEN;
204
+ this._refreshToken = options.refreshToken || process.env.PRIOR_REFRESH_TOKEN;
205
+ this._expiresAt = options.expiresAt;
59
206
  this.persistConfig = options.persistConfig ?? true;
60
207
  this.userAgent = options.userAgent || `prior-mcp/${VERSION}`;
61
208
  this.traceId = options.traceId;
62
- // Load config on startup if no key provided
63
- if (!this._apiKey && this.persistConfig) {
209
+ if (this._accessToken) {
210
+ this._authType = "oidc";
211
+ }
212
+ else if (this._apiKey) {
213
+ this._authType = "api_key";
214
+ }
215
+ if ((!this._apiKey && !this._accessToken) && this.persistConfig) {
64
216
  const config = this.loadConfig();
65
217
  if (config) {
66
- this._apiKey = config.apiKey;
67
- this._agentId = config.agentId;
218
+ this.applyConfig(config);
68
219
  }
69
220
  }
70
- // Require an API key — no more auto-registration
71
- if (!this._apiKey) {
72
- throw new Error("No Prior API key configured. " +
73
- "Get your key at https://prior.cg3.io/getkey then set PRIOR_API_KEY in your environment, " +
74
- "or add it to ~/.prior/config.json as {\"apiKey\": \"...\"}.");
75
- }
76
221
  }
222
+ get authType() { return this._authType; }
77
223
  get apiKey() { return this._apiKey; }
78
224
  get agentId() { return this._agentId; }
225
+ get accessToken() { return this._accessToken; }
226
+ prefersOidc() {
227
+ if (this._authType === "oidc")
228
+ return true;
229
+ if (this._authType === "api_key")
230
+ return false;
231
+ return Boolean(this._accessToken || this._refreshToken);
232
+ }
79
233
  loadConfig() {
80
234
  try {
81
235
  const raw = fs.readFileSync(exports.CONFIG_PATH, "utf-8");
@@ -86,15 +240,351 @@ class PriorApiClient {
86
240
  }
87
241
  }
88
242
  saveConfig(config) {
89
- fs.mkdirSync(path.dirname(exports.CONFIG_PATH), { recursive: true });
90
- fs.writeFileSync(exports.CONFIG_PATH, JSON.stringify(config, null, 2));
243
+ fs.mkdirSync(path.dirname(exports.CONFIG_PATH), { recursive: true, mode: 0o700 });
244
+ fs.writeFileSync(exports.CONFIG_PATH, JSON.stringify(compactConfig(config), null, 2));
245
+ hardenConfigPermissions(exports.CONFIG_PATH);
246
+ }
247
+ clearOidcConfig() {
248
+ const existing = this.loadConfig() || {};
249
+ const next = { ...existing };
250
+ delete next.authType;
251
+ delete next.accessToken;
252
+ delete next.refreshToken;
253
+ delete next.expiresAt;
254
+ delete next.accountId;
255
+ delete next.displayName;
256
+ delete next.email;
257
+ this.saveConfig(next);
258
+ this._accessToken = undefined;
259
+ this._refreshToken = undefined;
260
+ this._expiresAt = undefined;
261
+ this._accountId = undefined;
262
+ this._displayName = undefined;
263
+ this._email = undefined;
264
+ this._authType = this._apiKey ? "api_key" : undefined;
265
+ }
266
+ async logout() {
267
+ let remoteRevoked = false;
268
+ if (this._refreshToken) {
269
+ try {
270
+ const revokeUrl = new URL("/revoke", this.apiUrl).toString();
271
+ const response = await fetch(revokeUrl, {
272
+ method: "POST",
273
+ headers: {
274
+ "Content-Type": "application/x-www-form-urlencoded",
275
+ "User-Agent": this.userAgent,
276
+ },
277
+ body: new URLSearchParams({
278
+ token: this._refreshToken,
279
+ token_type_hint: "refresh_token",
280
+ }).toString(),
281
+ signal: AbortSignal.timeout(5_000),
282
+ });
283
+ remoteRevoked = response.ok;
284
+ }
285
+ catch {
286
+ // Best effort only. We always clear local state.
287
+ }
288
+ }
289
+ this.clearOidcConfig();
290
+ return { remoteRevoked };
291
+ }
292
+ applyConfig(config) {
293
+ this._authType = config.authType
294
+ || (config.accessToken ? "oidc" : config.apiKey ? "api_key" : undefined);
295
+ this._apiKey = this._apiKey || config.apiKey;
296
+ this._agentId = this._agentId || config.agentId;
297
+ this._accessToken = this._accessToken || config.accessToken;
298
+ this._refreshToken = this._refreshToken || config.refreshToken;
299
+ this._expiresAt = this._expiresAt || config.expiresAt;
300
+ this._accountId = this._accountId || config.accountId;
301
+ this._displayName = this._displayName || config.displayName;
302
+ this._email = this._email || config.email;
303
+ }
304
+ persistCurrentConfig(patch) {
305
+ if (!this.persistConfig)
306
+ return;
307
+ const base = this.loadConfig() || {};
308
+ this.saveConfig({ ...base, ...patch });
309
+ }
310
+ async loadDiscovery() {
311
+ const discoveryUrl = new URL("/.well-known/openid-configuration", this.apiUrl).toString();
312
+ const res = await fetch(discoveryUrl, {
313
+ headers: { "User-Agent": this.userAgent },
314
+ signal: AbortSignal.timeout(5_000),
315
+ });
316
+ if (!res.ok) {
317
+ throw new Error(`OIDC discovery failed (${res.status})`);
318
+ }
319
+ return await res.json();
320
+ }
321
+ async fetchUserInfo(token, explicitUrl) {
322
+ const userinfoUrl = explicitUrl || new URL("/userinfo", this.apiUrl).toString();
323
+ const res = await fetch(userinfoUrl, {
324
+ headers: {
325
+ "Authorization": `Bearer ${token}`,
326
+ "User-Agent": this.userAgent,
327
+ },
328
+ signal: AbortSignal.timeout(5_000),
329
+ });
330
+ if (!res.ok) {
331
+ throw new Error(`userinfo request failed (${res.status})`);
332
+ }
333
+ return await res.json();
334
+ }
335
+ async loginInteractive() {
336
+ const discovery = await this.loadDiscovery();
337
+ const authorizeUrl = discovery.authorization_endpoint || new URL("/authorize", this.apiUrl).toString();
338
+ const tokenUrl = discovery.token_endpoint || new URL("/token", this.apiUrl).toString();
339
+ const userinfoUrl = discovery.userinfo_endpoint || new URL("/userinfo", this.apiUrl).toString();
340
+ const state = crypto.randomBytes(16).toString("hex");
341
+ const nonce = crypto.randomBytes(16).toString("hex");
342
+ const codeVerifier = generateCodeVerifier();
343
+ const codeChallenge = generateCodeChallenge(codeVerifier);
344
+ return await new Promise((resolve, reject) => {
345
+ let settled = false;
346
+ const finishResolve = (config) => {
347
+ if (settled)
348
+ return;
349
+ settled = true;
350
+ clearTimeout(timeoutHandle);
351
+ resolve(config);
352
+ };
353
+ const finishReject = (error) => {
354
+ if (settled)
355
+ return;
356
+ settled = true;
357
+ clearTimeout(timeoutHandle);
358
+ reject(error);
359
+ };
360
+ const closeServer = () => {
361
+ try {
362
+ server.close();
363
+ }
364
+ catch {
365
+ // Ignore close races when the callback and timeout fire near-simultaneously.
366
+ }
367
+ };
368
+ const server = http.createServer(async (req, res) => {
369
+ if (!req.url?.startsWith("/callback")) {
370
+ res.writeHead(404);
371
+ res.end();
372
+ return;
373
+ }
374
+ const callbackUrl = new URL(req.url, `http://${req.headers.host}`);
375
+ const returnedState = callbackUrl.searchParams.get("state");
376
+ const code = callbackUrl.searchParams.get("code");
377
+ const error = callbackUrl.searchParams.get("error");
378
+ if (returnedState !== state) {
379
+ res.writeHead(403, { "Content-Type": "text/html", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" });
380
+ res.end("<html><body><h1>Invalid state</h1><p>This login response did not match the original request.</p></body></html>");
381
+ return;
382
+ }
383
+ if (error) {
384
+ res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" });
385
+ res.end(`<html><body><h1>Login cancelled</h1><p>${escapeHtml(error)}</p></body></html>`);
386
+ finishReject(new Error(`OIDC login cancelled: ${error}`));
387
+ closeServer();
388
+ return;
389
+ }
390
+ if (!code) {
391
+ res.writeHead(400, { "Content-Type": "text/html", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" });
392
+ res.end("<html><body><h1>Missing authorization code</h1></body></html>");
393
+ finishReject(new Error("OIDC login failed: missing authorization code"));
394
+ closeServer();
395
+ return;
396
+ }
397
+ try {
398
+ const redirectUri = `http://127.0.0.1:${server.address().port}/callback`;
399
+ const tokenRes = await fetch(tokenUrl, {
400
+ method: "POST",
401
+ headers: {
402
+ "Content-Type": "application/x-www-form-urlencoded",
403
+ "User-Agent": this.userAgent,
404
+ },
405
+ body: new URLSearchParams({
406
+ grant_type: "authorization_code",
407
+ client_id: exports.OIDC_CLIENT_ID,
408
+ code,
409
+ code_verifier: codeVerifier,
410
+ redirect_uri: redirectUri,
411
+ }).toString(),
412
+ signal: AbortSignal.timeout(10_000),
413
+ });
414
+ const tokenBody = await tokenRes.json();
415
+ if (!tokenRes.ok || !tokenBody.access_token) {
416
+ const message = tokenBody.error_description || tokenBody.error || `OIDC token exchange failed (${tokenRes.status})`;
417
+ res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" });
418
+ res.end(`<html><body><h1>Login failed</h1><p>${escapeHtml(message)}</p></body></html>`);
419
+ finishReject(new Error(message));
420
+ closeServer();
421
+ return;
422
+ }
423
+ const userinfo = await this.fetchUserInfo(tokenBody.access_token, userinfoUrl);
424
+ const config = {
425
+ ...(this.loadConfig() || {}),
426
+ authType: "oidc",
427
+ accessToken: tokenBody.access_token,
428
+ refreshToken: tokenBody.refresh_token,
429
+ expiresAt: deriveExpiresAt(tokenBody.access_token, tokenBody.expires_in),
430
+ accountId: userinfo.sub,
431
+ displayName: userinfo.name,
432
+ email: userinfo.email,
433
+ };
434
+ this.applyConfig(config);
435
+ this.persistCurrentConfig(config);
436
+ res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" });
437
+ res.end(`<html><body><h1>Connected to Prior</h1><p>You can close this tab.</p><script>setTimeout(()=>window.close(), 1500)</script></body></html>`);
438
+ finishResolve(config);
439
+ closeServer();
440
+ }
441
+ catch (error) {
442
+ const message = error instanceof Error ? error.message : "Unknown error";
443
+ res.writeHead(500, { "Content-Type": "text/html", "Cache-Control": "no-store", "Referrer-Policy": "no-referrer" });
444
+ res.end(`<html><body><h1>Login failed</h1><p>${escapeHtml(message)}</p></body></html>`);
445
+ finishReject(new Error(message));
446
+ closeServer();
447
+ }
448
+ });
449
+ server.listen(0, "127.0.0.1", () => {
450
+ const address = server.address();
451
+ const redirectUri = `http://127.0.0.1:${address.port}/callback`;
452
+ const loginUrl = `${authorizeUrl}?${new URLSearchParams({
453
+ response_type: "code",
454
+ client_id: exports.OIDC_CLIENT_ID,
455
+ redirect_uri: redirectUri,
456
+ scope: DEFAULT_OIDC_SCOPES,
457
+ state,
458
+ nonce,
459
+ code_challenge: codeChallenge,
460
+ code_challenge_method: "S256",
461
+ }).toString()}`;
462
+ process.stderr.write(`[prior-mcp] Opening browser login for ${exports.OIDC_CLIENT_ID}\n`);
463
+ process.stderr.write(`[prior-mcp] If the browser does not open, visit: ${loginUrl}\n`);
464
+ openBrowser(loginUrl);
465
+ });
466
+ const timeoutHandle = setTimeout(() => {
467
+ closeServer();
468
+ finishReject(new Error("OIDC login timed out"));
469
+ }, DEFAULT_TIMEOUT_MS);
470
+ timeoutHandle.unref?.();
471
+ });
472
+ }
473
+ async refreshOidcAccessToken() {
474
+ if (!this._refreshToken) {
475
+ return false;
476
+ }
477
+ const tokenUrl = new URL("/token", this.apiUrl).toString();
478
+ try {
479
+ const res = await fetch(tokenUrl, {
480
+ method: "POST",
481
+ headers: {
482
+ "Content-Type": "application/x-www-form-urlencoded",
483
+ "User-Agent": this.userAgent,
484
+ },
485
+ body: new URLSearchParams({
486
+ grant_type: "refresh_token",
487
+ client_id: exports.OIDC_CLIENT_ID,
488
+ refresh_token: this._refreshToken,
489
+ }).toString(),
490
+ signal: AbortSignal.timeout(10_000),
491
+ });
492
+ const body = await res.json();
493
+ if (!res.ok || !body.access_token) {
494
+ this.clearOidcConfig();
495
+ return false;
496
+ }
497
+ this._authType = "oidc";
498
+ this._accessToken = body.access_token;
499
+ this._refreshToken = body.refresh_token || this._refreshToken;
500
+ this._expiresAt = deriveExpiresAt(body.access_token, body.expires_in);
501
+ this.persistCurrentConfig({
502
+ authType: "oidc",
503
+ accessToken: this._accessToken,
504
+ refreshToken: this._refreshToken,
505
+ expiresAt: this._expiresAt,
506
+ accountId: this._accountId,
507
+ displayName: this._displayName,
508
+ email: this._email,
509
+ });
510
+ return true;
511
+ }
512
+ catch {
513
+ return false;
514
+ }
515
+ }
516
+ async ensureAuth() {
517
+ if (this.prefersOidc()) {
518
+ this._authType = "oidc";
519
+ if (this._accessToken && !isExpired(this._expiresAt, this._accessToken)) {
520
+ return this._accessToken;
521
+ }
522
+ const refreshed = await this.refreshOidcAccessToken();
523
+ if (refreshed && this._accessToken) {
524
+ return this._accessToken;
525
+ }
526
+ if (this._accessToken || this._refreshToken) {
527
+ throw new Error("Stored Prior OIDC auth is no longer usable. Run `prior-mcp --login` to refresh it " +
528
+ "or `prior-mcp --logout` to clear it before falling back to machine auth.");
529
+ }
530
+ }
531
+ if (this._apiKey) {
532
+ this._authType = "api_key";
533
+ return this._apiKey;
534
+ }
535
+ throw new Error(buildMissingAuthMessage());
536
+ }
537
+ async getStatus() {
538
+ const auth = await this.ensureAuth();
539
+ if (this._authType === "oidc") {
540
+ const [accountEnvelope, profileEnvelope, userinfo] = await Promise.all([
541
+ this.request("GET", "/v1/account", undefined, auth),
542
+ this.request("GET", "/v1/prior/me/profile", undefined, auth),
543
+ this.fetchUserInfo(auth),
544
+ ]);
545
+ const account = extractData(accountEnvelope);
546
+ const profile = extractData(profileEnvelope);
547
+ const displayName = userinfo.name || this._displayName;
548
+ const email = userinfo.email || this._email;
549
+ this._accountId = userinfo.sub || account?.account?.id || this._accountId;
550
+ this._displayName = displayName;
551
+ this._email = email;
552
+ this.persistCurrentConfig({
553
+ authType: "oidc",
554
+ accessToken: this._accessToken,
555
+ refreshToken: this._refreshToken,
556
+ expiresAt: this._expiresAt,
557
+ accountId: this._accountId,
558
+ displayName: this._displayName,
559
+ email: this._email,
560
+ });
561
+ return {
562
+ id: account?.account?.id || userinfo.sub || "",
563
+ authType: "oidc",
564
+ credits: Number(profile?.subscription?.credits ?? 0),
565
+ tier: profile?.subscription?.tier || "free",
566
+ contributions: profile?.reputation?.contributionCount,
567
+ displayName,
568
+ email,
569
+ };
570
+ }
571
+ const data = await this.request("GET", "/v1/agents/me", undefined, auth);
572
+ const agent = extractData(data);
573
+ return {
574
+ id: agent?.id || "",
575
+ authType: "api_key",
576
+ credits: agent?.credits ?? 0,
577
+ tier: agent?.tier || "free",
578
+ contributions: agent?.contributions,
579
+ displayName: agent?.agentName,
580
+ };
91
581
  }
92
- async request(method, path, body, key) {
93
- const k = key || this._apiKey;
94
- const res = await fetch(`${this.apiUrl}${path}`, {
582
+ async request(method, requestPath, body, key) {
583
+ const auth = key || await this.ensureAuth();
584
+ const res = await fetch(`${this.apiUrl}${requestPath}`, {
95
585
  method,
96
586
  headers: {
97
- ...(k ? { "Authorization": `Bearer ${k}` } : {}),
587
+ "Authorization": `Bearer ${auth}`,
98
588
  "Content-Type": "application/json",
99
589
  "User-Agent": this.userAgent,
100
590
  ...(this.traceId ? { "X-Request-Id": this.traceId } : {}),
package/dist/index.d.ts CHANGED
@@ -1,13 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { PriorApiClient, type PriorConfig } from "./client.js";
3
4
  export { CONFIG_PATH, PriorApiClient } from "./client.js";
4
5
  export { registerTools } from "./tools.js";
5
6
  export { registerResources } from "./resources.js";
6
7
  export { detectHost, formatResults } from "./utils.js";
7
- export declare function loadConfig(): import("./client.js").PriorConfig | null;
8
- export declare function saveConfig(config: {
9
- apiKey: string;
10
- agentId: string;
11
- }): void;
12
- export declare function createServer(): McpServer;
8
+ export declare function loadConfig(): PriorConfig | null;
9
+ export declare function saveConfig(config: PriorConfig): void;
10
+ export declare function createServer(client?: PriorApiClient): McpServer;
13
11
  export declare function main(): Promise<void>;
package/dist/index.js CHANGED
@@ -24,22 +24,42 @@ Object.defineProperty(exports, "registerResources", { enumerable: true, get: fun
24
24
  var utils_js_1 = require("./utils.js");
25
25
  Object.defineProperty(exports, "detectHost", { enumerable: true, get: function () { return utils_js_1.detectHost; } });
26
26
  Object.defineProperty(exports, "formatResults", { enumerable: true, get: function () { return utils_js_1.formatResults; } });
27
- // Legacy function exports for backward compatibility
28
- function loadConfig() { return client.loadConfig(); }
29
- function saveConfig(config) { return client.saveConfig(config); }
30
- const client = new client_js_1.PriorApiClient();
31
- const server = new mcp_js_1.McpServer({
32
- name: "prior",
33
- version: "0.6.3",
34
- }, {
35
- instructions: `Prior is a knowledge base of agent-discovered solutions to technical problems and research questions — including debugging, troubleshooting, configuration, integration, migration, architecture decisions, and more. Search Prior before investigating errors or starting multi-step research. Tool responses handle feedback and contribution prompts. See prior://docs/agent-guide for usage patterns.`,
36
- });
37
- (0, tools_js_1.registerTools)(server, { client });
38
- (0, resources_js_1.registerResources)(server, { client });
39
- function createServer() {
27
+ function buildServer(client) {
28
+ const server = new mcp_js_1.McpServer({
29
+ name: "prior",
30
+ version: "0.6.4",
31
+ }, {
32
+ instructions: `Prior is a knowledge base of agent-discovered solutions to technical problems and research questions, including debugging, troubleshooting, configuration, integration, migration, architecture decisions, and more. Search Prior before investigating errors or starting multi-step research. Tool responses handle feedback and contribution prompts. See prior://docs/agent-guide for usage patterns.`,
33
+ });
34
+ (0, tools_js_1.registerTools)(server, { client });
35
+ (0, resources_js_1.registerResources)(server, { client });
40
36
  return server;
41
37
  }
38
+ // Legacy function exports for backward compatibility
39
+ function loadConfig() {
40
+ return new client_js_1.PriorApiClient({ persistConfig: true }).loadConfig();
41
+ }
42
+ function saveConfig(config) {
43
+ return new client_js_1.PriorApiClient({ persistConfig: true }).saveConfig(config);
44
+ }
45
+ function createServer(client = new client_js_1.PriorApiClient()) {
46
+ return buildServer(client);
47
+ }
42
48
  async function main() {
49
+ if (process.argv.includes("--login")) {
50
+ const client = new client_js_1.PriorApiClient({ persistConfig: true });
51
+ const config = await client.loginInteractive();
52
+ const subject = config.displayName || config.email || config.accountId || "Prior user";
53
+ console.error(`[prior-mcp] Browser login complete for ${subject}`);
54
+ return;
55
+ }
56
+ if (process.argv.includes("--logout")) {
57
+ const client = new client_js_1.PriorApiClient({ persistConfig: true });
58
+ await client.logout();
59
+ console.error("[prior-mcp] Cleared stored OIDC login. API key config, if any, was preserved.");
60
+ return;
61
+ }
62
+ const server = createServer();
43
63
  const transport = new stdio_js_1.StdioServerTransport();
44
64
  await server.connect(transport);
45
65
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Prior MCP resources shared between local and remote MCP servers.
2
+ * Prior MCP resources shared between local and remote MCP servers.
3
3
  *
4
4
  * Usage:
5
5
  * import { registerResources } from "@cg3/prior-mcp/resources";