@askthew/mcp-plugin 0.2.3 → 0.2.4

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
@@ -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
  {
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,7 +140,7 @@ export function formatInstallCommand(input) {
70
140
  ].join(" ");
71
141
  }
72
142
  export function verificationNextStep(hostType) {
73
- const hostLabel = hostType === "claude_code" ? "Claude Code" : "Codex";
143
+ const hostLabel = hostType === "claude_code" ? "Claude Code" : hostType === "cursor" ? "Cursor" : "Codex";
74
144
  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.`;
75
145
  }
76
146
  export function installHostConfig(input) {
@@ -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.4",
4
4
  "private": false,
5
5
  "description": "Ask The W MCP connector for capturing compact coding-agent session signals.",
6
6
  "type": "module",