@theronap/cortex-mcp 0.4.2 → 0.4.3

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.
@@ -16,7 +16,7 @@
16
16
  * Get your token from the Cortex console → Connect your AI.
17
17
  */
18
18
 
19
- const VERSION = '0.4.2'
19
+ const VERSION = '0.4.3'
20
20
  const cmd = process.argv[2]
21
21
  const rest = process.argv.slice(3)
22
22
 
@@ -51,7 +51,7 @@ if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
51
51
  // commands don't fall through into the server.
52
52
  if (cmd === 'setup') {
53
53
  const { runSetup } = await import('../lib/setup.mjs')
54
- await runSetup(rest)
54
+ await runSetup(rest, VERSION)
55
55
  const { closeFetch } = await import('../lib/diagnose.mjs')
56
56
  await closeFetch()
57
57
  } else if (cmd === 'doctor') {
package/lib/capture.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFileSync } from 'fs'
2
- import { fetchCortex, classify } from './diagnose.mjs'
2
+ import { fetchCortex, classify, resolveBase } from './diagnose.mjs'
3
3
 
4
4
  // Claude Code Stop hook → POSTs a session digest to Cortex cloud, which
5
5
  // summarizes server-side and upserts ONE record per session. Node-native
@@ -32,7 +32,7 @@ function transcriptTail(path) {
32
32
  export async function runCapture() {
33
33
  const token = process.env.CORTEX_TOKEN
34
34
  if (!token) { process.stderr.write('cortex: CORTEX_TOKEN not set, skipping\n'); return }
35
- const base = (process.env.CORTEX_URL ?? 'https://cortex-console.vercel.app').replace(/\/$/, '')
35
+ const base = resolveBase(process.env.CORTEX_URL)
36
36
 
37
37
  let hook = {}
38
38
  try { hook = JSON.parse(readStdin()) } catch { /* no/invalid stdin */ }
package/lib/diagnose.mjs CHANGED
@@ -12,6 +12,30 @@
12
12
  export const isUuid = (s) =>
13
13
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s ?? '')
14
14
 
15
+ // The production alias — exempt from Vercel Deployment Protection.
16
+ export const CANONICAL_BASE = 'https://cortex-console.vercel.app'
17
+
18
+ // Resolve the API base from CORTEX_URL. A Vercel *deployment* URL
19
+ // (cortex-console-<hash>-<team>.vercel.app, or any non-canonical *.vercel.app) is guarded by
20
+ // Deployment Protection and returns an HTML auth page to programmatic clients — which looks
21
+ // exactly like an "infra block" (403/401, non-JSON). The production alias is exempt. So if
22
+ // CORTEX_URL points at a deployment URL, we fall back to the alias (and warn on stderr). A real
23
+ // custom domain (not *.vercel.app) is respected as-is. This makes the client robust to a stale
24
+ // CORTEX_URL pointing at a protected preview/deployment — the root cause of the Windows 403.
25
+ export function resolveBase(rawUrl) {
26
+ const raw = (rawUrl ?? '').trim()
27
+ if (!raw) return CANONICAL_BASE
28
+ let host
29
+ try { host = new URL(raw).host } catch { return CANONICAL_BASE }
30
+ if (host.endsWith('.vercel.app') && host !== 'cortex-console.vercel.app') {
31
+ process.stderr.write(
32
+ `cortex: CORTEX_URL (${raw}) is a protected Vercel deployment URL; using ${CANONICAL_BASE} instead.\n`,
33
+ )
34
+ return CANONICAL_BASE
35
+ }
36
+ return raw.replace(/\/$/, '')
37
+ }
38
+
15
39
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
16
40
 
17
41
  // A non-OK response → an actionable diagnosis: { kind, retriable, message }.
package/lib/doctor.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, existsSync } from 'fs'
2
2
  import { homedir } from 'os'
3
3
  import { join } from 'path'
4
- import { checkToken } from './diagnose.mjs'
4
+ import { checkToken, resolveBase } from './diagnose.mjs'
5
5
 
6
6
  // `npx @theronap/cortex-mcp doctor` — a live, one-command health check.
7
7
  //
@@ -25,7 +25,7 @@ function resolveToken() {
25
25
  }
26
26
 
27
27
  export async function runDoctor() {
28
- const base = (process.env.CORTEX_URL ?? 'https://cortex-console.vercel.app').replace(/\/$/, '')
28
+ const base = resolveBase(process.env.CORTEX_URL)
29
29
  const out = (m) => process.stdout.write(m + '\n')
30
30
 
31
31
  out('')
package/lib/server.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
3
  import { z } from 'zod'
4
- import { fetchCortex, classify } from './diagnose.mjs'
4
+ import { fetchCortex, classify, resolveBase } from './diagnose.mjs'
5
5
 
6
6
  // The Cortex MCP server (stdio). Serves the signed-in employee's scoped org
7
7
  // context to their AI assistant. CORTEX_TOKEN identifies the user + org.
8
8
 
9
9
  export async function runServer(version) {
10
10
  const TOKEN = process.env.CORTEX_TOKEN
11
- const BASE = (process.env.CORTEX_URL ?? 'https://cortex-console.vercel.app').replace(/\/$/, '')
11
+ const BASE = resolveBase(process.env.CORTEX_URL)
12
12
 
13
13
  if (!TOKEN) {
14
14
  process.stderr.write('cortex-mcp: CORTEX_TOKEN is required. Get yours from the Cortex console → Connect your AI.\n')
package/lib/setup.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'fs'
2
2
  import { homedir } from 'os'
3
3
  import { join, dirname } from 'path'
4
- import { checkToken } from './diagnose.mjs'
4
+ import { checkToken, resolveBase } from './diagnose.mjs'
5
5
 
6
6
  // One-command employee onboarding. Wires both:
7
7
  // 1. ~/.claude.json → the cortex MCP server (context-serving)
@@ -32,7 +32,7 @@ function ensureDir(path) {
32
32
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
33
33
  }
34
34
 
35
- export async function runSetup(argv) {
35
+ export async function runSetup(argv, version) {
36
36
  const token = argv[0]
37
37
  if (!token || token.startsWith('-')) {
38
38
  process.stderr.write(
@@ -41,7 +41,10 @@ export async function runSetup(argv) {
41
41
  )
42
42
  process.exit(1)
43
43
  }
44
- const base = process.env.CORTEX_URL ?? 'https://cortex-console.vercel.app'
44
+ // Pin the wired commands to the installed version so the config can't later run a stale
45
+ // cached build (npx may reuse a cached older version for an unpinned spec).
46
+ const spec = version ? `${PKG}@${version}` : PKG
47
+ const base = resolveBase(process.env.CORTEX_URL)
45
48
  const home = homedir()
46
49
  const claudeJson = join(home, '.claude.json')
47
50
  const settingsJson = join(home, '.claude', 'settings.json')
@@ -62,7 +65,7 @@ export async function runSetup(argv) {
62
65
  cfg.mcpServers.cortex = {
63
66
  type: 'stdio',
64
67
  command: 'npx',
65
- args: ['-y', PKG],
68
+ args: ['-y', spec],
66
69
  env: { CORTEX_TOKEN: token },
67
70
  }
68
71
  ensureDir(claudeJson)
@@ -84,7 +87,7 @@ export async function runSetup(argv) {
84
87
  s.hooks = s.hooks ?? {}
85
88
  s.hooks.Stop = Array.isArray(s.hooks.Stop) ? s.hooks.Stop : []
86
89
 
87
- const captureCmd = `CORTEX_TOKEN=${token} npx -y ${PKG} capture`
90
+ const captureCmd = `CORTEX_TOKEN=${token} npx -y ${spec} capture`
88
91
  // Remove any prior cortex capture hook (idempotent: drop old token / old path forms).
89
92
  for (const grp of s.hooks.Stop) {
90
93
  if (Array.isArray(grp.hooks)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Connect your AI assistant to Cortex — your org's projects, activity, gaps, and directives, scoped to you.",
5
5
  "type": "module",
6
6
  "bin": {