@mushi-mushi/mcp 0.16.2 → 0.17.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/dist/clients.d.ts +99 -0
- package/dist/clients.js +250 -0
- package/dist/index.js +44 -13
- package/dist/server.js +44 -13
- package/package.json +6 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FILE: packages/mcp/src/clients.ts
|
|
3
|
+
* PURPOSE: Shared, pure (React-free) client registry + install-config builders for
|
|
4
|
+
* every supported AI agent / IDE client.
|
|
5
|
+
*
|
|
6
|
+
* OVERVIEW:
|
|
7
|
+
* - Exports `MCP_CLIENTS: McpClientDef[]` — one entry per supported client.
|
|
8
|
+
* - Each entry carries metadata (id, label, method, transport) and a pure `build()`
|
|
9
|
+
* function that returns the install artifact (deeplink URL, JSON config, or CLI command).
|
|
10
|
+
* - Both the admin console ConnectStudio and the public docs /connect landing consume
|
|
11
|
+
* this module — the single source of truth so the two surfaces never drift.
|
|
12
|
+
*
|
|
13
|
+
* DEPENDENCIES:
|
|
14
|
+
* - feature-groups.ts (DEFAULT_FEATURE_GROUPS, featuresQueryString, appendFeaturesToUrl)
|
|
15
|
+
* - branding.ts (MUSHI_ICON_PNG_URL)
|
|
16
|
+
*
|
|
17
|
+
* USAGE:
|
|
18
|
+
* import { MCP_CLIENTS } from '@mushi-mushi/mcp/clients'
|
|
19
|
+
* const cursor = MCP_CLIENTS.find(c => c.id === 'cursor')!
|
|
20
|
+
* const result = cursor.build({ projectId, projectName, apiKey, endpoint, mcpHttpUrl })
|
|
21
|
+
* // result.kind === 'deeplink' → window.open(result.url)
|
|
22
|
+
*
|
|
23
|
+
* CLIENT INSTALL MATRIX (verified Jun 2026):
|
|
24
|
+
* - cursor / vscode / vscode-insiders — one-click deeplink
|
|
25
|
+
* - windsurf — copy-config to ~/.codeium/windsurf/mcp_config.json
|
|
26
|
+
* (uses `serverUrl`, NOT `url` — common footgun)
|
|
27
|
+
* - cline — copy-config to cline_mcp_settings.json
|
|
28
|
+
* - claude-code — CLI command: claude mcp add --transport http …
|
|
29
|
+
* - claude-desktop — copy-config to claude_desktop_config.json
|
|
30
|
+
* - zed — copy-config to ~/.config/zed/settings.json
|
|
31
|
+
* - any — hosted Streamable HTTP URL + Bearer key
|
|
32
|
+
*
|
|
33
|
+
* NOTES:
|
|
34
|
+
* - All builders are pure functions — no side-effects, no DOM, no React.
|
|
35
|
+
* - Cursor base64-encodes the config object without a `name` key in the payload.
|
|
36
|
+
* - VS Code includes `type:'stdio'` in the config and `name` as a separate query param.
|
|
37
|
+
* - Windsurf uses `serverUrl` for remote HTTP (NOT `url`) — encoding both would break it.
|
|
38
|
+
* - The `cliIde` field drives `mushi setup --ide <cliIde>` in the CLI lane.
|
|
39
|
+
*/
|
|
40
|
+
interface StdioConfig {
|
|
41
|
+
command: string;
|
|
42
|
+
args: string[];
|
|
43
|
+
env: Record<string, string>;
|
|
44
|
+
icon?: string;
|
|
45
|
+
}
|
|
46
|
+
interface HttpConfig {
|
|
47
|
+
type: 'http';
|
|
48
|
+
url: string;
|
|
49
|
+
headers: Record<string, string>;
|
|
50
|
+
icon?: string;
|
|
51
|
+
}
|
|
52
|
+
type McpClientId = 'cursor' | 'vscode' | 'vscode-insiders' | 'windsurf' | 'cline' | 'claude-code' | 'claude-desktop' | 'zed' | 'any';
|
|
53
|
+
/** The install method determines which UI treatment the console/docs page renders. */
|
|
54
|
+
type McpInstallMethod = 'deeplink' | 'config-json' | 'cli-command' | 'remote-url';
|
|
55
|
+
interface McpBuildInput {
|
|
56
|
+
/** Mushi project UUID — optional when building org-level configs. */
|
|
57
|
+
projectId?: string;
|
|
58
|
+
projectName: string;
|
|
59
|
+
apiKey: string;
|
|
60
|
+
/** Supabase edge function base URL (`MUSHI_API_ENDPOINT`). */
|
|
61
|
+
endpoint: string;
|
|
62
|
+
/** Hosted Streamable HTTP MCP URL (`RESOLVED_MCP_HTTP_URL`). */
|
|
63
|
+
mcpHttpUrl: string;
|
|
64
|
+
}
|
|
65
|
+
type McpBuildResult = {
|
|
66
|
+
kind: 'deeplink';
|
|
67
|
+
url: string;
|
|
68
|
+
} | {
|
|
69
|
+
kind: 'config';
|
|
70
|
+
filePath: string;
|
|
71
|
+
json: string;
|
|
72
|
+
} | {
|
|
73
|
+
kind: 'command';
|
|
74
|
+
text: string;
|
|
75
|
+
} | {
|
|
76
|
+
kind: 'remote-url';
|
|
77
|
+
url: string;
|
|
78
|
+
headerSnippet: string;
|
|
79
|
+
};
|
|
80
|
+
interface McpClientDef {
|
|
81
|
+
id: McpClientId;
|
|
82
|
+
label: string;
|
|
83
|
+
/** Short description shown in the client picker. */
|
|
84
|
+
description: string;
|
|
85
|
+
method: McpInstallMethod;
|
|
86
|
+
transport: 'stdio' | 'http';
|
|
87
|
+
/** Drives `mushi setup --ide <cliIde>` in the CLI lane. */
|
|
88
|
+
cliIde?: 'cursor' | 'claude' | 'continue' | 'zed';
|
|
89
|
+
build(input: McpBuildInput): McpBuildResult;
|
|
90
|
+
}
|
|
91
|
+
/** Build a stable, unique, human-readable MCP server slug for a project. */
|
|
92
|
+
declare function projectServerName(projectId: string, projectName: string): string;
|
|
93
|
+
declare function buildStdioConfigObj(projectId: string | undefined, apiKey: string, apiEndpoint: string, features?: readonly string[]): StdioConfig;
|
|
94
|
+
declare function buildHttpConfigObj(projectId: string | undefined, apiKey: string, mcpHttpUrl: string, features?: readonly string[]): HttpConfig;
|
|
95
|
+
declare const MCP_CLIENTS: McpClientDef[];
|
|
96
|
+
/** Look up a client by id — throws if not found. */
|
|
97
|
+
declare function getMcpClient(id: McpClientId): McpClientDef;
|
|
98
|
+
|
|
99
|
+
export { MCP_CLIENTS, type McpBuildInput, type McpBuildResult, type McpClientDef, type McpClientId, type McpInstallMethod, buildHttpConfigObj as buildRawHttpConfig, buildStdioConfigObj as buildRawStdioConfig, getMcpClient, projectServerName };
|
package/dist/clients.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MUSHI_ICON_PNG_URL
|
|
3
|
+
} from "./chunk-EHB2BGWX.js";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_FEATURE_GROUPS,
|
|
6
|
+
appendFeaturesToUrl,
|
|
7
|
+
featuresQueryString
|
|
8
|
+
} from "./chunk-ZX3XIJ7S.js";
|
|
9
|
+
|
|
10
|
+
// src/clients.ts
|
|
11
|
+
function projectServerName(projectId, projectName) {
|
|
12
|
+
const nameSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 22);
|
|
13
|
+
const idSuffix = projectId.replace(/-/g, "").slice(0, 6);
|
|
14
|
+
return `mushi-${nameSlug}-${idSuffix}`;
|
|
15
|
+
}
|
|
16
|
+
function buildStdioConfigObj(projectId, apiKey, apiEndpoint, features = DEFAULT_FEATURE_GROUPS) {
|
|
17
|
+
const env = {
|
|
18
|
+
MUSHI_API_ENDPOINT: apiEndpoint,
|
|
19
|
+
MUSHI_API_KEY: apiKey,
|
|
20
|
+
MUSHI_FEATURES: featuresQueryString(features)
|
|
21
|
+
};
|
|
22
|
+
if (projectId) env.MUSHI_PROJECT_ID = projectId;
|
|
23
|
+
return {
|
|
24
|
+
command: "npx",
|
|
25
|
+
args: ["-y", "@mushi-mushi/mcp@latest"],
|
|
26
|
+
env,
|
|
27
|
+
icon: MUSHI_ICON_PNG_URL
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function buildHttpConfigObj(projectId, apiKey, mcpHttpUrl, features = DEFAULT_FEATURE_GROUPS) {
|
|
31
|
+
const url = appendFeaturesToUrl(mcpHttpUrl, features);
|
|
32
|
+
const headers = {
|
|
33
|
+
Authorization: `Bearer ${apiKey}`,
|
|
34
|
+
"X-Mushi-Api-Key": apiKey
|
|
35
|
+
};
|
|
36
|
+
if (projectId) headers["X-Mushi-Project-Id"] = projectId;
|
|
37
|
+
return { type: "http", url, headers, icon: MUSHI_ICON_PNG_URL };
|
|
38
|
+
}
|
|
39
|
+
function encodeCursorDeeplink(name, config) {
|
|
40
|
+
const encoded = btoa(JSON.stringify(config));
|
|
41
|
+
return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(name)}&config=${encodeURIComponent(encoded)}`;
|
|
42
|
+
}
|
|
43
|
+
function toVsCodeConfig(config) {
|
|
44
|
+
if ("type" in config && config.type === "http") return config;
|
|
45
|
+
return { type: "stdio", ...config };
|
|
46
|
+
}
|
|
47
|
+
function encodeVsCodeDeeplink(scheme, name, config) {
|
|
48
|
+
const withType = toVsCodeConfig(config);
|
|
49
|
+
return `${scheme}:mcp/install?name=${encodeURIComponent(name)}&config=${encodeURIComponent(JSON.stringify(withType))}`;
|
|
50
|
+
}
|
|
51
|
+
var MCP_CLIENTS = [
|
|
52
|
+
// ── Cursor ──────────────────────────────────────────────────────────────────
|
|
53
|
+
{
|
|
54
|
+
id: "cursor",
|
|
55
|
+
label: "Cursor",
|
|
56
|
+
description: "One-click install into your project's .cursor/mcp.json",
|
|
57
|
+
method: "deeplink",
|
|
58
|
+
transport: "stdio",
|
|
59
|
+
cliIde: "cursor",
|
|
60
|
+
build({ projectId, projectName, apiKey, endpoint }) {
|
|
61
|
+
const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
|
|
62
|
+
const config = buildStdioConfigObj(projectId, apiKey, endpoint);
|
|
63
|
+
return { kind: "deeplink", url: encodeCursorDeeplink(name, config) };
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
// ── VS Code ──────────────────────────────────────────────────────────────────
|
|
67
|
+
{
|
|
68
|
+
id: "vscode",
|
|
69
|
+
label: "VS Code",
|
|
70
|
+
description: "One-click install via VS Code MCP extension",
|
|
71
|
+
method: "deeplink",
|
|
72
|
+
transport: "stdio",
|
|
73
|
+
build({ projectId, projectName, apiKey, endpoint }) {
|
|
74
|
+
const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
|
|
75
|
+
const config = buildStdioConfigObj(projectId, apiKey, endpoint);
|
|
76
|
+
return { kind: "deeplink", url: encodeVsCodeDeeplink("vscode", name, config) };
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
// ── VS Code Insiders ─────────────────────────────────────────────────────────
|
|
80
|
+
{
|
|
81
|
+
id: "vscode-insiders",
|
|
82
|
+
label: "VS Code Insiders",
|
|
83
|
+
description: "One-click install via VS Code Insiders MCP extension",
|
|
84
|
+
method: "deeplink",
|
|
85
|
+
transport: "stdio",
|
|
86
|
+
build({ projectId, projectName, apiKey, endpoint }) {
|
|
87
|
+
const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
|
|
88
|
+
const config = buildStdioConfigObj(projectId, apiKey, endpoint);
|
|
89
|
+
return { kind: "deeplink", url: encodeVsCodeDeeplink("vscode-insiders", name, config) };
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
// ── Windsurf ─────────────────────────────────────────────────────────────────
|
|
93
|
+
{
|
|
94
|
+
id: "windsurf",
|
|
95
|
+
label: "Windsurf",
|
|
96
|
+
description: "Copy config into ~/.codeium/windsurf/mcp_config.json",
|
|
97
|
+
method: "config-json",
|
|
98
|
+
transport: "http",
|
|
99
|
+
build({ projectId, projectName, apiKey, mcpHttpUrl }) {
|
|
100
|
+
const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
|
|
101
|
+
const url = appendFeaturesToUrl(mcpHttpUrl, DEFAULT_FEATURE_GROUPS);
|
|
102
|
+
const headers = {
|
|
103
|
+
Authorization: `Bearer ${apiKey}`,
|
|
104
|
+
"X-Mushi-Api-Key": apiKey
|
|
105
|
+
};
|
|
106
|
+
if (projectId) headers["X-Mushi-Project-Id"] = projectId;
|
|
107
|
+
const windsurfConfig = { type: "http", serverUrl: url, headers };
|
|
108
|
+
const configJson = JSON.stringify(
|
|
109
|
+
{ mcpServers: { [name]: windsurfConfig } },
|
|
110
|
+
null,
|
|
111
|
+
2
|
|
112
|
+
);
|
|
113
|
+
return {
|
|
114
|
+
kind: "config",
|
|
115
|
+
filePath: "~/.codeium/windsurf/mcp_config.json",
|
|
116
|
+
json: configJson
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
// ── Cline ────────────────────────────────────────────────────────────────────
|
|
121
|
+
{
|
|
122
|
+
id: "cline",
|
|
123
|
+
label: "Cline",
|
|
124
|
+
description: "Copy config into cline_mcp_settings.json",
|
|
125
|
+
method: "config-json",
|
|
126
|
+
transport: "stdio",
|
|
127
|
+
build({ projectId, projectName, apiKey, endpoint }) {
|
|
128
|
+
const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
|
|
129
|
+
const env = {
|
|
130
|
+
MUSHI_API_ENDPOINT: endpoint,
|
|
131
|
+
MUSHI_API_KEY: apiKey
|
|
132
|
+
};
|
|
133
|
+
if (projectId) env.MUSHI_PROJECT_ID = projectId;
|
|
134
|
+
const clineConfig = {
|
|
135
|
+
mcpServers: {
|
|
136
|
+
[name]: { command: "npx", args: ["-y", "@mushi-mushi/mcp@latest"], env }
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
kind: "config",
|
|
141
|
+
// Cline stores config in the VS Code extension's globalStorage
|
|
142
|
+
filePath: "~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
|
|
143
|
+
json: JSON.stringify(clineConfig, null, 2)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
// ── Claude Code ──────────────────────────────────────────────────────────────
|
|
148
|
+
{
|
|
149
|
+
id: "claude-code",
|
|
150
|
+
label: "Claude Code",
|
|
151
|
+
description: "Add via claude CLI command (HTTP transport)",
|
|
152
|
+
method: "cli-command",
|
|
153
|
+
transport: "http",
|
|
154
|
+
cliIde: "claude",
|
|
155
|
+
build({ projectId, projectName, apiKey, mcpHttpUrl }) {
|
|
156
|
+
const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
|
|
157
|
+
const url = appendFeaturesToUrl(mcpHttpUrl, DEFAULT_FEATURE_GROUPS);
|
|
158
|
+
const cmd = `claude mcp add --transport http ${name} "${url}" --header "Authorization: Bearer ${apiKey}"`;
|
|
159
|
+
return { kind: "command", text: cmd };
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
// ── Claude Desktop ───────────────────────────────────────────────────────────
|
|
163
|
+
{
|
|
164
|
+
id: "claude-desktop",
|
|
165
|
+
label: "Claude Desktop",
|
|
166
|
+
description: "Copy config into claude_desktop_config.json",
|
|
167
|
+
method: "config-json",
|
|
168
|
+
transport: "stdio",
|
|
169
|
+
build({ projectId, projectName, apiKey, endpoint }) {
|
|
170
|
+
const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
|
|
171
|
+
const env = {
|
|
172
|
+
MUSHI_API_ENDPOINT: endpoint,
|
|
173
|
+
MUSHI_API_KEY: apiKey
|
|
174
|
+
};
|
|
175
|
+
if (projectId) env.MUSHI_PROJECT_ID = projectId;
|
|
176
|
+
const desktopConfig = {
|
|
177
|
+
mcpServers: {
|
|
178
|
+
[name]: { command: "npx", args: ["-y", "@mushi-mushi/mcp@latest"], env }
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
kind: "config",
|
|
183
|
+
filePath: "~/Library/Application Support/Claude/claude_desktop_config.json",
|
|
184
|
+
json: JSON.stringify(desktopConfig, null, 2)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
// ── Zed ──────────────────────────────────────────────────────────────────────
|
|
189
|
+
{
|
|
190
|
+
id: "zed",
|
|
191
|
+
label: "Zed",
|
|
192
|
+
description: "Copy config into ~/.config/zed/settings.json",
|
|
193
|
+
method: "config-json",
|
|
194
|
+
transport: "stdio",
|
|
195
|
+
cliIde: "zed",
|
|
196
|
+
build({ projectId, projectName, apiKey, endpoint }) {
|
|
197
|
+
const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
|
|
198
|
+
const env = {
|
|
199
|
+
MUSHI_API_ENDPOINT: endpoint,
|
|
200
|
+
MUSHI_API_KEY: apiKey
|
|
201
|
+
};
|
|
202
|
+
if (projectId) env.MUSHI_PROJECT_ID = projectId;
|
|
203
|
+
const zedConfig = {
|
|
204
|
+
context_servers: {
|
|
205
|
+
[name]: {
|
|
206
|
+
command: {
|
|
207
|
+
path: "npx",
|
|
208
|
+
args: ["-y", "@mushi-mushi/mcp@latest"],
|
|
209
|
+
env
|
|
210
|
+
},
|
|
211
|
+
settings: {}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
kind: "config",
|
|
217
|
+
filePath: "~/.config/zed/settings.json",
|
|
218
|
+
json: JSON.stringify(zedConfig, null, 2)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
// ── Any MCP client ───────────────────────────────────────────────────────────
|
|
223
|
+
{
|
|
224
|
+
id: "any",
|
|
225
|
+
label: "Any MCP client",
|
|
226
|
+
description: "Streamable HTTP URL \u2014 works with OpenClaw, Hermes, Gemini CLI, ChatGPT, and more",
|
|
227
|
+
method: "remote-url",
|
|
228
|
+
transport: "http",
|
|
229
|
+
build({ projectId, apiKey, mcpHttpUrl }) {
|
|
230
|
+
const url = appendFeaturesToUrl(mcpHttpUrl, DEFAULT_FEATURE_GROUPS);
|
|
231
|
+
const headerSnippet = [
|
|
232
|
+
`Authorization: Bearer ${apiKey}`,
|
|
233
|
+
projectId ? `X-Mushi-Project-Id: ${projectId}` : ""
|
|
234
|
+
].filter(Boolean).join("\n");
|
|
235
|
+
return { kind: "remote-url", url, headerSnippet };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
];
|
|
239
|
+
function getMcpClient(id) {
|
|
240
|
+
const client = MCP_CLIENTS.find((c) => c.id === id);
|
|
241
|
+
if (!client) throw new Error(`Unknown MCP client id: ${id}`);
|
|
242
|
+
return client;
|
|
243
|
+
}
|
|
244
|
+
export {
|
|
245
|
+
MCP_CLIENTS,
|
|
246
|
+
buildHttpConfigObj as buildRawHttpConfig,
|
|
247
|
+
buildStdioConfigObj as buildRawStdioConfig,
|
|
248
|
+
getMcpClient,
|
|
249
|
+
projectServerName
|
|
250
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -933,7 +933,16 @@ function createMushiServer(config) {
|
|
|
933
933
|
return body;
|
|
934
934
|
}
|
|
935
935
|
async function resolveProjectId(explicitId) {
|
|
936
|
-
if (explicitId)
|
|
936
|
+
if (explicitId) {
|
|
937
|
+
if (projectId && explicitId !== projectId) {
|
|
938
|
+
throw new MushiApiError(
|
|
939
|
+
400,
|
|
940
|
+
"PROJECT_SCOPE_MISMATCH",
|
|
941
|
+
`This MCP entry is bound to project ${projectId}. Either pass projectId="${projectId}", add a second MCP server entry for ${explicitId} (Console \u2192 Connect \u2192 Add to Cursor while that project is selected), or run \`mushi setup --all-projects --ide cursor\` for one entry per repo.`
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
return explicitId;
|
|
945
|
+
}
|
|
937
946
|
if (projectId) return projectId;
|
|
938
947
|
try {
|
|
939
948
|
const data = await apiCall(
|
|
@@ -964,6 +973,11 @@ function createMushiServer(config) {
|
|
|
964
973
|
);
|
|
965
974
|
}
|
|
966
975
|
}
|
|
976
|
+
async function projectScopeHeaders(explicitId) {
|
|
977
|
+
const pid = await resolveProjectId(explicitId ?? void 0);
|
|
978
|
+
const headers = { "X-Mushi-Project-Id": pid };
|
|
979
|
+
return { projectId: pid, headers };
|
|
980
|
+
}
|
|
967
981
|
function jsonText(value) {
|
|
968
982
|
return {
|
|
969
983
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
@@ -1045,11 +1059,15 @@ function createMushiServer(config) {
|
|
|
1045
1059
|
title: titleOf("get_report_detail"),
|
|
1046
1060
|
description: descOf("get_report_detail"),
|
|
1047
1061
|
annotations: annotationsFor("get_report_detail"),
|
|
1048
|
-
inputSchema: {
|
|
1062
|
+
inputSchema: {
|
|
1063
|
+
reportId: z.string().describe("The report UUID"),
|
|
1064
|
+
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
1065
|
+
},
|
|
1049
1066
|
outputSchema: { report: z.unknown() }
|
|
1050
1067
|
},
|
|
1051
1068
|
async (args) => {
|
|
1052
|
-
const
|
|
1069
|
+
const { headers } = await projectScopeHeaders(args.project_id);
|
|
1070
|
+
const data = await apiCall(`/v1/admin/reports/${args.reportId}`, { headers });
|
|
1053
1071
|
const res = jsonResult({ report: data });
|
|
1054
1072
|
return {
|
|
1055
1073
|
...res,
|
|
@@ -1140,7 +1158,10 @@ function createMushiServer(config) {
|
|
|
1140
1158
|
title: titleOf("get_fix_context"),
|
|
1141
1159
|
description: descOf("get_fix_context"),
|
|
1142
1160
|
annotations: annotationsFor("get_fix_context"),
|
|
1143
|
-
inputSchema: {
|
|
1161
|
+
inputSchema: {
|
|
1162
|
+
reportId: z.string().describe("The report UUID to fix"),
|
|
1163
|
+
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
1164
|
+
},
|
|
1144
1165
|
outputSchema: {
|
|
1145
1166
|
report: z.unknown(),
|
|
1146
1167
|
fixPrompt: z.unknown(),
|
|
@@ -1151,7 +1172,10 @@ function createMushiServer(config) {
|
|
|
1151
1172
|
}
|
|
1152
1173
|
},
|
|
1153
1174
|
async (args) => {
|
|
1154
|
-
const
|
|
1175
|
+
const { headers } = await projectScopeHeaders(args.project_id);
|
|
1176
|
+
const report = await apiCall(`/v1/admin/reports/${args.reportId}`, {
|
|
1177
|
+
headers
|
|
1178
|
+
});
|
|
1155
1179
|
return jsonResult({
|
|
1156
1180
|
report,
|
|
1157
1181
|
// Paste-ready fix prompt composed server-side by composeFixPacket()
|
|
@@ -1171,9 +1195,15 @@ function createMushiServer(config) {
|
|
|
1171
1195
|
title: titleOf("get_fix_timeline"),
|
|
1172
1196
|
description: descOf("get_fix_timeline"),
|
|
1173
1197
|
annotations: annotationsFor("get_fix_timeline"),
|
|
1174
|
-
inputSchema: {
|
|
1198
|
+
inputSchema: {
|
|
1199
|
+
fixId: z.string().describe("fix_attempt UUID"),
|
|
1200
|
+
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
1201
|
+
}
|
|
1175
1202
|
},
|
|
1176
|
-
async (args) =>
|
|
1203
|
+
async (args) => {
|
|
1204
|
+
const { headers } = await projectScopeHeaders(args.project_id);
|
|
1205
|
+
return jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`, { headers }));
|
|
1206
|
+
}
|
|
1177
1207
|
);
|
|
1178
1208
|
server.registerTool(
|
|
1179
1209
|
"get_blast_radius",
|
|
@@ -1803,18 +1833,19 @@ function createMushiServer(config) {
|
|
|
1803
1833
|
}
|
|
1804
1834
|
},
|
|
1805
1835
|
async (args) => {
|
|
1806
|
-
const pid = args.project_id
|
|
1836
|
+
const { projectId: pid, headers } = await projectScopeHeaders(args.project_id);
|
|
1807
1837
|
const includeLogs = args.include_logs !== false;
|
|
1808
1838
|
const [reportRes, evidenceRes, similarRes, fixCtxRes, blastRes, logsRes] = await Promise.allSettled([
|
|
1809
|
-
apiCall(`/v1/admin/reports/${args.report_id}
|
|
1810
|
-
apiCall(`/v1/admin/reports/${args.report_id}/timeline
|
|
1839
|
+
apiCall(`/v1/admin/reports/${args.report_id}`, { headers }),
|
|
1840
|
+
apiCall(`/v1/admin/reports/${args.report_id}/timeline`, { headers }),
|
|
1811
1841
|
apiCall(`/v1/admin/reports/similarity`, {
|
|
1812
1842
|
method: "POST",
|
|
1843
|
+
headers,
|
|
1813
1844
|
body: JSON.stringify({ report_id: args.report_id })
|
|
1814
1845
|
}).catch(() => null),
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
includeLogs
|
|
1846
|
+
apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(() => null),
|
|
1847
|
+
apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`, { headers }).catch(() => null),
|
|
1848
|
+
includeLogs ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`, { headers }).catch(() => null) : Promise.resolve(null)
|
|
1818
1849
|
]);
|
|
1819
1850
|
const report = reportRes.status === "fulfilled" ? reportRes.value : null;
|
|
1820
1851
|
const severity = report ? report.severity : "unknown";
|
package/dist/server.js
CHANGED
|
@@ -199,7 +199,16 @@ function createMushiServer(config) {
|
|
|
199
199
|
return body;
|
|
200
200
|
}
|
|
201
201
|
async function resolveProjectId(explicitId) {
|
|
202
|
-
if (explicitId)
|
|
202
|
+
if (explicitId) {
|
|
203
|
+
if (projectId && explicitId !== projectId) {
|
|
204
|
+
throw new MushiApiError(
|
|
205
|
+
400,
|
|
206
|
+
"PROJECT_SCOPE_MISMATCH",
|
|
207
|
+
`This MCP entry is bound to project ${projectId}. Either pass projectId="${projectId}", add a second MCP server entry for ${explicitId} (Console \u2192 Connect \u2192 Add to Cursor while that project is selected), or run \`mushi setup --all-projects --ide cursor\` for one entry per repo.`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return explicitId;
|
|
211
|
+
}
|
|
203
212
|
if (projectId) return projectId;
|
|
204
213
|
try {
|
|
205
214
|
const data = await apiCall(
|
|
@@ -230,6 +239,11 @@ function createMushiServer(config) {
|
|
|
230
239
|
);
|
|
231
240
|
}
|
|
232
241
|
}
|
|
242
|
+
async function projectScopeHeaders(explicitId) {
|
|
243
|
+
const pid = await resolveProjectId(explicitId ?? void 0);
|
|
244
|
+
const headers = { "X-Mushi-Project-Id": pid };
|
|
245
|
+
return { projectId: pid, headers };
|
|
246
|
+
}
|
|
233
247
|
function jsonText(value) {
|
|
234
248
|
return {
|
|
235
249
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
@@ -311,11 +325,15 @@ function createMushiServer(config) {
|
|
|
311
325
|
title: titleOf("get_report_detail"),
|
|
312
326
|
description: descOf("get_report_detail"),
|
|
313
327
|
annotations: annotationsFor("get_report_detail"),
|
|
314
|
-
inputSchema: {
|
|
328
|
+
inputSchema: {
|
|
329
|
+
reportId: z.string().describe("The report UUID"),
|
|
330
|
+
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
331
|
+
},
|
|
315
332
|
outputSchema: { report: z.unknown() }
|
|
316
333
|
},
|
|
317
334
|
async (args) => {
|
|
318
|
-
const
|
|
335
|
+
const { headers } = await projectScopeHeaders(args.project_id);
|
|
336
|
+
const data = await apiCall(`/v1/admin/reports/${args.reportId}`, { headers });
|
|
319
337
|
const res = jsonResult({ report: data });
|
|
320
338
|
return {
|
|
321
339
|
...res,
|
|
@@ -406,7 +424,10 @@ function createMushiServer(config) {
|
|
|
406
424
|
title: titleOf("get_fix_context"),
|
|
407
425
|
description: descOf("get_fix_context"),
|
|
408
426
|
annotations: annotationsFor("get_fix_context"),
|
|
409
|
-
inputSchema: {
|
|
427
|
+
inputSchema: {
|
|
428
|
+
reportId: z.string().describe("The report UUID to fix"),
|
|
429
|
+
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
430
|
+
},
|
|
410
431
|
outputSchema: {
|
|
411
432
|
report: z.unknown(),
|
|
412
433
|
fixPrompt: z.unknown(),
|
|
@@ -417,7 +438,10 @@ function createMushiServer(config) {
|
|
|
417
438
|
}
|
|
418
439
|
},
|
|
419
440
|
async (args) => {
|
|
420
|
-
const
|
|
441
|
+
const { headers } = await projectScopeHeaders(args.project_id);
|
|
442
|
+
const report = await apiCall(`/v1/admin/reports/${args.reportId}`, {
|
|
443
|
+
headers
|
|
444
|
+
});
|
|
421
445
|
return jsonResult({
|
|
422
446
|
report,
|
|
423
447
|
// Paste-ready fix prompt composed server-side by composeFixPacket()
|
|
@@ -437,9 +461,15 @@ function createMushiServer(config) {
|
|
|
437
461
|
title: titleOf("get_fix_timeline"),
|
|
438
462
|
description: descOf("get_fix_timeline"),
|
|
439
463
|
annotations: annotationsFor("get_fix_timeline"),
|
|
440
|
-
inputSchema: {
|
|
464
|
+
inputSchema: {
|
|
465
|
+
fixId: z.string().describe("fix_attempt UUID"),
|
|
466
|
+
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
467
|
+
}
|
|
441
468
|
},
|
|
442
|
-
async (args) =>
|
|
469
|
+
async (args) => {
|
|
470
|
+
const { headers } = await projectScopeHeaders(args.project_id);
|
|
471
|
+
return jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`, { headers }));
|
|
472
|
+
}
|
|
443
473
|
);
|
|
444
474
|
server.registerTool(
|
|
445
475
|
"get_blast_radius",
|
|
@@ -1069,18 +1099,19 @@ function createMushiServer(config) {
|
|
|
1069
1099
|
}
|
|
1070
1100
|
},
|
|
1071
1101
|
async (args) => {
|
|
1072
|
-
const pid = args.project_id
|
|
1102
|
+
const { projectId: pid, headers } = await projectScopeHeaders(args.project_id);
|
|
1073
1103
|
const includeLogs = args.include_logs !== false;
|
|
1074
1104
|
const [reportRes, evidenceRes, similarRes, fixCtxRes, blastRes, logsRes] = await Promise.allSettled([
|
|
1075
|
-
apiCall(`/v1/admin/reports/${args.report_id}
|
|
1076
|
-
apiCall(`/v1/admin/reports/${args.report_id}/timeline
|
|
1105
|
+
apiCall(`/v1/admin/reports/${args.report_id}`, { headers }),
|
|
1106
|
+
apiCall(`/v1/admin/reports/${args.report_id}/timeline`, { headers }),
|
|
1077
1107
|
apiCall(`/v1/admin/reports/similarity`, {
|
|
1078
1108
|
method: "POST",
|
|
1109
|
+
headers,
|
|
1079
1110
|
body: JSON.stringify({ report_id: args.report_id })
|
|
1080
1111
|
}).catch(() => null),
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
includeLogs
|
|
1112
|
+
apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(() => null),
|
|
1113
|
+
apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`, { headers }).catch(() => null),
|
|
1114
|
+
includeLogs ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`, { headers }).catch(() => null) : Promise.resolve(null)
|
|
1084
1115
|
]);
|
|
1085
1116
|
const report = reportRes.status === "fulfilled" ? reportRes.value : null;
|
|
1086
1117
|
const severity = report ? report.severity : "unknown";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/mcp",
|
|
3
3
|
"mcpName": "io.github.kensaurus/mushi-mushi",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.17.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "MCP server for Mushi Mushi — triage user-felt bugs, pull evidence (screenshots, console logs, network requests), check blast radius, and dispatch fix PRs, all from your AI coding agent",
|
|
7
7
|
"type": "module",
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"./feature-groups": {
|
|
26
26
|
"types": "./dist/feature-groups.d.ts",
|
|
27
27
|
"import": "./dist/feature-groups.js"
|
|
28
|
+
},
|
|
29
|
+
"./clients": {
|
|
30
|
+
"types": "./dist/clients.d.ts",
|
|
31
|
+
"import": "./dist/clients.js"
|
|
28
32
|
}
|
|
29
33
|
},
|
|
30
34
|
"main": "./dist/index.js",
|
|
@@ -43,7 +47,7 @@
|
|
|
43
47
|
"dependencies": {
|
|
44
48
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
49
|
"zod": "^4.4.3",
|
|
46
|
-
"@mushi-mushi/core": "^1.
|
|
50
|
+
"@mushi-mushi/core": "^1.21.0"
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
49
53
|
"@types/node": "^22.19.21",
|