@askthew/mcp-plugin 0.4.7 → 0.4.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.
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { spawn } from "node:child_process";
3
4
  import fs from "node:fs";
5
+ import { createRequire } from "node:module";
4
6
  import path from "node:path";
5
7
  import { fileURLToPath } from "node:url";
6
8
  import { z } from "zod";
@@ -9,9 +11,18 @@ import { resolveMcpMode } from "./lib/free-tier-policy.js";
9
11
  import { LocalStore } from "./lib/local-store.js";
10
12
  import { buildTelemetryPayload, flushTelemetryOutbox } from "./lib/telemetry.js";
11
13
  import { ensureLocalIdentity } from "./lib/local-identity.js";
12
- import { buildLocalTimeline, buildTimelineInsights as buildLocalTimelineInsights, renderTimelineMarkdown as renderLocalTimelineMarkdown } from "./lib/timeline-insights.js";
13
14
  import { paidDescription, paidFeatureNudge, toolJson } from "./lib/upgrade-nudge.js";
14
15
  import { configPath, readJsonFile } from "./lib/paths.js";
16
+ const requirePackageJson = createRequire(import.meta.url);
17
+ function packageVersion() {
18
+ try {
19
+ const manifest = requirePackageJson("../package.json");
20
+ return typeof manifest.version === "string" ? manifest.version : "unknown";
21
+ }
22
+ catch {
23
+ return "unknown";
24
+ }
25
+ }
15
26
  const evidenceRoleSchema = z.enum(["user", "assistant", "system"]);
16
27
  const evidenceEntrySchema = z.object({
17
28
  role: evidenceRoleSchema,
@@ -569,7 +580,7 @@ export function createAskTheWMcpServer(options = {}) {
569
580
  }
570
581
  const server = new McpServer({
571
582
  name: "Ask The W Coding Agent Connector",
572
- version: "0.4.7",
583
+ version: packageVersion(),
573
584
  });
574
585
  if (options.sendStartupHeartbeat !== false && mode.mode === "paid") {
575
586
  void sendStartupSignals(options);
@@ -655,6 +666,89 @@ export function createAskTheWMcpServer(options = {}) {
655
666
  }
656
667
  return null;
657
668
  };
669
+ const localSessionAnalysisResponse = (payload) => {
670
+ if (!localStore) {
671
+ return localToolError({
672
+ code: "local_store_unavailable",
673
+ message: "The local Ask The W store is unavailable.",
674
+ retryable: true,
675
+ hint: "Retry after restarting the plugin host.",
676
+ });
677
+ }
678
+ const loginRequired = requireFreeIdentity();
679
+ if (loginRequired)
680
+ return loginRequired;
681
+ const scopeKey = currentScopeKey();
682
+ const sessionId = payload.sessionId ?? localStore.mostRecentSessionId({ scopeKey });
683
+ const allSessionIds = localStore.listSessionIds({ limit: 100000, scopeKey });
684
+ const allowedSessionIds = new Set(allSessionIds.slice(0, 3));
685
+ if (sessionId && allSessionIds.length > 3 && !allowedSessionIds.has(sessionId)) {
686
+ return localToolError({
687
+ code: "free_tier_limit",
688
+ message: "The free plugin can analyze the latest three local sessions.",
689
+ retryable: false,
690
+ hint: "Upgrade to review more than three sessions in the workspace dashboard.",
691
+ extra: {
692
+ tool: "analyze_session",
693
+ limit: 3,
694
+ upgradeUrl: "https://askthew.com/plugin?utm_source=mcp-plugin&utm_medium=tool-nudge&utm_campaign=mcp-free&tool=analyze_session",
695
+ cta: "Upgrade to analyze more than three sessions in the workspace dashboard.",
696
+ },
697
+ });
698
+ }
699
+ const limit = Math.min(50, payload.limit ?? 50);
700
+ const signals = sessionId
701
+ ? localStore.listSignals({ sessionId, scopeKey, cursor: payload.cursor, limit })
702
+ : [];
703
+ const allSignals = sessionId ? localStore.listSignals({ sessionId, scopeKey, limit: 100000 }) : [];
704
+ const decisions = sessionId
705
+ ? localStore.listDecisions({ sessionId, scopeKey, limit: 100000 })
706
+ : [];
707
+ const decisionCandidates = listDecisionCandidates({ store: localStore, sessionId, scopeKey, limit: 25 }).candidates;
708
+ const counts = signals.reduce((accumulator, signal) => {
709
+ accumulator[signal.kind] = (accumulator[signal.kind] ?? 0) + 1;
710
+ return accumulator;
711
+ }, {});
712
+ const allCounts = allSignals.reduce((accumulator, signal) => {
713
+ accumulator[signal.kind] = (accumulator[signal.kind] ?? 0) + 1;
714
+ return accumulator;
715
+ }, {});
716
+ const nextCursor = signals.length >= limit ? signals.at(-1)?.capturedAt ?? null : null;
717
+ if ((payload.format ?? "markdown") === "json") {
718
+ return budgetedLocalResponse({
719
+ ok: true,
720
+ tier: "free",
721
+ sessionId,
722
+ format: "json",
723
+ signals: payload.compact
724
+ ? signals.map((signal) => compactSignal(signal, localStore.getDecisionForSignal(signal.id)))
725
+ : signals.map((signal) => signalWithDecision(localStore, signal)),
726
+ decisions: payload.compact
727
+ ? decisions.map((decision) => ({ id: decision.id, headline: decision.headline, status: decision.status, signalIds: decision.sourceSignalIds }))
728
+ : decisions.map((decision) => decisionWithSignals(localStore, decision)),
729
+ decisionCandidates,
730
+ nextCursor,
731
+ counts: {
732
+ totalSignals: allSignals.length,
733
+ byKind: allCounts,
734
+ },
735
+ }, payload.max_chars);
736
+ }
737
+ return budgetedLocalResponse({
738
+ ok: true,
739
+ tier: "free",
740
+ sessionId,
741
+ format: "markdown",
742
+ rendered: renderSessionMarkdown({ sessionId, signals: allSignals, decisions, decisionCandidates }),
743
+ ...(payload.compact
744
+ ? { signals: allSignals.map((signal) => compactSignal(signal, localStore.getDecisionForSignal(signal.id))) }
745
+ : {}),
746
+ counts: {
747
+ totalSignals: allSignals.length,
748
+ byKind: Object.keys(allCounts).length ? allCounts : counts,
749
+ },
750
+ }, payload.max_chars);
751
+ };
658
752
  server.tool("capture_session_signal", {
659
753
  sessionId: z.string().min(1),
660
754
  sequence: z.number().int().nonnegative(),
@@ -973,18 +1067,7 @@ export function createAskTheWMcpServer(options = {}) {
973
1067
  idempotencyKey: idempotencyKeySchema,
974
1068
  }, async (payload) => {
975
1069
  if (mode.mode !== "paid" && localStore) {
976
- const loginRequired = requireFreeIdentity();
977
- if (loginRequired)
978
- return loginRequired;
979
- if (payload.confirmText !== payload.id) {
980
- return localToolError({
981
- code: "confirmation_required",
982
- message: "confirmText must match the decision id.",
983
- retryable: false,
984
- hint: "Pass the exact decision id as confirmText.",
985
- });
986
- }
987
- return localResponse({ ok: localStore.deleteDecision(payload.id), tier: "free" });
1070
+ return localResponse(paidFeatureNudge("delete_decision"));
988
1071
  }
989
1072
  return apiToolResponse(`/api/decisions/${encodeURIComponent(payload.id)}`, {
990
1073
  confirmText: payload.confirmText,
@@ -1138,31 +1221,7 @@ export function createAskTheWMcpServer(options = {}) {
1138
1221
  max_chars: maxCharsSchema,
1139
1222
  }, async (payload) => {
1140
1223
  if (mode.mode !== "paid" && localStore) {
1141
- const loginRequired = requireFreeIdentity();
1142
- if (loginRequired)
1143
- return loginRequired;
1144
- const normalizedQuery = payload.query.toLowerCase();
1145
- const signals = localStore
1146
- .listSignals({
1147
- limit: 100000,
1148
- sessionId: payload.sessionId,
1149
- scopeKey: currentScopeKey(),
1150
- })
1151
- .filter((signal) => [
1152
- signal.summary,
1153
- signal.kind,
1154
- ...signal.filesTouched,
1155
- ...signal.commandsRun,
1156
- ].join("\n").toLowerCase().includes(normalizedQuery))
1157
- .slice(0, payload.limit);
1158
- return budgetedLocalResponse({
1159
- ok: true,
1160
- tier: "free",
1161
- query: payload.query,
1162
- signals: payload.compact === false
1163
- ? signals.map((signal) => signalWithDecision(localStore, signal))
1164
- : signals.map((signal) => compactSignal(signal, localStore.getDecisionForSignal(signal.id))),
1165
- }, payload.max_chars ?? 8000);
1224
+ return localResponse(paidFeatureNudge("find_signal_by_summary"));
1166
1225
  }
1167
1226
  return apiToolResponse(routeWithQuery("/api/signals", {
1168
1227
  query: payload.query,
@@ -1201,6 +1260,8 @@ export function createAskTheWMcpServer(options = {}) {
1201
1260
  compact: z.boolean().optional(),
1202
1261
  max_chars: maxCharsSchema,
1203
1262
  }, async (payload) => {
1263
+ if (mode.mode !== "paid")
1264
+ return localResponse(paidFeatureNudge("review_decisions"));
1204
1265
  if (mode.mode === "paid") {
1205
1266
  return apiToolResponse(routeWithQuery("/api/decisions", {
1206
1267
  since: payload.since,
@@ -1212,42 +1273,7 @@ export function createAskTheWMcpServer(options = {}) {
1212
1273
  max_chars: payload.max_chars,
1213
1274
  }));
1214
1275
  }
1215
- if (!localStore) {
1216
- return localToolError({
1217
- code: "local_store_unavailable",
1218
- message: "The local Ask The W store is unavailable.",
1219
- retryable: true,
1220
- hint: "Retry after restarting the plugin host.",
1221
- });
1222
- }
1223
- const loginRequired = requireFreeIdentity();
1224
- if (loginRequired)
1225
- return loginRequired;
1226
- const decisions = localStore.listDecisions({
1227
- since: payload.since,
1228
- status: payload.status,
1229
- limit: payload.limit,
1230
- cursor: payload.cursor,
1231
- sessionId: payload.sessionId,
1232
- scopeKey: currentScopeKey(),
1233
- });
1234
- return budgetedLocalResponse({
1235
- ok: true,
1236
- tier: "free",
1237
- format: payload.format,
1238
- rendered: renderDecisionDigest(decisions),
1239
- decisions: payload.compact
1240
- ? decisions.map((decision) => ({
1241
- id: decision.id,
1242
- headline: decision.headline,
1243
- status: decision.status,
1244
- signalIds: decision.sourceSignalIds,
1245
- }))
1246
- : decisions.map((decision) => decisionWithSignals(localStore, decision)),
1247
- count: decisions.length,
1248
- nextCursor: decisions.length >= payload.limit ? decisions.at(-1)?.createdAt ?? null : null,
1249
- copyHint: "Copy this output to back up your decisions - `export_decisions` is a paid feature.",
1250
- }, payload.max_chars);
1276
+ return localResponse(paidFeatureNudge("review_decisions"));
1251
1277
  });
1252
1278
  server.tool("review_session", "Review the current session trail. Use for natural prompts like: Show me my session trail.", {
1253
1279
  sessionId: z.string().optional(),
@@ -1257,6 +1283,8 @@ export function createAskTheWMcpServer(options = {}) {
1257
1283
  compact: z.boolean().optional(),
1258
1284
  max_chars: maxCharsSchema,
1259
1285
  }, async (payload) => {
1286
+ if (mode.mode !== "paid")
1287
+ return localResponse(paidFeatureNudge("review_session"));
1260
1288
  if (!localStore || mode.mode === "paid") {
1261
1289
  return apiToolResponse(routeWithQuery("/api/signals", {
1262
1290
  sessionId: payload.sessionId,
@@ -1266,79 +1294,26 @@ export function createAskTheWMcpServer(options = {}) {
1266
1294
  max_chars: payload.max_chars,
1267
1295
  }));
1268
1296
  }
1269
- const loginRequired = requireFreeIdentity();
1270
- if (loginRequired)
1271
- return loginRequired;
1272
- const scopeKey = currentScopeKey();
1273
- const sessionId = payload.sessionId ?? localStore.mostRecentSessionId({ scopeKey });
1274
- const allSessionIds = localStore.listSessionIds({ limit: 100000, scopeKey });
1275
- const allowedSessionIds = new Set(allSessionIds.slice(0, 3));
1276
- if (sessionId && allSessionIds.length > 3 && !allowedSessionIds.has(sessionId)) {
1277
- return localToolError({
1278
- code: "free_tier_limit",
1279
- message: "The free plugin can review the latest three local sessions.",
1280
- retryable: false,
1281
- hint: "Upgrade to review more than three sessions in the workspace dashboard.",
1282
- extra: {
1283
- tool: "review_session",
1284
- limit: 3,
1285
- upgradeUrl: "https://askthew.com/plugin?utm_source=mcp-plugin&utm_medium=tool-nudge&utm_campaign=mcp-free&tool=review_session",
1286
- cta: "Upgrade to review more than three sessions in the workspace dashboard.",
1287
- },
1288
- });
1289
- }
1290
- const limit = Math.min(50, payload.limit ?? 50);
1291
- const signals = sessionId
1292
- ? localStore.listSignals({ sessionId, scopeKey, cursor: payload.cursor, limit })
1293
- : [];
1294
- const allSignals = sessionId ? localStore.listSignals({ sessionId, scopeKey, limit: 100000 }) : [];
1295
- const decisions = sessionId
1296
- ? localStore.listDecisions({ sessionId, scopeKey, limit: 100000 })
1297
- : [];
1298
- const decisionCandidates = listDecisionCandidates({ store: localStore, sessionId, scopeKey, limit: 25 }).candidates;
1299
- const counts = signals.reduce((accumulator, signal) => {
1300
- accumulator[signal.kind] = (accumulator[signal.kind] ?? 0) + 1;
1301
- return accumulator;
1302
- }, {});
1303
- const allCounts = allSignals.reduce((accumulator, signal) => {
1304
- accumulator[signal.kind] = (accumulator[signal.kind] ?? 0) + 1;
1305
- return accumulator;
1306
- }, {});
1307
- const nextCursor = signals.length >= limit ? signals.at(-1)?.capturedAt ?? null : null;
1308
- if (payload.format === "json") {
1309
- return budgetedLocalResponse({
1310
- ok: true,
1311
- tier: "free",
1312
- sessionId,
1313
- format: "json",
1314
- signals: payload.compact
1315
- ? signals.map((signal) => compactSignal(signal, localStore.getDecisionForSignal(signal.id)))
1316
- : signals.map((signal) => signalWithDecision(localStore, signal)),
1317
- decisions: payload.compact
1318
- ? decisions.map((decision) => ({ id: decision.id, headline: decision.headline, status: decision.status, signalIds: decision.sourceSignalIds }))
1319
- : decisions.map((decision) => decisionWithSignals(localStore, decision)),
1320
- decisionCandidates,
1321
- nextCursor,
1322
- counts: {
1323
- totalSignals: allSignals.length,
1324
- byKind: allCounts,
1325
- },
1326
- }, payload.max_chars);
1297
+ return localResponse(paidFeatureNudge("review_session"));
1298
+ });
1299
+ server.tool("analyze_session", "Analyze the latest local coding-agent session.", {
1300
+ sessionId: z.string().optional(),
1301
+ format: z.enum(["markdown", "json"]).default("markdown"),
1302
+ cursor: cursorSchema,
1303
+ limit: z.number().int().positive().max(50).default(50),
1304
+ compact: z.boolean().optional(),
1305
+ max_chars: maxCharsSchema,
1306
+ }, async (payload) => {
1307
+ if (!localStore || mode.mode === "paid") {
1308
+ return apiToolResponse(routeWithQuery("/api/signals", {
1309
+ sessionId: payload.sessionId,
1310
+ cursor: payload.cursor,
1311
+ limit: payload.limit,
1312
+ compact: payload.compact,
1313
+ max_chars: payload.max_chars,
1314
+ }));
1327
1315
  }
1328
- return budgetedLocalResponse({
1329
- ok: true,
1330
- tier: "free",
1331
- sessionId,
1332
- format: "markdown",
1333
- rendered: renderSessionMarkdown({ sessionId, signals: allSignals, decisions, decisionCandidates }),
1334
- ...(payload.compact
1335
- ? { signals: allSignals.map((signal) => compactSignal(signal, localStore.getDecisionForSignal(signal.id))) }
1336
- : {}),
1337
- counts: {
1338
- totalSignals: allSignals.length,
1339
- byKind: Object.keys(allCounts).length ? allCounts : counts,
1340
- },
1341
- }, payload.max_chars);
1316
+ return localSessionAnalysisResponse(payload);
1342
1317
  });
1343
1318
  server.tool("recap", "Summarize the latest local coding-agent session as a digest, standup, or share-ready recap.", {
1344
1319
  format: z.enum(["digest", "standup", "share"]).default("digest"),
@@ -1474,27 +1449,14 @@ export function createAskTheWMcpServer(options = {}) {
1474
1449
  cursor: cursorSchema,
1475
1450
  max_chars: maxCharsSchema,
1476
1451
  }, async (payload) => {
1477
- if (!localStore || mode.mode === "paid")
1452
+ if (mode.mode !== "paid")
1478
1453
  return localResponse(paidFeatureNudge("list_decision_candidates"));
1479
- const loginRequired = requireFreeIdentity();
1480
- if (loginRequired)
1481
- return loginRequired;
1482
- const scopeKey = currentScopeKey();
1483
- const sessionId = payload.sessionId ?? localStore.mostRecentSessionId({ scopeKey });
1484
- const result = listDecisionCandidates({
1485
- store: localStore,
1486
- sessionId,
1487
- scopeKey,
1454
+ return apiToolResponse(routeWithQuery("/api/signals/decision-candidates", {
1455
+ sessionId: payload.sessionId,
1488
1456
  limit: payload.limit,
1489
1457
  cursor: payload.cursor,
1490
- });
1491
- return budgetedLocalResponse({
1492
- ok: true,
1493
- tier: "free",
1494
- sessionId,
1495
- decisionCandidates: result.candidates,
1496
- nextCursor: result.nextCursor,
1497
- }, payload.max_chars);
1458
+ max_chars: payload.max_chars,
1459
+ }));
1498
1460
  });
1499
1461
  server.tool("search_trail", "Search local signals and decisions together.", {
1500
1462
  query: z.string().min(1),
@@ -1504,27 +1466,16 @@ export function createAskTheWMcpServer(options = {}) {
1504
1466
  compact: z.boolean().optional(),
1505
1467
  max_chars: maxCharsSchema,
1506
1468
  }, async (payload) => {
1507
- if (!localStore || mode.mode === "paid")
1469
+ if (mode.mode !== "paid")
1508
1470
  return localResponse(paidFeatureNudge("search_trail"));
1509
- const loginRequired = requireFreeIdentity();
1510
- if (loginRequired)
1511
- return loginRequired;
1512
- const result = searchTrail({
1513
- store: localStore,
1471
+ return apiToolResponse(routeWithQuery("/api/search/trail", {
1514
1472
  query: payload.query,
1515
- scopeKey: currentScopeKey(),
1516
1473
  sessionId: payload.sessionId,
1517
1474
  limit: payload.limit,
1518
1475
  cursor: payload.cursor,
1519
1476
  compact: payload.compact,
1520
- });
1521
- return budgetedLocalResponse({
1522
- ok: true,
1523
- tier: "free",
1524
- query: payload.query,
1525
- matches: result.matches,
1526
- nextCursor: result.nextCursor,
1527
- }, payload.max_chars);
1477
+ max_chars: payload.max_chars,
1478
+ }));
1528
1479
  });
1529
1480
  server.tool("export_decisions", paidDescription("Export decisions from your workspace.", mode.mode), {
1530
1481
  format: z.enum(["json", "markdown", "jsonl"]).default("json"),
@@ -1562,44 +1513,7 @@ export function createAskTheWMcpServer(options = {}) {
1562
1513
  signalSource: payload.signalSource,
1563
1514
  }));
1564
1515
  }
1565
- if (!localStore) {
1566
- return localToolError({
1567
- code: "local_store_unavailable",
1568
- message: "The local Ask The W store is unavailable.",
1569
- retryable: true,
1570
- hint: "Retry after restarting the plugin host.",
1571
- });
1572
- }
1573
- const loginRequired = requireFreeIdentity();
1574
- if (loginRequired)
1575
- return loginRequired;
1576
- const scopeKey = currentScopeKey();
1577
- const points = buildLocalTimeline({
1578
- scope: payload.scope,
1579
- signals: localStore.listSignals({ scopeKey, limit: 100000 }),
1580
- decisions: localStore.listDecisions({ scopeKey, limit: 100000 }),
1581
- limit: payload.limit,
1582
- });
1583
- const totals = points.reduce((accumulator, point) => ({
1584
- signals: accumulator.signals + point.signalCount,
1585
- decisions: accumulator.decisions + point.decisionCount,
1586
- }), { signals: 0, decisions: 0 });
1587
- return budgetedLocalResponse({
1588
- ok: true,
1589
- tier: "free",
1590
- scope: payload.scope,
1591
- period: {
1592
- start: points[0]?.startedAt ?? points[0]?.x ?? "",
1593
- end: points.at(-1)?.endedAt ?? points.at(-1)?.x ?? "",
1594
- label: "Local timeline",
1595
- tz: "UTC",
1596
- },
1597
- points,
1598
- totals,
1599
- insights: buildLocalTimelineInsights(points),
1600
- narrative: `Local timeline: ${totals.signals} signals, ${totals.decisions} decisions.`,
1601
- markdownTable: renderLocalTimelineMarkdown(points, payload.scope),
1602
- }, payload.max_chars);
1516
+ return localResponse(paidFeatureNudge("view_timeline"));
1603
1517
  });
1604
1518
  return server;
1605
1519
  }
@@ -1914,16 +1828,96 @@ const isDirectIndexExecution = Boolean(process.argv[1]) &&
1914
1828
  return invokedPath === modulePath;
1915
1829
  }
1916
1830
  })();
1917
- if (isDirectIndexExecution) {
1918
- const server = createAskTheWMcpServer();
1919
- const transport = new StdioServerTransport();
1920
- server.connect(transport).catch((error) => {
1921
- if (error instanceof Error) {
1922
- console.error(error.message);
1923
- }
1924
- else {
1925
- console.error("Ask The W MCP server failed to start.", error);
1926
- }
1927
- process.exit(1);
1831
+ export async function runInitializeHandshake(input = {}) {
1832
+ const modulePath = input.entrypoint ?? fileURLToPath(import.meta.url);
1833
+ const child = spawn(process.execPath, [modulePath], {
1834
+ cwd: process.cwd(),
1835
+ env: input.env ?? process.env,
1836
+ stdio: ["pipe", "pipe", "pipe"],
1837
+ });
1838
+ let stdout = "";
1839
+ let stderr = "";
1840
+ child.stdout.setEncoding("utf8");
1841
+ child.stderr.setEncoding("utf8");
1842
+ child.stdout.on("data", (chunk) => {
1843
+ stdout += chunk;
1928
1844
  });
1845
+ child.stderr.on("data", (chunk) => {
1846
+ stderr += chunk;
1847
+ });
1848
+ const initialize = {
1849
+ jsonrpc: "2.0",
1850
+ id: 1,
1851
+ method: "initialize",
1852
+ params: {
1853
+ protocolVersion: "2024-11-05",
1854
+ capabilities: {},
1855
+ clientInfo: { name: "askthew-initialize-handshake", version: "1.0.0" },
1856
+ },
1857
+ };
1858
+ child.stdin.write(`${JSON.stringify(initialize)}\n`);
1859
+ try {
1860
+ return await new Promise((resolve, reject) => {
1861
+ const timeout = setTimeout(() => {
1862
+ reject(new Error(`Timed out waiting for initialize response. stdout=${stdout} stderr=${stderr}`));
1863
+ }, input.timeoutMs ?? 3000);
1864
+ const failOnExit = (code) => {
1865
+ clearTimeout(timeout);
1866
+ reject(new Error(`MCP stdio server exited before initialize. code=${code} stdout=${stdout} stderr=${stderr}`));
1867
+ };
1868
+ const check = () => {
1869
+ const lineEnd = stdout.indexOf("\n");
1870
+ if (lineEnd === -1)
1871
+ return;
1872
+ clearTimeout(timeout);
1873
+ child.off("exit", failOnExit);
1874
+ const response = JSON.parse(stdout.slice(0, lineEnd));
1875
+ if (response.id !== 1 || response.jsonrpc !== "2.0" || !response.result) {
1876
+ reject(new Error(`Unexpected initialize response: ${JSON.stringify(response)}`));
1877
+ return;
1878
+ }
1879
+ resolve({
1880
+ serverInfoVersion: typeof response.result?.serverInfo?.version === "string"
1881
+ ? response.result.serverInfo.version
1882
+ : undefined,
1883
+ });
1884
+ };
1885
+ child.stdout.on("data", check);
1886
+ child.once("exit", failOnExit);
1887
+ check();
1888
+ });
1889
+ }
1890
+ finally {
1891
+ child.kill();
1892
+ }
1893
+ }
1894
+ if (isDirectIndexExecution) {
1895
+ if (process.argv[2] === "initialize-handshake") {
1896
+ runInitializeHandshake()
1897
+ .then(() => {
1898
+ console.log("Ask The W MCP initialize handshake succeeded.");
1899
+ })
1900
+ .catch((error) => {
1901
+ if (error instanceof Error) {
1902
+ console.error(error.message);
1903
+ }
1904
+ else {
1905
+ console.error("Ask The W MCP initialize handshake failed.", error);
1906
+ }
1907
+ process.exit(1);
1908
+ });
1909
+ }
1910
+ else {
1911
+ const server = createAskTheWMcpServer();
1912
+ const transport = new StdioServerTransport();
1913
+ server.connect(transport).catch((error) => {
1914
+ if (error instanceof Error) {
1915
+ console.error(error.message);
1916
+ }
1917
+ else {
1918
+ console.error("Ask The W MCP server failed to start.", error);
1919
+ }
1920
+ process.exit(1);
1921
+ });
1922
+ }
1929
1923
  }
package/dist/install.d.ts CHANGED
@@ -9,6 +9,7 @@ interface HostConfigInput {
9
9
  free?: boolean;
10
10
  email?: string;
11
11
  cwd?: string;
12
+ serverEntrypoint?: string;
12
13
  }
13
14
  interface InstallHostConfigInput extends HostConfigInput {
14
15
  dryRun?: boolean;
@@ -18,10 +19,26 @@ interface InstallHostConfigInput extends HostConfigInput {
18
19
  interface UninstallHostConfigInput {
19
20
  hostType: SupportedHostType;
20
21
  serverName?: string;
22
+ cwds?: string[];
21
23
  dryRun?: boolean;
22
24
  homeDirectory?: string;
23
25
  cwd?: string;
24
26
  }
27
+ export declare const DEFAULT_FREE_SERVER_NAME = "askthew-free";
28
+ export declare const DEFAULT_WORKSPACE_SERVER_NAME = "askthew-workspace";
29
+ export interface InstallReceipt {
30
+ hostType: SupportedHostType;
31
+ serverName: string;
32
+ settingsPath: string;
33
+ cwd: string;
34
+ instructionPaths: string[];
35
+ dataDir: string;
36
+ serverEntrypoint?: string;
37
+ installedAt: string;
38
+ packageVersion?: string;
39
+ }
40
+ export declare function packageVersion(): string;
41
+ export declare function defaultServerNameForTier(free?: boolean): "askthew-free" | "askthew-workspace";
25
42
  export declare function resolveSettingsPath(input: {
26
43
  hostType: SupportedHostType;
27
44
  homeDirectory?: string;
@@ -107,14 +124,46 @@ export declare function installHostConfig(input: InstallHostConfigInput): {
107
124
  wroteFile: boolean;
108
125
  nextStep: string;
109
126
  };
127
+ export declare function readInstallReceipts(env?: NodeJS.ProcessEnv): InstallReceipt[];
128
+ export declare function writeInstallReceipt(receipt: Omit<InstallReceipt, "dataDir" | "installedAt"> & {
129
+ dataDir?: string;
130
+ installedAt?: string;
131
+ }, env?: NodeJS.ProcessEnv): InstallReceipt;
132
+ export declare function findInstallReceipts(input: {
133
+ hostType: SupportedHostType;
134
+ serverName?: string;
135
+ }, env?: NodeJS.ProcessEnv): InstallReceipt[];
136
+ export declare function removeInstallReceipts(input: {
137
+ hostType: SupportedHostType;
138
+ serverName?: string;
139
+ cwd?: string;
140
+ }, env?: NodeJS.ProcessEnv): number;
110
141
  export declare function uninstallHostConfig(input: UninstallHostConfigInput): {
111
142
  settingsPath: string;
112
143
  json: string;
113
144
  removedServerName: string;
145
+ removedServerNames: string[];
114
146
  foundConfigFile: boolean;
115
147
  removedServer: boolean;
116
148
  wroteFile: boolean;
117
149
  };
150
+ export declare function upgradePinnedHostConfig(input: {
151
+ hostType: SupportedHostType;
152
+ serverName?: string;
153
+ packageSpec?: string;
154
+ dryRun?: boolean;
155
+ homeDirectory?: string;
156
+ cwd?: string;
157
+ cwds?: string[];
158
+ }): {
159
+ settingsPath: string;
160
+ json: string;
161
+ packageSpec: string;
162
+ foundConfigFile: boolean;
163
+ upgradedServer: boolean;
164
+ upgradedServerNames: string[];
165
+ wroteFile: boolean;
166
+ };
118
167
  export declare function sendInstallHeartbeat(input: HostConfigInput & {
119
168
  cwd?: string;
120
169
  fetchImpl?: typeof fetch;