@spilno/herald-mcp 1.36.0 → 1.36.2

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/cli.js CHANGED
@@ -26,11 +26,106 @@ import { sanitize, previewSanitization } from "./sanitization.js";
26
26
  const CEDA_API_URL = process.env.CEDA_URL || process.env.HERALD_API_URL || "https://getceda.com";
27
27
  // CEDA_TOKEN is the primary auth (from app.getceda.com OAuth)
28
28
  // HERALD_API_TOKEN kept for backwards compatibility
29
- const CEDA_API_TOKEN = process.env.CEDA_TOKEN || process.env.HERALD_API_TOKEN;
29
+ // CEDA-FIX: Also check ~/.herald/token.json for global token (one key per user)
30
+ const TOKEN_FILE_PATH = join(homedir(), ".herald", "token.json");
31
+ function loadGlobalToken() {
32
+ try {
33
+ if (existsSync(TOKEN_FILE_PATH)) {
34
+ const data = JSON.parse(readFileSync(TOKEN_FILE_PATH, "utf-8"));
35
+ return {
36
+ token: data.token || undefined,
37
+ refreshToken: data.refreshToken || undefined,
38
+ };
39
+ }
40
+ }
41
+ catch {
42
+ // Ignore errors reading global token
43
+ }
44
+ return {};
45
+ }
46
+ function isTokenExpired(token) {
47
+ try {
48
+ const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
49
+ if (!payload.exp)
50
+ return true;
51
+ // Consider expired if less than 60s remaining
52
+ return payload.exp * 1000 < Date.now() + 60_000;
53
+ }
54
+ catch {
55
+ return true;
56
+ }
57
+ }
58
+ const globalTokens = loadGlobalToken();
59
+ let CEDA_API_TOKEN = process.env.CEDA_TOKEN || process.env.HERALD_API_TOKEN || globalTokens.token;
60
+ let CEDA_REFRESH_TOKEN = globalTokens.refreshToken;
61
+ // Mutex: serialize concurrent refresh attempts so only one network call fires
62
+ let refreshInFlight = null;
63
+ /**
64
+ * Refresh the access token using the stored refresh token.
65
+ * Updates in-memory token and writes back to ~/.herald/token.json.
66
+ * Returns true on success, false on failure.
67
+ * Concurrent calls coalesce into a single network request.
68
+ */
69
+ async function refreshAccessToken() {
70
+ if (refreshInFlight)
71
+ return refreshInFlight;
72
+ refreshInFlight = doRefreshAccessToken().finally(() => { refreshInFlight = null; });
73
+ return refreshInFlight;
74
+ }
75
+ async function doRefreshAccessToken() {
76
+ if (!CEDA_REFRESH_TOKEN)
77
+ return false;
78
+ try {
79
+ const controller = new AbortController();
80
+ const timeoutId = setTimeout(() => controller.abort(), 10_000);
81
+ const response = await fetch(`${CEDA_API_URL}/api/auth/refresh`, {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify({ refreshToken: CEDA_REFRESH_TOKEN }),
85
+ signal: controller.signal,
86
+ });
87
+ clearTimeout(timeoutId);
88
+ if (!response.ok) {
89
+ console.error(`[Herald] Token refresh failed: HTTP ${response.status}`);
90
+ return false;
91
+ }
92
+ const data = await response.json();
93
+ CEDA_API_TOKEN = data.accessToken;
94
+ CEDA_REFRESH_TOKEN = data.refreshToken;
95
+ // Write updated tokens back to disk
96
+ try {
97
+ if (existsSync(TOKEN_FILE_PATH)) {
98
+ const existing = JSON.parse(readFileSync(TOKEN_FILE_PATH, "utf-8"));
99
+ existing.token = data.accessToken;
100
+ existing.refreshToken = data.refreshToken;
101
+ // Update expiresAt from new token
102
+ try {
103
+ const payload = JSON.parse(Buffer.from(data.accessToken.split('.')[1], 'base64').toString());
104
+ existing.expiresAt = payload.exp
105
+ ? new Date(payload.exp * 1000).toISOString()
106
+ : new Date(Date.now() + data.expiresIn * 1000).toISOString();
107
+ }
108
+ catch {
109
+ existing.expiresAt = new Date(Date.now() + data.expiresIn * 1000).toISOString();
110
+ }
111
+ writeFileSync(TOKEN_FILE_PATH, JSON.stringify(existing, null, 2), "utf-8");
112
+ }
113
+ }
114
+ catch {
115
+ // Non-fatal: token works in memory even if disk write fails
116
+ }
117
+ console.error("[Herald] Token refreshed");
118
+ return true;
119
+ }
120
+ catch (error) {
121
+ console.error(`[Herald] Token refresh error: ${error}`);
122
+ return false;
123
+ }
124
+ }
30
125
  const CEDA_API_USER = process.env.HERALD_API_USER;
31
126
  const CEDA_API_PASS = process.env.HERALD_API_PASS;
32
127
  // CEDA-70: Zero-config context - everything auto-derived, nothing required
33
- // User is ALWAYS known (whoami). Company/project inferred from path as tags.
128
+ // User is ALWAYS known (whoami). Org/project inferred from path as tags.
34
129
  function deriveUser() {
35
130
  // Priority: git user > env var > OS user
36
131
  // Git user is trusted (immutable identity from git config)
@@ -729,8 +824,55 @@ async function callCedaAPI(endpoint, method = "GET", body) {
729
824
  signal: controller.signal,
730
825
  });
731
826
  clearTimeout(timeoutId);
827
+ if (response.status === 401 && CEDA_REFRESH_TOKEN) {
828
+ // Drain original response body to avoid leak
829
+ await response.text().catch(() => { });
830
+ // Attempt auto-refresh and retry once
831
+ const refreshed = await refreshAccessToken();
832
+ if (refreshed) {
833
+ const retryHeaders = { ...headers, Authorization: `Bearer ${CEDA_API_TOKEN}` };
834
+ const retryController = new AbortController();
835
+ const retryTimeoutId = setTimeout(() => retryController.abort(), CEDA_REQUEST_TIMEOUT_MS);
836
+ try {
837
+ const retryResponse = await fetch(url, {
838
+ method,
839
+ headers: retryHeaders,
840
+ body: enrichedBody ? JSON.stringify(enrichedBody) : undefined,
841
+ signal: retryController.signal,
842
+ });
843
+ clearTimeout(retryTimeoutId);
844
+ if (!retryResponse.ok) {
845
+ try {
846
+ const errorBody = await retryResponse.json();
847
+ return { success: false, error: `HTTP ${retryResponse.status}: ${errorBody.message || errorBody.error || retryResponse.statusText}`, details: errorBody };
848
+ }
849
+ catch {
850
+ return { success: false, error: `HTTP ${retryResponse.status}: ${retryResponse.statusText}` };
851
+ }
852
+ }
853
+ return await retryResponse.json();
854
+ }
855
+ catch (retryError) {
856
+ clearTimeout(retryTimeoutId);
857
+ return { success: false, error: `Retry after refresh failed: ${retryError}` };
858
+ }
859
+ }
860
+ // Refresh failed — fall through with a synthetic 401 error
861
+ return { success: false, error: `HTTP 401: Unauthorized (token refresh failed)` };
862
+ }
732
863
  if (!response.ok) {
733
- return { success: false, error: `HTTP ${response.status}: ${response.statusText}` };
864
+ // CEDA-FIX: Include response body for better error messages
865
+ try {
866
+ const errorBody = await response.json();
867
+ return {
868
+ success: false,
869
+ error: `HTTP ${response.status}: ${errorBody.message || errorBody.error || response.statusText}`,
870
+ details: errorBody
871
+ };
872
+ }
873
+ catch {
874
+ return { success: false, error: `HTTP ${response.status}: ${response.statusText}` };
875
+ }
734
876
  }
735
877
  return await response.json();
736
878
  }
@@ -953,7 +1095,7 @@ const tools = [
953
1095
  },
954
1096
  {
955
1097
  name: "herald_context",
956
- description: `Get or refresh Herald's context (company/project/user).
1098
+ description: `Get or refresh Herald's context (org/project/user).
957
1099
 
958
1100
  Context is derived from git (trusted) or path (fallback).
959
1101
  Use refresh=true after cloning a repo or changing directories to update context.
@@ -1118,7 +1260,7 @@ Example flow:
1118
1260
  // CEDA-49: Session Management Tools
1119
1261
  {
1120
1262
  name: "herald_session_list",
1121
- description: "List sessions for a company with optional filters. Returns session summaries including id, status, created/updated timestamps.",
1263
+ description: "List sessions for a org with optional filters. Returns session summaries including id, status, created/updated timestamps.",
1122
1264
  inputSchema: {
1123
1265
  type: "object",
1124
1266
  properties: {
@@ -1235,6 +1377,52 @@ Shows what would be redacted and final transmitted text.`,
1235
1377
  required: ["session", "feeling", "insight"],
1236
1378
  },
1237
1379
  },
1380
+ // Capture raw observation (Level 0) - feeds into graduation pipeline
1381
+ {
1382
+ name: "herald_observe",
1383
+ description: `Capture a raw observation (Level 0) for later pattern graduation.
1384
+
1385
+ Unlike herald_reflect (Level 1, pre-validated), observations are raw signals that
1386
+ feed into CEDA's clustering pipeline. When enough similar observations accumulate,
1387
+ they auto-graduate into patterns.
1388
+
1389
+ WHEN TO USE:
1390
+ - You notice something interesting but aren't sure if it's a pattern yet
1391
+ - Recording a data point without committing to "pattern" or "antipattern"
1392
+ - Logging what happened for later analysis
1393
+ - "That's interesting..." moments
1394
+
1395
+ WHEN TO USE herald_reflect INSTEAD:
1396
+ - You're confident it IS a pattern or antipattern
1397
+ - User explicitly says "capture this" / "log this pattern"
1398
+ - Clear success or failure signal
1399
+
1400
+ Example flow:
1401
+ 1. User tries a new approach → herald_observe (raw signal)
1402
+ 2. Same approach works 3x → CEDA auto-graduates to pattern
1403
+ 3. User confirms it's reliable → herald_reflect (validated)
1404
+
1405
+ TRIGGER WORDS: "observe", "note this", "interesting", "log observation", "raw signal"`,
1406
+ inputSchema: {
1407
+ type: "object",
1408
+ properties: {
1409
+ input: {
1410
+ type: "string",
1411
+ description: "What was observed — the raw signal or event"
1412
+ },
1413
+ outcome: {
1414
+ type: "string",
1415
+ enum: ["accepted", "modified", "rejected"],
1416
+ description: "How it turned out: accepted (worked), modified (worked with changes), rejected (didn't work)"
1417
+ },
1418
+ feedback: {
1419
+ type: "string",
1420
+ description: "Optional context or notes about the observation"
1421
+ },
1422
+ },
1423
+ required: ["input", "outcome"],
1424
+ },
1425
+ },
1238
1426
  // Query learned patterns - Claude reads this to avoid repeating mistakes
1239
1427
  {
1240
1428
  name: "herald_patterns",
@@ -1359,8 +1547,8 @@ call with outcome="helped" to reinforce that pattern.`,
1359
1547
  description: `Share an insight with other Herald contexts using scope control.
1360
1548
 
1361
1549
  Scopes:
1362
- - "parent": Share with parent project/company (escalate learning)
1363
- - "siblings": Share with sibling projects in same company
1550
+ - "parent": Share with parent project/org (escalate learning)
1551
+ - "siblings": Share with sibling projects in same org
1364
1552
  - "all": Share globally across all contexts
1365
1553
 
1366
1554
  Use this to propagate valuable patterns beyond the current context.
@@ -1400,7 +1588,7 @@ Use this when:
1400
1588
  Parameters:
1401
1589
  - pattern_id: Delete a specific pattern by ID
1402
1590
  - session_id: Delete all patterns from a session
1403
- - all: Delete ALL patterns for current context (company/project/user)
1591
+ - all: Delete ALL patterns for current context (org/project/user)
1404
1592
 
1405
1593
  WARNING: This action is irreversible. Data will be permanently deleted.`,
1406
1594
  inputSchema: {
@@ -1432,7 +1620,7 @@ Use this when:
1432
1620
  - Migrating data between systems
1433
1621
  - Compliance audit requires data export
1434
1622
 
1435
- Returns all patterns for the current context (company/project/user) in the specified format.`,
1623
+ Returns all patterns for the current context (org/project/user) in the specified format.`,
1436
1624
  inputSchema: {
1437
1625
  type: "object",
1438
1626
  properties: {
@@ -1456,11 +1644,11 @@ async function fetchPatternsWithCascade() {
1456
1644
  const seenInsights = new Set();
1457
1645
  const patterns = [];
1458
1646
  const antipatterns = [];
1459
- // CEDA-102: Use company param for backwards compat with deployed CEDA server
1647
+ // CEDA-102: Use org param for backwards compat with deployed CEDA server
1460
1648
  const queries = [
1461
- { scope: "user", url: `/api/herald/reflections?company=${HERALD_ORG}&project=${HERALD_PROJECT}&user=${HERALD_USER}&limit=100` },
1462
- { scope: "project", url: `/api/herald/reflections?company=${HERALD_ORG}&project=${HERALD_PROJECT}&limit=100` },
1463
- { scope: "org", url: `/api/herald/reflections?company=${HERALD_ORG}&limit=100` },
1649
+ { scope: "user", url: `/api/herald/reflections?org=${HERALD_ORG}&project=${HERALD_PROJECT}&user=${HERALD_USER}&limit=100` },
1650
+ { scope: "project", url: `/api/herald/reflections?org=${HERALD_ORG}&project=${HERALD_PROJECT}&limit=100` },
1651
+ { scope: "org", url: `/api/herald/reflections?org=${HERALD_ORG}&limit=100` },
1464
1652
  ];
1465
1653
  for (const { scope, url } of queries) {
1466
1654
  try {
@@ -1499,7 +1687,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
1499
1687
  {
1500
1688
  uri: "herald://context",
1501
1689
  name: "Herald Context",
1502
- description: "Current Herald context configuration (company/project/user)",
1690
+ description: "Current Herald context configuration (org/project/user)",
1503
1691
  mimeType: "application/json",
1504
1692
  },
1505
1693
  ];
@@ -1582,7 +1770,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1582
1770
  Welcome to Herald - your AI-native interface to CEDA (Cognitive Event-Driven Architecture).
1583
1771
 
1584
1772
  ## Current Context
1585
- - Company: ${HERALD_ORG}
1773
+ - Org: ${HERALD_ORG}
1586
1774
  - Project: ${HERALD_PROJECT}
1587
1775
  - User: ${HERALD_USER}
1588
1776
 
@@ -1603,7 +1791,7 @@ Welcome to Herald - your AI-native interface to CEDA (Cognitive Event-Driven Arc
1603
1791
  - \`herald_session\` - View session history (legacy)
1604
1792
 
1605
1793
  **Session Management (CEDA-49):**
1606
- - \`herald_session_list\` - List sessions with filters (company, project, user, status)
1794
+ - \`herald_session_list\` - List sessions with filters (org, project, user, status)
1607
1795
  - \`herald_session_get\` - Get detailed session info including prediction state
1608
1796
  - \`herald_session_history\` - View version history for a session
1609
1797
  - \`herald_session_rollback\` - Restore a session to a previous version
@@ -1960,6 +2148,7 @@ Herald will:
1960
2148
  }
1961
2149
  const synced = [];
1962
2150
  const failed = [];
2151
+ const errors = []; // CEDA-FIX: Track error details
1963
2152
  for (const item of buffer) {
1964
2153
  try {
1965
2154
  let result;
@@ -1987,13 +2176,23 @@ Herald will:
1987
2176
  }
1988
2177
  if (result.error) {
1989
2178
  failed.push(item);
2179
+ // CEDA-FIX: Capture error details
2180
+ const details = result.details;
2181
+ const errorMsg = String(details?.message || result.error);
2182
+ if (!errors.includes(errorMsg)) {
2183
+ errors.push(errorMsg);
2184
+ }
1990
2185
  }
1991
2186
  else {
1992
2187
  synced.push(item);
1993
2188
  }
1994
2189
  }
1995
- catch {
2190
+ catch (err) {
1996
2191
  failed.push(item);
2192
+ const errorMsg = err instanceof Error ? err.message : String(err);
2193
+ if (!errors.includes(errorMsg)) {
2194
+ errors.push(errorMsg);
2195
+ }
1997
2196
  }
1998
2197
  }
1999
2198
  // Save only failed items back to buffer
@@ -2007,6 +2206,8 @@ Herald will:
2007
2206
  synced: synced.length,
2008
2207
  failed: failed.length,
2009
2208
  remainingBuffer: failed.length,
2209
+ // CEDA-FIX: Include error details so users know WHY sync failed
2210
+ ...(errors.length > 0 && { errors }),
2010
2211
  }, null, 2)
2011
2212
  }],
2012
2213
  };
@@ -2029,13 +2230,13 @@ Herald will:
2029
2230
  }
2030
2231
  // CEDA-49: Session Management Tools
2031
2232
  case "herald_session_list": {
2032
- const company = args?.company;
2233
+ const org = args?.org;
2033
2234
  const project = args?.project;
2034
2235
  const user = args?.user;
2035
2236
  const status = args?.status;
2036
2237
  const limit = args?.limit;
2037
2238
  const params = new URLSearchParams();
2038
- params.set("company", company || HERALD_ORG);
2239
+ params.set("org", org || HERALD_ORG);
2039
2240
  if (project)
2040
2241
  params.set("project", project);
2041
2242
  if (user)
@@ -2249,8 +2450,120 @@ Herald will:
2249
2450
  };
2250
2451
  }
2251
2452
  }
2453
+ case "herald_observe": {
2454
+ const input = args?.input;
2455
+ const outcome = args?.outcome;
2456
+ const feedback = args?.feedback;
2457
+ if (!input || !outcome) {
2458
+ return {
2459
+ content: [{ type: "text", text: JSON.stringify({ error: "Missing required fields: input and outcome" }) }],
2460
+ isError: true,
2461
+ };
2462
+ }
2463
+ // Sanitize before transmission
2464
+ const inputPreview = previewSanitization(input);
2465
+ const feedbackPreview = feedback ? previewSanitization(feedback) : null;
2466
+ if (inputPreview.wouldBlock || feedbackPreview?.wouldBlock) {
2467
+ return {
2468
+ content: [{
2469
+ type: "text",
2470
+ text: JSON.stringify({
2471
+ success: false,
2472
+ error: "Content contains restricted data that cannot be transmitted",
2473
+ blockReason: inputPreview.blockReason || feedbackPreview?.blockReason,
2474
+ hint: "Remove private keys, credentials, or other restricted data before observing.",
2475
+ }, null, 2)
2476
+ }],
2477
+ isError: true,
2478
+ };
2479
+ }
2480
+ try {
2481
+ const result = await callCedaAPI("/api/observations", "POST", {
2482
+ input: inputPreview.sanitized,
2483
+ outcome,
2484
+ feedback: feedbackPreview?.sanitized,
2485
+ source: "herald",
2486
+ org: HERALD_ORG,
2487
+ project: HERALD_PROJECT,
2488
+ user: HERALD_USER,
2489
+ });
2490
+ if (result.error) {
2491
+ // Buffer locally if cloud unavailable
2492
+ bufferInsight({
2493
+ insight: inputPreview.sanitized,
2494
+ session: `observation: ${outcome}`,
2495
+ feeling: outcome === "rejected" ? "stuck" : "success",
2496
+ method: "observe",
2497
+ type: "observation",
2498
+ topic: "observation",
2499
+ org: HERALD_ORG,
2500
+ project: HERALD_PROJECT,
2501
+ user: HERALD_USER,
2502
+ });
2503
+ return {
2504
+ content: [{
2505
+ type: "text",
2506
+ text: JSON.stringify({
2507
+ success: true,
2508
+ mode: "local",
2509
+ message: "Observation buffered locally (cloud unavailable)",
2510
+ level: 0,
2511
+ hint: "Use herald_sync to flush buffer when cloud recovers.",
2512
+ buffered: true,
2513
+ }, null, 2)
2514
+ }],
2515
+ };
2516
+ }
2517
+ return {
2518
+ content: [{
2519
+ type: "text",
2520
+ text: JSON.stringify({
2521
+ success: true,
2522
+ mode: "cloud",
2523
+ level: 0,
2524
+ message: `Observation captured: "${input.substring(0, 80)}"`,
2525
+ outcome,
2526
+ observationId: result.observationId,
2527
+ context: {
2528
+ org: HERALD_ORG,
2529
+ project: HERALD_PROJECT,
2530
+ },
2531
+ graduation: "Will auto-graduate to pattern when similar observations cluster (3+ needed)",
2532
+ ...result,
2533
+ }, null, 2)
2534
+ }],
2535
+ };
2536
+ }
2537
+ catch {
2538
+ // Network error - buffer locally
2539
+ bufferInsight({
2540
+ insight: inputPreview.sanitized,
2541
+ session: `observation: ${outcome}`,
2542
+ feeling: outcome === "rejected" ? "stuck" : "success",
2543
+ method: "observe",
2544
+ type: "observation",
2545
+ topic: "observation",
2546
+ org: HERALD_ORG,
2547
+ project: HERALD_PROJECT,
2548
+ user: HERALD_USER,
2549
+ });
2550
+ return {
2551
+ content: [{
2552
+ type: "text",
2553
+ text: JSON.stringify({
2554
+ success: true,
2555
+ mode: "local",
2556
+ message: "Observation buffered locally (network error)",
2557
+ level: 0,
2558
+ hint: "Use herald_sync when cloud recovers.",
2559
+ buffered: true,
2560
+ }, null, 2)
2561
+ }],
2562
+ };
2563
+ }
2564
+ }
2252
2565
  case "herald_patterns": {
2253
- // Query learned patterns with inheritance: user → project → company
2566
+ // Query learned patterns with inheritance: user → project → org
2254
2567
  // More specific patterns take precedence over broader ones
2255
2568
  try {
2256
2569
  // Helper to dedupe patterns by insight text (first occurrence wins)
@@ -2264,17 +2577,15 @@ Herald will:
2264
2577
  return true;
2265
2578
  }).map(item => ({ ...item, scope }));
2266
2579
  };
2267
- // CEDA-95: Cascade queries - use company param for backwards compat with deployed server
2268
- // CEDA-102: User reflections now saved with level=1, so minLevel=1 is appropriate
2269
- // But we also query without minLevel to catch level=0 reflections from before the fix
2580
+ // Level-gated cascade: user sees own (any level), project sees level>=2, org sees level>=3
2270
2581
  const queries = [
2271
- { scope: "user", url: `/api/herald/reflections?company=${HERALD_ORG}&project=${HERALD_PROJECT}&user=${HERALD_USER}&limit=100` },
2272
- { scope: "project", url: `/api/herald/reflections?company=${HERALD_ORG}&project=${HERALD_PROJECT}&limit=100` },
2273
- { scope: "org", url: `/api/herald/reflections?company=${HERALD_ORG}&limit=100` },
2582
+ { scope: "user", url: `/api/herald/reflections?org=${HERALD_ORG}&project=${HERALD_PROJECT}&user=${HERALD_USER}&limit=50` },
2583
+ { scope: "project", url: `/api/herald/reflections?org=${HERALD_ORG}&project=${HERALD_PROJECT}&minLevel=2&limit=50` },
2584
+ { scope: "org", url: `/api/herald/reflections?org=${HERALD_ORG}&minLevel=3&limit=50` },
2274
2585
  ];
2275
2586
  const patterns = [];
2276
2587
  const antipatterns = [];
2277
- // Query each level, dedupe as we go (user patterns win over project, project over company)
2588
+ // Query each level, dedupe as we go (user patterns win over project, project over org)
2278
2589
  for (const { scope, url } of queries) {
2279
2590
  try {
2280
2591
  const result = await callCedaAPI(url);
@@ -2577,7 +2888,7 @@ Herald will:
2577
2888
  insight,
2578
2889
  scope,
2579
2890
  topic,
2580
- sourceCompany: HERALD_ORG,
2891
+ sourceOrg: HERALD_ORG,
2581
2892
  sourceProject: HERALD_PROJECT,
2582
2893
  sourceUser: HERALD_USER,
2583
2894
  sourceVault: HERALD_VAULT || undefined,
@@ -2595,7 +2906,7 @@ Herald will:
2595
2906
  };
2596
2907
  }
2597
2908
  const scopeDescription = {
2598
- parent: "parent project/company",
2909
+ parent: "parent project/org",
2599
2910
  siblings: "sibling projects",
2600
2911
  all: "all contexts globally",
2601
2912
  };
@@ -2837,7 +3148,7 @@ async function verifyWithCeda() {
2837
3148
  const ctx = result.context;
2838
3149
  VERIFIED_CONTEXT = {
2839
3150
  verified: true,
2840
- org: (ctx.org || ctx.company),
3151
+ org: (ctx.org || ctx.org),
2841
3152
  project: ctx.project,
2842
3153
  trust: ctx.trust,
2843
3154
  tags: ctx.tags,
@@ -2890,6 +3201,10 @@ async function sendStartupHeartbeat() {
2890
3201
  async function runMCP() {
2891
3202
  const transport = new StdioServerTransport();
2892
3203
  await server.connect(transport);
3204
+ // Auto-refresh expired access token before verification
3205
+ if (CEDA_API_TOKEN && isTokenExpired(CEDA_API_TOKEN) && CEDA_REFRESH_TOKEN) {
3206
+ await refreshAccessToken();
3207
+ }
2893
3208
  // CEDA-82: Verify trust with CEDA server before announcing
2894
3209
  // This may upgrade trust if user is registered
2895
3210
  await verifyWithCeda();