@askthew/mcp-plugin 0.2.3 → 0.2.5

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
@@ -9,7 +9,7 @@ This package runs a small MCP server that lets Codex, Claude Code, Cursor, and o
9
9
  - Installs an Ask The W MCP server entry into a supported local client.
10
10
  - Preserves existing MCP servers and settings.
11
11
  - 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 that the plugin was seen.
12
+ - Sends a startup heartbeat so Ask The W can show the plugin as installed.
13
13
  - Exposes one primary MCP tool: `capture_session_signal`.
14
14
  - Redacts obvious secrets from summaries, evidence excerpts, commands, and metadata before sending.
15
15
  - Adds lightweight workspace metadata such as host type, repo name, app path, and server name.
@@ -75,7 +75,22 @@ To skip this behavior, pass:
75
75
 
76
76
  ## Configuration
77
77
 
78
- The installer writes an MCP server entry like this:
78
+ The installer writes the MCP server into the host's native MCP configuration so it appears in that host's MCP server list:
79
+
80
+ - Codex: `~/.codex/config.toml`
81
+ - Claude Code: `~/.claude.json`, scoped to the current project
82
+ - Cursor: `~/.cursor/mcp.json`
83
+
84
+ Codex example:
85
+
86
+ ```toml
87
+ [mcp_servers.askthew]
88
+ command = "npx"
89
+ args = ["-y", "@askthew/mcp-plugin@latest"]
90
+ env = { ASKTHEW_INSTALL_TOKEN = "<ASKTHEW_INSTALL_TOKEN>", ASKTHEW_HOST_TYPE = "codex", ASKTHEW_API_URL = "https://app.askthew.com/", ASKTHEW_SERVER_NAME = "askthew" }
91
+ ```
92
+
93
+ Claude Code and Cursor use an MCP JSON entry like this:
79
94
 
80
95
  ```json
81
96
  {
@@ -125,7 +140,7 @@ Use compact summaries and short evidence excerpts. Do not send full transcripts.
125
140
 
126
141
  - Empty `list_mcp_resources` or `list_mcp_resource_templates` results are normal. This connector is tool-driven.
127
142
  - If Ask The W shows "Waiting for install", restart or reload your coding agent.
128
- - If Ask The W shows "Installed", ask your coding agent to send the first setup check.
143
+ - If Ask The W shows "Installed", approve the Ask The W setup popup if your coding agent shows one after reload. The installed behavior instructions handle future compact updates automatically.
129
144
  - If a token fails, rotate it in Ask The W and rerun the installer.
130
145
 
131
146
  ## Development
package/dist/cli.js CHANGED
@@ -128,7 +128,7 @@ async function main() {
128
128
  console.log(`Install command: ${formatInstallCommand(options)}`);
129
129
  if (result.wroteFile) {
130
130
  console.log(heartbeatSent
131
- ? "Ask The W setup check sent. Refresh the app to confirm the plugin was seen."
131
+ ? "Ask The W setup check sent. Refresh the app to confirm the plugin shows Installed."
132
132
  : "Ask The W setup check could not be sent yet. Restart or reload your coding app, then refresh Ask The W.");
133
133
  }
134
134
  console.log(`Next step: ${result.nextStep}`);
package/dist/install.d.ts CHANGED
@@ -10,6 +10,7 @@ interface HostConfigInput {
10
10
  interface InstallHostConfigInput extends HostConfigInput {
11
11
  dryRun?: boolean;
12
12
  homeDirectory?: string;
13
+ cwd?: string;
13
14
  }
14
15
  export declare function resolveSettingsPath(input: {
15
16
  hostType: SupportedHostType;
@@ -28,6 +29,10 @@ export declare function createServerEntry(input: HostConfigInput): {
28
29
  };
29
30
  };
30
31
  export declare function createHostConfigSnippet(input: HostConfigInput): {
32
+ settingsPath: string;
33
+ snippet: string;
34
+ json: string;
35
+ } | {
31
36
  settingsPath: string;
32
37
  snippet: {
33
38
  mcpServers: {
package/dist/install.js CHANGED
@@ -9,8 +9,13 @@ function isRecord(value) {
9
9
  }
10
10
  export function resolveSettingsPath(input) {
11
11
  const homeDirectory = input.homeDirectory ?? os.homedir();
12
- const configDirectory = input.hostType === "claude_code" ? ".claude" : ".codex";
13
- return path.join(homeDirectory, configDirectory, "settings.json");
12
+ if (input.hostType === "codex") {
13
+ return path.join(homeDirectory, ".codex", "config.toml");
14
+ }
15
+ if (input.hostType === "cursor") {
16
+ return path.join(homeDirectory, ".cursor", "mcp.json");
17
+ }
18
+ return path.join(homeDirectory, ".claude.json");
14
19
  }
15
20
  export function createServerEntry(input) {
16
21
  return {
@@ -27,6 +32,14 @@ export function createServerEntry(input) {
27
32
  };
28
33
  }
29
34
  export function createHostConfigSnippet(input) {
35
+ if (input.hostType === "codex") {
36
+ const toml = createCodexTomlSection(input);
37
+ return {
38
+ settingsPath: resolveSettingsPath({ hostType: input.hostType }),
39
+ snippet: toml,
40
+ json: toml,
41
+ };
42
+ }
30
43
  const snippet = {
31
44
  mcpServers: {
32
45
  [input.serverName]: createServerEntry(input),
@@ -38,6 +51,63 @@ export function createHostConfigSnippet(input) {
38
51
  json: JSON.stringify(snippet, null, 2),
39
52
  };
40
53
  }
54
+ function escapeTomlString(value) {
55
+ return JSON.stringify(value);
56
+ }
57
+ function tomlKey(value) {
58
+ return /^[A-Za-z0-9_-]+$/.test(value) ? value : escapeTomlString(value);
59
+ }
60
+ function createCodexTomlSection(input) {
61
+ const entry = createServerEntry(input);
62
+ const args = entry.args.map(escapeTomlString).join(", ");
63
+ const env = Object.entries(entry.env)
64
+ .map(([key, value]) => `${key} = ${escapeTomlString(String(value))}`)
65
+ .join(", ");
66
+ return [
67
+ `[mcp_servers.${tomlKey(input.serverName)}]`,
68
+ `command = ${escapeTomlString(entry.command)}`,
69
+ `args = [${args}]`,
70
+ `env = { ${env} }`,
71
+ ].join("\n");
72
+ }
73
+ function removeCodexTomlServer(content, serverName) {
74
+ const escapedServerName = serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
75
+ const quotedServerName = escapeTomlString(serverName).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
76
+ const sectionPattern = new RegExp(`\\n?\\[mcp_servers\\.(?:${escapedServerName}|${quotedServerName})\\]\\n[\\s\\S]*?(?=\\n\\[[^\\]]+\\]|$)`, "g");
77
+ return content.replace(sectionPattern, "").trimEnd();
78
+ }
79
+ function mergeCodexSettings(input) {
80
+ let next = input.existingSettings.trimEnd();
81
+ if (input.serverName !== "askthew") {
82
+ next = removeCodexTomlServer(next, "askthew");
83
+ }
84
+ next = removeCodexTomlServer(next, input.serverName);
85
+ return `${next}${next ? "\n\n" : ""}${createCodexTomlSection(input)}\n`;
86
+ }
87
+ function mergeClaudeCodeSettings(input) {
88
+ const cwd = path.resolve(input.cwd ?? process.cwd());
89
+ const existingSettings = isRecord(input.existingSettings) ? input.existingSettings : {};
90
+ const existingProjects = isRecord(existingSettings.projects) ? existingSettings.projects : {};
91
+ const existingProject = isRecord(existingProjects[cwd]) ? existingProjects[cwd] : {};
92
+ const existingMcpServers = isRecord(existingProject.mcpServers) ? existingProject.mcpServers : {};
93
+ const nextMcpServers = { ...existingMcpServers };
94
+ if (input.serverName !== "askthew" && "askthew" in nextMcpServers) {
95
+ delete nextMcpServers.askthew;
96
+ }
97
+ return {
98
+ ...existingSettings,
99
+ projects: {
100
+ ...existingProjects,
101
+ [cwd]: {
102
+ ...existingProject,
103
+ mcpServers: {
104
+ ...nextMcpServers,
105
+ [input.serverName]: createServerEntry(input),
106
+ },
107
+ },
108
+ },
109
+ };
110
+ }
41
111
  export function mergeHostSettings(input) {
42
112
  const existingSettings = isRecord(input.existingSettings) ? input.existingSettings : {};
43
113
  const existingMcpServers = isRecord(existingSettings.mcpServers) ? existingSettings.mcpServers : {};
@@ -70,8 +140,8 @@ export function formatInstallCommand(input) {
70
140
  ].join(" ");
71
141
  }
72
142
  export function verificationNextStep(hostType) {
73
- const hostLabel = hostType === "claude_code" ? "Claude Code" : "Codex";
74
- return `Refresh Ask The W to confirm the plugin was seen. Restart ${hostLabel} if it is already open. The installed project instructions tell the coding agent when to send Ask The W updates automatically. list_mcp_resources/list_mcp_resource_templates may be empty for this tool-driven connector and are not failure signals.`;
143
+ const hostLabel = hostType === "claude_code" ? "Claude Code" : hostType === "cursor" ? "Cursor" : "Codex";
144
+ return `Refresh Ask The W to confirm the plugin shows Installed. Restart or reload ${hostLabel} if it is already open. If ${hostLabel} shows an Ask The W setup popup, approve it once. The installed project instructions tell the coding agent when to send updates automatically. list_mcp_resources/list_mcp_resource_templates may be empty for this tool-driven connector and are not failure signals.`;
75
145
  }
76
146
  export function installHostConfig(input) {
77
147
  const settingsPath = resolveSettingsPath({
@@ -79,11 +149,15 @@ export function installHostConfig(input) {
79
149
  homeDirectory: input.homeDirectory,
80
150
  });
81
151
  let existingSettings = {};
152
+ let existingText = "";
82
153
  if (fs.existsSync(settingsPath)) {
83
154
  const raw = fs.readFileSync(settingsPath, "utf8");
155
+ existingText = raw;
84
156
  if (raw.trim().length > 0) {
85
157
  try {
86
- existingSettings = JSON.parse(raw);
158
+ if (input.hostType !== "codex") {
159
+ existingSettings = JSON.parse(raw);
160
+ }
87
161
  }
88
162
  catch (error) {
89
163
  const detail = error instanceof Error ? error.message : "Unknown parse failure.";
@@ -91,7 +165,7 @@ export function installHostConfig(input) {
91
165
  }
92
166
  }
93
167
  }
94
- const merged = mergeHostSettings({
168
+ const hostInput = {
95
169
  existingSettings,
96
170
  hostType: input.hostType,
97
171
  token: input.token,
@@ -99,11 +173,21 @@ export function installHostConfig(input) {
99
173
  serverName: input.serverName,
100
174
  clientId: input.clientId,
101
175
  clientLabel: input.clientLabel,
102
- });
103
- const json = JSON.stringify(merged, null, 2);
176
+ };
177
+ const json = input.hostType === "codex"
178
+ ? mergeCodexSettings({
179
+ ...hostInput,
180
+ existingSettings: existingText,
181
+ })
182
+ : JSON.stringify(input.hostType === "claude_code"
183
+ ? mergeClaudeCodeSettings({
184
+ ...hostInput,
185
+ cwd: input.cwd,
186
+ })
187
+ : mergeHostSettings(hostInput), null, 2);
104
188
  if (!input.dryRun) {
105
189
  fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
106
- fs.writeFileSync(settingsPath, `${json}\n`, "utf8");
190
+ fs.writeFileSync(settingsPath, json.endsWith("\n") ? json : `${json}\n`, "utf8");
107
191
  }
108
192
  return {
109
193
  settingsPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askthew/mcp-plugin",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "private": false,
5
5
  "description": "Ask The W MCP connector for capturing compact coding-agent session signals.",
6
6
  "type": "module",