@askthew/mcp-plugin 0.2.7 → 0.4.0

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 CHANGED
@@ -1,6 +1,15 @@
1
1
  # Ask The W MCP Plugin
2
2
 
3
- Connect a local coding agent to Ask The W.
3
+ Connect a local coding agent to Ask The W. The fastest path is free and local-first:
4
+
5
+ ```bash
6
+ npx -y --prefer-online @askthew/mcp-plugin@latest install --host claude_code --free
7
+ npx @askthew/mcp-plugin auth login --email you@founder.com
8
+ ```
9
+
10
+ This captures decisions and session signals to `~/.askthew/store.sqlite` and lets your agent run `review_decisions`, `review_session`, and `analyze_session` without onboarding into the web app.
11
+
12
+ Founder-friendly promise: install from npm, magic-link login, then ask your coding agent to review the last session. You should see value in under 60 seconds.
4
13
 
5
14
  This package runs a small MCP server that lets Codex, Claude Code, Cursor, and other MCP-capable tools send compact work-session signals to an Ask The W workspace.
6
15
 
@@ -9,23 +18,38 @@ This package runs a small MCP server that lets Codex, Claude Code, Cursor, and o
9
18
  - Installs an Ask The W MCP server entry into a supported local client.
10
19
  - Preserves existing MCP servers and settings.
11
20
  - Adds marked project instructions so future coding-agent sessions know when to send Ask The W updates.
12
- - Sends a startup heartbeat so Ask The W can show the plugin as installed.
13
- - Exposes one primary MCP tool: `capture_session_signal`.
21
+ - Free mode stores full-fidelity signals and decisions locally in SQLite.
22
+ - Paid workspace mode sends a startup heartbeat so Ask The W can show the plugin as installed.
23
+ - Exposes `capture_session_signal` plus v1 API tools for decisions, outcomes, signals, outcome detail graphs, and north star reads or updates.
14
24
  - Redacts obvious secrets from summaries, evidence excerpts, commands, and metadata before sending.
15
25
  - Adds lightweight workspace metadata such as host type, repo name, app path, and server name.
16
26
 
17
27
  ## What It Does Not Do
18
28
 
19
29
  - It does not send full transcripts by default.
20
- - It does not infer decisions locally.
30
+ - It does not send local free-tier content to Ask The W unless you upgrade and run sync upload.
21
31
  - It does not link outcomes, score confidence, dedupe signals, or update the graph locally.
22
32
  - It does not include the Ask The W app, private server code, Supabase code, or internal analytics code.
23
33
 
24
34
  Ask The W performs inference, linking, approval state, dedupe, and outcome updates in the app.
25
35
 
26
- ## Install
36
+ ## Free Local Install
37
+
38
+ ```bash
39
+ npx -y --prefer-online @askthew/mcp-plugin@latest install \
40
+ --host claude_code \
41
+ --free
42
+
43
+ npx @askthew/mcp-plugin auth login --email you@founder.com
44
+ ```
45
+
46
+ Telemetry is aggregate-only and opt-out. Ask The W does not receive code, file contents, file paths, file names, command text, summaries, or decision content in free mode telemetry. We do tie aggregate counts, stack, and tool usage to your email for upgrade onboarding. Opt out with `--no-telemetry`, `ASKTHEW_TELEMETRY=off`, or `askthew-mcp telemetry opt-out`.
47
+
48
+ ## Workspace Install
49
+
50
+ Create a workspace token in Ask The W at `/decisions/settings/connectors`, then run the installer from your coding agent or terminal. Treat the token like a password; anyone with it can write compact source signals into that workspace.
27
51
 
28
- Create a workspace token in Ask The W, then run the installer from your coding agent or terminal.
52
+ Use setup tokens promptly. They expire after 24 hours if the connector never sends activity. Once connected, active tokens renew while in use and expire after 90 days without activity. Rotate the token in Ask The W if it is exposed or if the connector reports an expired or revoked token.
29
53
 
30
54
  Codex:
31
55
 
@@ -57,7 +81,9 @@ npx -y --prefer-online @askthew/mcp-plugin@latest install \
57
81
  --server-name "askthew"
58
82
  ```
59
83
 
60
- After install, restart or reload your coding agent if needed.
84
+ After install, restart or reload your coding agent if needed. At the start of every new coding-agent session in this repo, before plan mode or exploration, call `capture_session_signal` with `kind: "setup_complete"` and choose "Always allow" if the host prompts for tool permission. If you realize later that the startup call was missed, send it immediately with `metadata.recovered_missed_startup=true`.
85
+
86
+ Claude Desktop and Cowork custom connectors use Ask The W's hosted Remote MCP URL instead of this local `npx` installer. In Ask The W, create a Claude Remote URL from the plugin source, then paste it into Claude's custom connector form as the Remote MCP server URL. The URL must be public HTTPS; `localhost`, `127.0.0.1`, LAN IPs, VPN-only hosts, and firewall-blocked servers will fail because Claude connects from Anthropic's cloud. Treat the URL like a password and rotate it if it leaks.
61
87
 
62
88
  The installer also adds safe, marked project instructions:
63
89
 
@@ -65,7 +91,7 @@ The installer also adds safe, marked project instructions:
65
91
  - Claude Code: `CLAUDE.md`
66
92
  - Cursor: `.cursor/rules/askthew.mdc`
67
93
 
68
- These instructions tell the coding agent to send compact Ask The W updates after meaningful direction changes, implementation work, verification, long-session checkpoints, and final summaries. Existing instruction files are preserved.
94
+ These instructions tell the coding agent to send compact Ask The W updates at the start of every new repo session, after meaningful direction changes, implementation work, verification, long-session checkpoints, and final summaries. Existing instruction files are preserved.
69
95
 
70
96
  To skip this behavior, pass:
71
97
 
@@ -116,7 +142,7 @@ Optional environment variables:
116
142
 
117
143
  ## Tool Contract
118
144
 
119
- The public tool surface is intentionally small.
145
+ The session-signal tool remains the main automatic capture path.
120
146
 
121
147
  ```json
122
148
  {
@@ -136,12 +162,24 @@ The public tool surface is intentionally small.
136
162
 
137
163
  Use compact summaries and short evidence excerpts. Do not send full transcripts.
138
164
 
165
+ The plugin also exposes v1 API tools that map to the app's authenticated routes:
166
+
167
+ | Tool | Purpose |
168
+ |---|---|
169
+ | `list_decisions`, `get_decision`, `create_decision`, `update_decision`, `delete_decision` | Work with decision feed entries. |
170
+ | `list_outcomes`, `get_outcome`, `list_outcome_signals`, `create_outcome`, `update_outcome`, `delete_outcome` | Work with outcomes and their linked signals. |
171
+ | `get_north_star`, `update_north_star` | Read or update the workspace north star. API updates are allowed only for private workspaces. |
172
+ | `list_signals`, `get_signal` | Read workspace signals. |
173
+
174
+ API mutations are text-only and are recorded back into the workspace signal feed. Decision and outcome deletes require a `confirmText` value that exactly matches the stored decision headline or outcome name after whitespace normalization. North star delete is not available through the API.
175
+
139
176
  ## Troubleshooting
140
177
 
141
178
  - Empty `list_mcp_resources` or `list_mcp_resource_templates` results are normal. This connector is tool-driven.
142
179
  - If Ask The W shows "Waiting for install", restart or reload your coding agent.
143
- - If Ask The W shows "Installed", keep working normally. The installed behavior instructions handle future compact updates automatically. If your coding agent asks for Ask The W tool permission during a future update, choose "Always allow" if available.
144
- - If a token fails, rotate it in Ask The W and rerun the installer.
180
+ - If Ask The W shows "Installed", restart or reload your coding agent if it was already open. At the start of the next repo session, the coding agent should call `capture_session_signal` with `kind: "setup_complete"` before plan mode; choose "Always allow" if it asks for tool permission.
181
+ - For Claude Desktop or Cowork, set `capture_session_signal` to "Always allow" in connector Tool permissions when that panel is available, or choose "Allow always" on the first tool prompt.
182
+ - If a token fails, check whether the error says missing, malformed, expired, or revoked. Copy the full token if it is malformed; rotate it in Ask The W and rerun the installer if it expired or was revoked.
145
183
 
146
184
  ## Development
147
185
 
package/dist/cli.js CHANGED
@@ -1,14 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import fs from "node:fs";
3
4
  import { createAskTheWMcpServer } from "./index.js";
5
+ import { requestMagicLinkCode, verifyMagicLinkCode } from "./lib/auth-magic-link.js";
6
+ import { credentialsPath, ensureAskTheWDataDir } from "./lib/paths.js";
7
+ import { loadCliCredentials } from "./lib/free-tier-policy.js";
8
+ import { LocalStore } from "./lib/local-store.js";
9
+ import { buildTelemetryPayload } from "./lib/telemetry.js";
10
+ import { syncDryRun, uploadLocalStore } from "./lib/upgrade-sync.js";
4
11
  import { createHostConfigSnippet, formatInstallCommand, installBehaviorInstructions, installHostConfig, sendInstallHeartbeat, } from "./install.js";
5
12
  function usage() {
6
13
  return [
7
- "AskTheW Coding Agent Connector",
14
+ "Ask The W Coding Agent Connector",
8
15
  "",
9
16
  "Usage:",
10
17
  " askthew-mcp",
11
18
  " askthew-mcp install --host <claude_code|codex|cursor> --token <install-token> --api-url <url> --server-name <name> [--client-id <id>] [--client-label <label>] [--dry-run] [--no-agent-instructions]",
19
+ " askthew-mcp install --host <claude_code|codex|cursor> --free [--api-url <url>] [--server-name <name>]",
20
+ " askthew-mcp auth login --email <email> [--code <code>] [--no-telemetry]",
21
+ " askthew-mcp auth logout | status",
22
+ " askthew-mcp telemetry status | opt-out | opt-in | preview",
23
+ " askthew-mcp local stats | reset --hard",
24
+ " askthew-mcp sync upload [--dry-run]",
12
25
  " askthew-mcp print-config --host <claude_code|codex|cursor> --token <install-token> --api-url <url> --server-name <name> [--client-id <id>] [--client-label <label>]",
13
26
  ].join("\n");
14
27
  }
@@ -21,6 +34,7 @@ function parseInstallArgs(argv) {
21
34
  let serverName = process.env.ASKTHEW_SERVER_NAME?.trim() || "";
22
35
  let dryRun = false;
23
36
  let installAgentInstructions = true;
37
+ let free = false;
24
38
  for (let index = 0; index < argv.length; index += 1) {
25
39
  const argument = argv[index];
26
40
  if (argument === "--dry-run") {
@@ -31,6 +45,10 @@ function parseInstallArgs(argv) {
31
45
  installAgentInstructions = false;
32
46
  continue;
33
47
  }
48
+ if (argument === "--free") {
49
+ free = true;
50
+ continue;
51
+ }
34
52
  const next = argv[index + 1];
35
53
  if (!next) {
36
54
  throw new Error(`Missing value for ${argument}.`);
@@ -73,14 +91,14 @@ function parseInstallArgs(argv) {
73
91
  if (!hostType) {
74
92
  throw new Error("Missing required --host argument.");
75
93
  }
76
- if (!token) {
94
+ if (!free && !token) {
77
95
  throw new Error("Missing required --token argument.");
78
96
  }
79
97
  if (!apiUrl) {
80
- throw new Error("Missing required --api-url argument.");
98
+ apiUrl = "https://app.askthew.com";
81
99
  }
82
100
  if (!serverName) {
83
- throw new Error("Missing required --server-name argument.");
101
+ serverName = "askthew";
84
102
  }
85
103
  return {
86
104
  hostType,
@@ -91,6 +109,7 @@ function parseInstallArgs(argv) {
91
109
  serverName,
92
110
  dryRun,
93
111
  installAgentInstructions,
112
+ free,
94
113
  };
95
114
  }
96
115
  function normalizeInstallToken(token) {
@@ -117,19 +136,21 @@ async function main() {
117
136
  dryRun: options.dryRun,
118
137
  })
119
138
  : null;
120
- const heartbeatSent = result.wroteFile
139
+ const heartbeatSent = result.wroteFile && !options.free
121
140
  ? await sendInstallHeartbeat(options).catch(() => false)
122
141
  : false;
123
- console.log(result.wroteFile ? "AskTheW plugin install complete." : "AskTheW plugin dry run complete.");
142
+ console.log(result.wroteFile ? "Ask The W plugin install complete." : "Ask The W plugin dry run complete.");
124
143
  console.log(`Settings path: ${result.settingsPath}`);
125
144
  if (instructions) {
126
145
  console.log(`Agent instructions: ${instructions.path}`);
127
146
  }
128
147
  console.log(`Install command: ${formatInstallCommand(options)}`);
129
148
  if (result.wroteFile) {
130
- console.log(heartbeatSent
131
- ? "Ask The W setup check sent. Refresh the app to confirm the plugin shows Installed."
132
- : "Ask The W setup check could not be sent yet. Restart or reload your coding app, then refresh Ask The W.");
149
+ console.log(options.free
150
+ ? "Free local mode installed. Next step: run `askthew-mcp auth login --email you@example.com`."
151
+ : heartbeatSent
152
+ ? "Ask The W install heartbeat sent. Refresh the app to confirm the plugin shows Installed."
153
+ : "Ask The W install heartbeat could not be sent yet. Restart or reload your coding app, then refresh Ask The W.");
133
154
  }
134
155
  console.log(`Next step: ${result.nextStep}`);
135
156
  if (!result.wroteFile) {
@@ -138,6 +159,29 @@ async function main() {
138
159
  }
139
160
  return;
140
161
  }
162
+ if (command === "auth") {
163
+ await runAuthCommand(argv);
164
+ return;
165
+ }
166
+ if (command === "telemetry") {
167
+ await runTelemetryCommand(argv);
168
+ return;
169
+ }
170
+ if (command === "local") {
171
+ await runLocalCommand(argv);
172
+ return;
173
+ }
174
+ if (command === "sync") {
175
+ await runSyncCommand(argv);
176
+ return;
177
+ }
178
+ if (command === "upgrade") {
179
+ console.log("Open https://askthew.com/mcp to upgrade, then run `askthew-mcp upgrade --finalize`.");
180
+ if (argv.includes("--finalize")) {
181
+ console.log("Finalize will rewrite host config after a paid workspace token is available in the web app.");
182
+ }
183
+ return;
184
+ }
141
185
  if (command) {
142
186
  throw new Error(`Unknown command "${command}".\n\n${usage()}`);
143
187
  }
@@ -145,12 +189,106 @@ async function main() {
145
189
  const transport = new StdioServerTransport();
146
190
  await server.connect(transport);
147
191
  }
192
+ function argValue(argv, name) {
193
+ const index = argv.indexOf(name);
194
+ return index >= 0 ? argv[index + 1] : undefined;
195
+ }
196
+ async function runAuthCommand(argv) {
197
+ const [subcommand] = argv;
198
+ if (subcommand === "status") {
199
+ const credentials = loadCliCredentials();
200
+ console.log(credentials
201
+ ? `Logged in as ${credentials.email ?? credentials.userId}. Telemetry: ${credentials.telemetryOptOut ? "off" : "on"}`
202
+ : "Not logged in. Run `askthew-mcp auth login --email you@example.com`.");
203
+ return;
204
+ }
205
+ if (subcommand === "logout") {
206
+ const file = credentialsPath();
207
+ if (fs.existsSync(file))
208
+ fs.rmSync(file);
209
+ console.log("Logged out of Ask The W local free tier.");
210
+ return;
211
+ }
212
+ if (subcommand !== "login") {
213
+ throw new Error("Usage: askthew-mcp auth login --email <email> [--code <code>] [--no-telemetry]");
214
+ }
215
+ ensureAskTheWDataDir();
216
+ const email = argValue(argv, "--email")?.trim();
217
+ if (!email)
218
+ throw new Error("Missing --email.");
219
+ const noTelemetry = argv.includes("--no-telemetry");
220
+ const requested = await requestMagicLinkCode({ email, deviceLabel: "askthew-mcp" });
221
+ console.log(`Code sent to ${email}. It expires at ${requested.expiresAt}.`);
222
+ if (requested.devCode)
223
+ console.log(`Dev code: ${requested.devCode}`);
224
+ const code = argValue(argv, "--code")?.trim() ?? requested.devCode;
225
+ if (!code) {
226
+ throw new Error("Re-run with --code <6-digit-code> after checking your email.");
227
+ }
228
+ const credentials = await verifyMagicLinkCode({
229
+ requestId: requested.requestId,
230
+ code,
231
+ telemetryOptOut: noTelemetry,
232
+ });
233
+ console.log(`Logged in. Account status: ${credentials.accountStatus}. Credentials stored with mode 0600.`);
234
+ }
235
+ async function runTelemetryCommand(argv) {
236
+ const [subcommand] = argv;
237
+ const credentials = loadCliCredentials();
238
+ if (!credentials)
239
+ throw new Error("Not logged in. Run `askthew-mcp auth login` first.");
240
+ if (subcommand === "status") {
241
+ console.log(`Telemetry: ${credentials.telemetryOptOut ? "off" : "on"}`);
242
+ return;
243
+ }
244
+ if (subcommand === "opt-out" || subcommand === "opt-in") {
245
+ const next = { ...credentials, telemetryOptOut: subcommand === "opt-out" };
246
+ fs.writeFileSync(credentialsPath(), `${JSON.stringify(next, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
247
+ fs.chmodSync(credentialsPath(), 0o600);
248
+ console.log(`Telemetry: ${next.telemetryOptOut ? "off" : "on"}`);
249
+ return;
250
+ }
251
+ if (subcommand === "preview") {
252
+ const store = LocalStore.open();
253
+ console.log(JSON.stringify(buildTelemetryPayload({ store, credentials }), null, 2));
254
+ return;
255
+ }
256
+ throw new Error("Usage: askthew-mcp telemetry status | opt-out | opt-in | preview");
257
+ }
258
+ async function runLocalCommand(argv) {
259
+ const [subcommand, flag] = argv;
260
+ const store = LocalStore.open();
261
+ if (subcommand === "stats") {
262
+ console.log(JSON.stringify(store.stats(), null, 2));
263
+ return;
264
+ }
265
+ if (subcommand === "reset" && flag === "--hard") {
266
+ const dir = ensureAskTheWDataDir();
267
+ fs.rmSync(dir, { recursive: true, force: true });
268
+ console.log("Local Ask The W data removed.");
269
+ return;
270
+ }
271
+ throw new Error("Usage: askthew-mcp local stats | reset --hard");
272
+ }
273
+ async function runSyncCommand(argv) {
274
+ if (argv[0] !== "upload")
275
+ throw new Error("Usage: askthew-mcp sync upload [--dry-run]");
276
+ const credentials = loadCliCredentials();
277
+ if (!credentials)
278
+ throw new Error("Not logged in. Run `askthew-mcp auth login` first.");
279
+ const store = LocalStore.open();
280
+ if (argv.includes("--dry-run")) {
281
+ console.log(JSON.stringify(syncDryRun(store), null, 2));
282
+ return;
283
+ }
284
+ console.log(JSON.stringify(await uploadLocalStore({ store, credentials }), null, 2));
285
+ }
148
286
  main().catch((error) => {
149
287
  if (error instanceof Error) {
150
288
  console.error(error.message);
151
289
  }
152
290
  else {
153
- console.error("AskTheW plugin failed to start.", error);
291
+ console.error("Ask The W plugin failed to start.", error);
154
292
  }
155
293
  process.exit(1);
156
294
  });
package/dist/index.d.ts CHANGED
@@ -82,21 +82,18 @@ export declare const provenanceSignalSchema: z.ZodObject<{
82
82
  installToken?: string | undefined;
83
83
  }>;
84
84
  export type ProvenanceSignal = z.infer<typeof provenanceSignalSchema>;
85
- export declare function inferFunctionalArea(signal: Pick<ProvenanceSignal, "filesAffected">): string;
86
85
  export declare function redactProvenanceSignal(input: ProvenanceSignal): {
86
+ decision: string;
87
+ rationale: string;
88
+ framework: string | undefined;
89
+ filesAffected: string[];
87
90
  originatingPrompt: string | undefined;
88
- metadata: {
89
- functional_area: string;
90
- };
91
+ installToken: string | undefined;
92
+ metadata: Record<string, unknown>;
91
93
  sessionId: string;
92
94
  source: string;
93
- decision: string;
94
- rationale: string;
95
95
  confidence: number;
96
- filesAffected: string[];
97
- framework?: string | undefined;
98
96
  pendingApproval?: boolean | undefined;
99
- installToken?: string | undefined;
100
97
  };
101
98
  export declare function redactCodingSessionSignal(input: CodingSessionSignal): {
102
99
  summary: string;
@@ -104,12 +101,27 @@ export declare function redactCodingSessionSignal(input: CodingSessionSignal): {
104
101
  excerpt: string;
105
102
  role: "user" | "assistant" | "system";
106
103
  }[];
104
+ filesTouched: string[];
107
105
  commandsRun: string[];
108
106
  metadata: Record<string, unknown>;
109
107
  sessionId: string;
110
108
  sequence: number;
111
109
  kind: "setup_complete" | "session_checkpoint" | "direction_change" | "implementation_update" | "verification_result" | "final_summary";
112
- filesTouched: string[];
113
110
  };
111
+ export interface AskTheWMcpServerOptions {
112
+ credentials?: {
113
+ installToken?: string;
114
+ userId?: string;
115
+ apiKey?: string;
116
+ serverName?: string;
117
+ clientId?: string;
118
+ clientLabel?: string;
119
+ hostType?: "claude_code" | "codex" | "cursor";
120
+ };
121
+ apiBaseUrl?: string;
122
+ fetchImpl?: typeof fetch;
123
+ runtimeMetadata?: Record<string, unknown> | (() => Record<string, unknown>);
124
+ sendStartupHeartbeat?: boolean;
125
+ }
114
126
  export declare function normalizeInstallTokenInput(token: string | undefined): string;
115
- export declare function createAskTheWMcpServer(): McpServer;
127
+ export declare function createAskTheWMcpServer(options?: AskTheWMcpServerOptions): McpServer;