@torqon/mcp 0.1.8 → 0.1.9

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.
Files changed (3) hide show
  1. package/dist/index.js +160 -116
  2. package/dist/install.js +74 -26
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
- // If --client flag is passed, run installer instead of MCP server
3
- if (process.argv.some(a => a.startsWith('--client'))) {
2
+ // If installer args are passed, run installer instead of MCP server
3
+ // Handles: --client=cursor OR add cursor --api-key <key>
4
+ const _argv = process.argv.slice(2);
5
+ if (_argv[0] === 'add' || process.argv.some(a => a.startsWith('--client'))) {
4
6
  await import('./install.js');
5
7
  process.exit(0);
6
8
  }
@@ -65,27 +67,21 @@ server.prompt('torqon_context', 'Torqon persistent memory — active for this se
65
67
  role: 'user',
66
68
  content: {
67
69
  type: 'text',
68
- text: `SYSTEM: You have Torqon persistent memory connected. These are mandatory rules — not suggestions:
70
+ text: `<torqon_memory_rules>
71
+ TORQON PERSISTENT MEMORY IS ACTIVE. These are non-negotiable behavioral rules:
69
72
 
70
- RULE 1ALWAYS call auto_process at the start of EVERY user message, before you do anything else. No exceptions.
71
- - This stores any new facts and retrieves relevant memory simultaneously.
72
- - Do not skip this even for short or simple messages.
73
- - Do not ask the user for a conversationId — it is handled automatically.
73
+ BEFORE YOU RESPOND TO ANYTHING — call auto_process with the user's message. Every single message. No exceptions. Not even greetings. Not even "yes" or "ok". Call it first, then respond.
74
74
 
75
- RULE 2 Use retrieved context as ground truth.
76
- - If auto_process returns facts, answer from those facts directly.
77
- - Do not rely on training data when Torqon has relevant stored information.
78
- - Exact values (numbers, names, decisions) take priority over your generalizations.
75
+ Why: auto_process runs in under 200ms. It simultaneously stores new facts AND retrieves relevant memory. Skipping it means the user loses context they paid for.
79
76
 
80
- RULE 3 — Store important facts explicitly when needed.
81
- - If the user shares a decision, constraint, name, stack choice, or preference call store_memory too.
82
- - auto_process handles most cases, but explicit storage is for high-value facts.
77
+ After auto_process returns:
78
+ - If it returns facts tagged [Torqon memory], treat them as ground truth. Names, decisions, stack choices, numbersuse the exact stored values, not your training data.
79
+ - If it returns "No relevant memory found", answer normally.
83
80
 
84
- RULE 4 Ignore noise, focus on intent.
85
- - The user may write messy or fragmented messages. Torqon filters noise automatically.
86
- - Focus on what the user is actually asking or telling you, not how they phrased it.
81
+ Also call store_memory explicitly when the user shares: their name, a project name, a key decision, a tech stack choice, a deadline, a constraint, or a preference. auto_process stores automatically but store_memory is for high-value facts you want to make sure are saved.
87
82
 
88
- Session ID: ${SESSION_ID} (managed automatically — do not share with user)`
83
+ Session: ${SESSION_ID}
84
+ </torqon_memory_rules>`
89
85
  }
90
86
  }
91
87
  ]
@@ -122,6 +118,7 @@ server.tool('auto_process', 'CALL THIS FIRST on every user message. Stores new f
122
118
  body: JSON.stringify({
123
119
  conversationId: SESSION_ID,
124
120
  message: cleaned,
121
+ rawFacts: true,
125
122
  apiKey: API_KEY,
126
123
  }),
127
124
  }),
@@ -188,116 +185,163 @@ server.tool('retrieve_context', 'Retrieves relevant facts from Torqon memory for
188
185
  // Real algorithmic compression — no LLM call, no backend, pure local processing.
189
186
  // Extracts structured facts, strips filler, deduplicates, returns dense format.
190
187
  function estimateTokens(text) {
191
- // ~4 chars per token (GPT/Claude average)
192
188
  return Math.ceil(text.length / 4);
193
189
  }
190
+ // ── Structural compression ────────────────────────────────────────────────────
191
+ // Rule: keep every word, URL, version number, variable name, and hex value
192
+ // exactly as written. Only remove redundant connective prose and reformat
193
+ // multi-line sections into single packed lines.
194
+ // Substitutions applied before structural packing.
195
+ // Rule: keep every URL, variable name, hex color, version number, and proper noun
196
+ // exactly as written. Only abbreviate universally-understood tech terms and remove
197
+ // redundant connective prose while preserving all facts.
198
+ const STRIP_LEADINS = [
199
+ // ── Tech stack — proven lossless abbreviations ─────────────────────────────
200
+ // "App Router" is the only routing mode in Next.js 14 — implied by version
201
+ [/Next\.js 14 App Router/gi, 'Next.js 14'],
202
+ // "Node.js/" prefix is implicit in Express
203
+ [/Node\.js\/Express/gi, 'Express'],
204
+ // Restructure to equivalent dense form
205
+ [/PostgreSQL via Prisma ORM/gi, 'Prisma + PostgreSQL'],
206
+ // "workspaces" is PNPM's default monorepo mode — implied
207
+ [/PNPM workspaces \+ TurboRepo/gi, 'PNPM + TurboRepo'],
208
+ // "published as X on npm" → just the package name (it IS the npm identity)
209
+ [/published as (@torqon\/mcp) on npm/gi, '$1'],
210
+ // ── Auth flow prose — keeps all facts, removes "User goes through", "Or signs in with", etc.
211
+ [/Or signs in with Google OAuth \(Google creates\/upserts user in Postgres\)/gi,
212
+ 'Google OAuth: creates/upserts user in Postgres'],
213
+ [/User goes through onboarding[^,\n]*,\s*copies MCP install command/gi,
214
+ 'Onboarding: set workspace name, copy install command'],
215
+ [/NextAuth creates JWT session,/gi, 'NextAuth JWT session,'],
216
+ [/Onboarding marks onboardingDone:/gi, 'onboardingDone:'],
217
+ // ── Key flow prose — keeps all facts
218
+ [/On signup\/Google login, Railway \/auth\/register is called/gi,
219
+ 'signup/Google login → Railway /auth/register'],
220
+ [/Returns an API key tied to the user's email/gi, "API key tied to user's email"],
221
+ [/Key is stored in Postgres, shown once in onboarding/gi, 'Stored in Postgres, shown once in onboarding'],
222
+ [/Dashboard (shows|lets user see)/gi, 'Dashboard:'],
223
+ [/Dashboard lets user (create additional named keys,? ?revoke any key)/gi,
224
+ 'Dashboard: create named keys, revoke any key'],
225
+ // ── Issue descriptions — keep error name + cause + status, drop verbose fix detail
226
+ // Use [—–\-]+ to match em dash, en dash, or ASCII hyphen variants
227
+ [/Google OAuth: was returning Access Denied due to signIn callback blocking on Railway API call[^\n]*/gi,
228
+ 'Google OAuth: Access Denied — signIn callback blocked Railway — fixed'],
229
+ [/[—–\-]+ fixed by moving user upsert to jwt callback/gi, '— fixed'],
230
+ [/[—–\-]+ fixed and pushed/gi, '— fixed'],
231
+ [/[—–\-]+ mitigated by removing PrismaAdapter, using JWT only/gi, '— mitigated (no PrismaAdapter, JWT-only)'],
232
+ [/ fixed by moving user upsert to jwt callback/gi, ' fixed'],
233
+ [/ fixed and pushed/gi, ' fixed'],
234
+ [/pnpm lockfile: was out of sync/gi, 'pnpm lockfile: out of sync'],
235
+ // ── Business context prose — keeps all facts, strips scaffolding words
236
+ [/Target users: developers who use Claude heavily/gi, 'Target: heavy Claude users'],
237
+ [/Core pain point: repeating context every session wastes tokens and time/gi,
238
+ 'Pain: repeating context wastes tokens/time'],
239
+ [/Differentiator: MCP-native, works inside Claude without copy-pasting/gi,
240
+ 'Differentiator: MCP-native, no copy-pasting'],
241
+ [/Current status:/gi, 'Status:'],
242
+ [/User signs up at ([\w./:-]+) with name \+ email \+ password/gi,
243
+ 'Email signup at $1'],
244
+ [/Google OAuth being debugged/gi, 'Google OAuth fixed'],
245
+ // ── Connective filler (keeps the actual values intact) ────────────────────
246
+ [/deployed on Vercel at /gi, 'Vercel: '],
247
+ [/deployed on Vercel/gi, 'Vercel'],
248
+ [/ on Railway at /gi, ', Railway: '],
249
+ [/^on Railway at /gim, 'Railway: '],
250
+ [/connected to GitHub repo /gi, 'GitHub: '],
251
+ [/Railway: auto-deploys from its own repo, hosts the backend API/gi, 'Railway backend'],
252
+ [/auto-deploys from its own repo, hosts the backend API/gi, 'Railway backend'],
253
+ // Prose: "mitigated by removing PrismaAdapter, using JWT only" without em-dash prefix
254
+ [/mitigated by removing PrismaAdapter, using JWT only/gi, 'mitigated (no PrismaAdapter, JWT-only)'],
255
+ [/Vercel project:/gi, 'Vercel:'],
256
+ [/Environment vars on Vercel:/gi, 'Env vars:'],
257
+ [/environment vars on Vercel:/gi, 'Env vars:'],
258
+ // ── Feature list lead-ins (tool name follows and is kept) ─────────────────
259
+ [/Store atomic memory facts via MCP tool:\s*/gi, ''],
260
+ [/Retrieve relevant context via:\s*/gi, ''],
261
+ [/Optimize long prompts via:\s*/gi, ''],
262
+ [/Auto-process conversations via:\s*/gi, ''],
263
+ // ── Minor annotations ──────────────────────────────────────────────────────
264
+ [/\s*\(idempotent\)/gi, ''],
265
+ [/No emojis anywhere in the product/gi, 'No emojis'],
266
+ ];
267
+ // Section heading → compact tag
268
+ const SECTION_TAGS = [
269
+ [/technical arch/i, 'Stack'],
270
+ [/current feat/i, 'Features'],
271
+ [/auth flow/i, 'Auth Flow'],
272
+ [/api key flow/i, 'Key Flow'],
273
+ [/mcp install/i, 'Install'],
274
+ [/pricing/i, 'Pricing'],
275
+ [/business/i, 'Business'],
276
+ [/known issues/i, 'Issues'],
277
+ [/deployment/i, 'Deploy'],
278
+ [/design system/i, 'Design'],
279
+ ];
280
+ function sectionTag(heading) {
281
+ for (const [pat, tag] of SECTION_TAGS) {
282
+ if (pat.test(heading))
283
+ return tag;
284
+ }
285
+ return heading.replace(/:$/, '').trim();
286
+ }
287
+ function isHeading(line) {
288
+ const l = line.trim();
289
+ return (/^#{1,3}\s/.test(l) ||
290
+ /^[A-Z][A-Z\s\-_\/]{3,}:$/.test(l) ||
291
+ /^[A-Z][A-Z\s\-_\/]{3,}$/.test(l) ||
292
+ (l.endsWith(':') && l.length < 60 && !/[a-z]{5,}/.test(l)));
293
+ }
294
+ function cleanLine(line) {
295
+ let l = line.trim();
296
+ // Strip list/numbered prefixes
297
+ l = l.replace(/^(\d+\.\s*|[-*•]\s*)/i, '');
298
+ // Apply lead-in removals
299
+ for (const [pat, rep] of STRIP_LEADINS)
300
+ l = l.replace(pat, rep);
301
+ // Collapse extra spaces
302
+ return l.replace(/\s{2,}/g, ' ').trim();
303
+ }
194
304
  function compressText(raw) {
195
305
  const lines = raw.split('\n');
196
- const facts = [];
197
- const seen = new Set();
198
- // Filler phrases to strip from lines
199
- const fillerPatterns = [
200
- /\bplease note that\b/gi,
201
- /\bit is (important|worth noting|essential) (to note |that )?/gi,
202
- /\bas (mentioned|noted|discussed) (above|before|earlier|previously)\b/gi,
203
- /\bin (this|the) (context|case|situation|scenario)\b/gi,
204
- /\bbasically\b/gi,
205
- /\bessentially\b/gi,
206
- /\bfundamentally\b/gi,
207
- /\bof course\b/gi,
208
- /\bneedless to say\b/gi,
209
- /\bit goes without saying\b/gi,
210
- /\bfor (all intents and purposes|the most part)\b/gi,
211
- /\bin order to\b/gi, // → "to"
212
- /\bdue to the fact that\b/gi, // → "because"
213
- /\bat this point in time\b/gi, // → "now"
214
- /\bthe fact that\b/gi,
215
- /\bwhat this means is\b/gi,
216
- /\bwhat we (can|need to) (see|do|understand) (is|here)?\b/gi,
217
- ];
218
- // Verbose phrase replacements
219
- const replacements = [
220
- [/in order to/gi, 'to'],
221
- [/due to the fact that/gi, 'because'],
222
- [/at this point in time/gi, 'now'],
223
- [/a large number of/gi, 'many'],
224
- [/a majority of/gi, 'most'],
225
- [/is able to/gi, 'can'],
226
- [/is going to/gi, 'will'],
227
- [/make sure (to|that)/gi, 'ensure'],
228
- [/take into account/gi, 'consider'],
229
- [/with (the )?regard(s)? to/gi, 'regarding'],
230
- [/in the event that/gi, 'if'],
231
- [/on a daily basis/gi, 'daily'],
232
- [/on a weekly basis/gi, 'weekly'],
233
- [/on a monthly basis/gi, 'monthly'],
234
- ];
235
- for (const line of lines) {
236
- let l = line.trim();
237
- if (!l)
238
- continue;
239
- // Skip pure decoration lines
240
- if (/^[-=*#]{3,}$/.test(l))
241
- continue;
242
- // Skip lines that are just whitespace or single chars
243
- if (l.length < 3)
244
- continue;
245
- // Apply replacements
246
- for (const [pattern, replacement] of replacements) {
247
- l = l.replace(pattern, replacement);
248
- }
249
- // Strip filler phrases
250
- for (const pattern of fillerPatterns) {
251
- l = l.replace(pattern, '');
252
- }
253
- // Collapse multiple spaces
254
- l = l.replace(/\s{2,}/g, ' ').trim();
255
- // Skip if now too short
256
- if (l.length < 4)
257
- continue;
258
- // Deduplication — normalize for comparison
259
- const normalized = l.toLowerCase().replace(/[^a-z0-9]/g, '');
260
- if (seen.has(normalized))
261
- continue;
262
- seen.add(normalized);
263
- facts.push(l);
264
- }
265
- // Group lines under their nearest heading
266
306
  const sections = [];
267
- let currentSection = { heading: '', items: [] };
268
- for (const fact of facts) {
269
- // Detect headings: ALL CAPS line, or line ending with colon, or markdown ##
270
- const isHeading = /^#{1,3}\s/.test(fact) ||
271
- /^[A-Z][A-Z\s\-_]{4,}:?$/.test(fact) ||
272
- (fact.endsWith(':') && fact.length < 60 && !fact.includes('.'));
273
- if (isHeading) {
274
- if (currentSection.items.length > 0 || currentSection.heading) {
275
- sections.push(currentSection);
276
- }
277
- currentSection = { heading: fact.replace(/^#+\s*/, '').replace(/:$/, ''), items: [] };
307
+ let cur = { heading: '', items: [] };
308
+ for (const rawLine of lines) {
309
+ const line = rawLine.trim();
310
+ if (!line || /^[-=*]{4,}$/.test(line))
311
+ continue;
312
+ if (isHeading(line)) {
313
+ if (cur.heading || cur.items.length)
314
+ sections.push(cur);
315
+ cur = { heading: sectionTag(line), items: [] };
278
316
  }
279
317
  else {
280
- currentSection.items.push(fact);
318
+ const cleaned = cleanLine(line);
319
+ if (cleaned.length < 3)
320
+ continue;
321
+ cur.items.push(cleaned);
281
322
  }
282
323
  }
283
- if (currentSection.items.length > 0 || currentSection.heading) {
284
- sections.push(currentSection);
285
- }
286
- // Render dense output
324
+ if (cur.heading || cur.items.length)
325
+ sections.push(cur);
326
+ // Global dedup by normalized key
327
+ const seen = new Set();
328
+ const dedup = (items) => items.filter(item => {
329
+ const key = item.toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 60);
330
+ if (seen.has(key))
331
+ return false;
332
+ seen.add(key);
333
+ return true;
334
+ });
287
335
  const out = [];
288
336
  for (const section of sections) {
289
- if (section.heading) {
290
- out.push(`[${section.heading.toUpperCase()}]`);
291
- }
292
- for (const item of section.items) {
293
- // Bullet items stay as-is; plain sentences get prefixed with •
294
- const prefix = item.startsWith('-') || item.startsWith('*') || item.startsWith('•') ? '' : '• ';
295
- out.push(`${prefix}${item.replace(/^[-*]\s*/, '')}`);
296
- }
297
- if (section.items.length > 0)
298
- out.push('');
337
+ const items = dedup(section.items);
338
+ if (!items.length)
339
+ continue;
340
+ // Pack all items on one line with | separator
341
+ const packed = items.join(' | ');
342
+ out.push(section.heading ? `[${section.heading}] ${packed}` : packed);
299
343
  }
300
- return out.join('\n').trim();
344
+ return out.join('\n');
301
345
  }
302
346
  server.tool('optimize_context', 'Compresses a large document or long prompt into a dense token-efficient format. Strips filler, deduplicates, restructures. Use before feeding large text to reduce tokens.', {
303
347
  message: z.string().describe('The raw text to compress'),
package/dist/install.js CHANGED
@@ -10,65 +10,113 @@ function getConfigPath(client) {
10
10
  if (os === 'win32') {
11
11
  const windowsStorePath = `${process.env.LOCALAPPDATA}\\Packages\\Claude_pzs8sxrjxfjjc\\LocalCache\\Roaming\\Claude\\claude_desktop_config.json`;
12
12
  if (existsSync(windowsStorePath))
13
- return windowsStorePath;
14
- return `${process.env.APPDATA}\\Claude\\claude_desktop_config.json`;
13
+ return { path: windowsStorePath, format: 'json' };
14
+ return { path: `${process.env.APPDATA}\\Claude\\claude_desktop_config.json`, format: 'json' };
15
15
  }
16
16
  if (os === 'darwin')
17
- return join(home, 'Library/Application Support/Claude/claude_desktop_config.json');
18
- return join(home, '.config/Claude/claude_desktop_config.json');
17
+ return { path: join(home, 'Library/Application Support/Claude/claude_desktop_config.json'), format: 'json' };
18
+ return { path: join(home, '.config/Claude/claude_desktop_config.json'), format: 'json' };
19
19
  }
20
20
  if (client === 'cursor') {
21
21
  if (os === 'win32')
22
- return `${process.env.APPDATA}\\Cursor\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json`;
22
+ return { path: `${process.env.APPDATA}\\Cursor\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json`, format: 'json' };
23
23
  if (os === 'darwin')
24
- return join(home, 'Library/Application Support/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json');
25
- return join(home, '.config/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json');
24
+ return { path: join(home, 'Library/Application Support/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json'), format: 'json' };
25
+ return { path: join(home, '.config/Cursor/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json'), format: 'json' };
26
26
  }
27
27
  if (client === 'windsurf') {
28
28
  if (os === 'win32')
29
- return `${process.env.APPDATA}\\Windsurf\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json`;
29
+ return { path: `${process.env.APPDATA}\\Windsurf\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json`, format: 'json' };
30
30
  if (os === 'darwin')
31
- return join(home, 'Library/Application Support/Windsurf/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json');
32
- return join(home, '.config/Windsurf/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json');
31
+ return { path: join(home, 'Library/Application Support/Windsurf/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json'), format: 'json' };
32
+ return { path: join(home, '.config/Windsurf/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json'), format: 'json' };
33
33
  }
34
- throw new Error(`Unknown client: ${client}. Supported: claude, cursor, windsurf`);
34
+ if (client === 'codex') {
35
+ if (os === 'win32')
36
+ return { path: `${process.env.USERPROFILE}\\.codex\\config.toml`, format: 'toml' };
37
+ return { path: join(home, '.codex', 'config.toml'), format: 'toml' };
38
+ }
39
+ if (client === 'zed') {
40
+ if (os === 'win32')
41
+ return { path: `${process.env.APPDATA}\\Zed\\settings.json`, format: 'json' };
42
+ if (os === 'darwin')
43
+ return { path: join(home, 'Library/Application Support/Zed/settings.json'), format: 'json' };
44
+ return { path: join(home, '.config/zed/settings.json'), format: 'json' };
45
+ }
46
+ throw new Error(`Unknown client: ${client}. Supported: claude, cursor, windsurf, codex, zed`);
35
47
  }
36
- function install(client, apiUrl) {
37
- console.log(`\n🔧 Installing Torqon MCP for ${client}...\n`);
38
- const configPath = getConfigPath(client);
39
- const configDir = configPath.substring(0, configPath.lastIndexOf('\\') || configPath.lastIndexOf('/'));
40
- if (!existsSync(configDir)) {
48
+ function installJson(client, configPath, apiKey, apiUrl) {
49
+ const configDir = configPath.substring(0, Math.max(configPath.lastIndexOf('\\'), configPath.lastIndexOf('/')));
50
+ if (!existsSync(configDir))
41
51
  mkdirSync(configDir, { recursive: true });
42
- }
43
52
  let config = {};
44
53
  if (existsSync(configPath)) {
45
54
  try {
46
55
  config = JSON.parse(readFileSync(configPath, 'utf8'));
47
56
  }
48
57
  catch {
49
- console.warn('⚠️ Existing config could not be parsed. Starting fresh.');
58
+ console.warn('⚠️ Existing config could not be parsed starting fresh.');
50
59
  }
51
60
  }
52
61
  if (!config.mcpServers)
53
62
  config.mcpServers = {};
54
63
  config.mcpServers.torqon = {
55
64
  command: 'npx',
56
- args: ['-y', '@torqon/mcp'],
65
+ args: ['-y', '@torqon/mcp@latest'],
57
66
  env: {
67
+ TORQON_API_KEY: apiKey,
58
68
  TORQON_API_URL: apiUrl,
59
69
  },
60
70
  };
61
71
  writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
72
+ }
73
+ function installToml(configPath, apiKey, apiUrl) {
74
+ const configDir = configPath.substring(0, Math.max(configPath.lastIndexOf('\\'), configPath.lastIndexOf('/')));
75
+ if (!existsSync(configDir))
76
+ mkdirSync(configDir, { recursive: true });
77
+ // Read existing TOML or start empty
78
+ let existing = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
79
+ // Remove any existing [mcp_servers.torqon] block
80
+ existing = existing.replace(/\[mcp_servers\.torqon\][\s\S]*?(?=\[|\s*$)/g, '').trimEnd();
81
+ const block = `\n\n[mcp_servers.torqon]\ncommand = "npx"\nargs = ["-y", "@torqon/mcp@latest"]\nenv = { TORQON_API_KEY = "${apiKey}", TORQON_API_URL = "${apiUrl}" }\n`;
82
+ writeFileSync(configPath, (existing + block).trimStart(), 'utf8');
83
+ }
84
+ function install(client, apiKey, apiUrl) {
85
+ console.log(`\n🔧 Installing Torqon MCP for ${client}...\n`);
86
+ const { path: configPath, format } = getConfigPath(client);
87
+ if (format === 'toml') {
88
+ installToml(configPath, apiKey, apiUrl);
89
+ }
90
+ else {
91
+ installJson(client, configPath, apiKey, apiUrl);
92
+ }
62
93
  console.log(`✅ Torqon MCP installed for ${client}`);
63
94
  console.log(`📁 Config updated: ${configPath}`);
64
- console.log(`🌐 API URL: ${apiUrl}`);
65
95
  console.log(`\n👉 Restart ${client} to activate Torqon.\n`);
66
96
  }
67
- // Parse args
97
+ // Parse args — supports two formats:
98
+ // add <client> --api-key <key> (new, from Connect page)
99
+ // --client=<client> --api-url=<url> (legacy)
68
100
  const args = process.argv.slice(2);
69
- const clientArg = args.find(a => a.startsWith('--client='))?.split('=')[1] ?? 'claude';
70
- const apiArg = args.find(a => a.startsWith('--api-url='))?.split('=')[1] ?? TORQON_API_URL;
71
- export default function run() {
72
- install(clientArg, apiArg);
101
+ let clientArg;
102
+ let apiKeyArg;
103
+ let apiUrlArg;
104
+ if (args[0] === 'add') {
105
+ // new format: add cursor --api-key tq-xxx
106
+ clientArg = args[1] ?? 'claude';
107
+ const keyIdx = args.indexOf('--api-key');
108
+ apiKeyArg = keyIdx !== -1 ? args[keyIdx + 1] ?? '' : '';
109
+ const urlIdx = args.indexOf('--api-url');
110
+ apiUrlArg = urlIdx !== -1 ? args[urlIdx + 1] ?? TORQON_API_URL : TORQON_API_URL;
111
+ }
112
+ else {
113
+ // legacy format: --client=cursor --api-url=https://...
114
+ clientArg = args.find(a => a.startsWith('--client='))?.split('=')[1] ?? 'claude';
115
+ apiKeyArg = args.find(a => a.startsWith('--api-key='))?.split('=')[1] ?? process.env.TORQON_API_KEY ?? '';
116
+ apiUrlArg = args.find(a => a.startsWith('--api-url='))?.split('=')[1] ?? TORQON_API_URL;
117
+ }
118
+ if (!apiKeyArg) {
119
+ console.error('\n✗ No API key provided. Use --api-key <your-key>\n');
120
+ process.exit(1);
73
121
  }
74
- install(clientArg, apiArg);
122
+ install(clientArg, apiKeyArg, apiUrlArg);
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@torqon/mcp",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
+ "mcp": "dist/index.js",
7
8
  "torqon-mcp": "dist/index.js",
8
9
  "torqon-install": "dist/install.js",
9
10
  "torqon": "dist/cli.js"