@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/README.md +2 -0
- package/dist/cli.js +184 -30
- package/dist/index.d.ts +7 -0
- package/dist/index.js +223 -229
- package/dist/install.d.ts +49 -0
- package/dist/install.js +388 -52
- package/dist/lib/local-store.js +15 -4
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.js +14 -0
- package/dist/lib/upgrade-nudge.js +1 -1
- package/package.json +1 -1
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
|
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 (
|
|
1452
|
+
if (mode.mode !== "paid")
|
|
1478
1453
|
return localResponse(paidFeatureNudge("list_decision_candidates"));
|
|
1479
|
-
|
|
1480
|
-
|
|
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
|
-
|
|
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 (
|
|
1469
|
+
if (mode.mode !== "paid")
|
|
1508
1470
|
return localResponse(paidFeatureNudge("search_trail"));
|
|
1509
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1918
|
-
const
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
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;
|