@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 +16 -1
- package/dist/install.d.ts +5 -0
- package/dist/install.js +92 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,7 +75,22 @@ To skip this behavior, pass:
|
|
|
75
75
|
|
|
76
76
|
## Configuration
|
|
77
77
|
|
|
78
|
-
The installer writes
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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,
|