@mgsoftwarebv/mg-dashboard-mcp 7.0.13 → 7.0.15
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/index.js +349 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -683,10 +683,10 @@ var TRIGGER_TOOL_MODULE_MAP = {
|
|
|
683
683
|
"trigger-run": "ci_cd"
|
|
684
684
|
};
|
|
685
685
|
async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
686
|
-
const
|
|
686
|
+
const sql25 = `SELECT re.\\"apiKey\\" FROM \\"RuntimeEnvironment\\" re JOIN \\"Project\\" p ON re.\\"projectId\\" = p.id WHERE p.slug='${projectSlug}' AND re.slug='prod' LIMIT 1`;
|
|
687
687
|
const cmd = [
|
|
688
688
|
`PORT=$(docker port "${WA_CONTAINER}" 3000/tcp 2>/dev/null | head -1 | sed 's/.*://')`,
|
|
689
|
-
`KEY=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
689
|
+
`KEY=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql25}" 2>/dev/null | tr -d '[:space:]')`,
|
|
690
690
|
'echo "$PORT|$KEY"'
|
|
691
691
|
].join(" && ");
|
|
692
692
|
const result = await sshExec2(conn, cmd, proxy);
|
|
@@ -707,8 +707,8 @@ async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
|
|
|
707
707
|
return { port, apiKey: apiKey2 };
|
|
708
708
|
}
|
|
709
709
|
async function fetchRunLogs(runId, conn, proxy, sshExec2) {
|
|
710
|
-
const
|
|
711
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
710
|
+
const sql25 = `SELECT level, message, \\"isError\\", \\"createdAt\\" FROM \\"TaskEvent\\" WHERE \\"runId\\" = '${runId}' AND level IN ('INFO','WARN','ERROR','DEBUG','LOG','TRACE') ORDER BY \\"startTime\\" ASC LIMIT 200`;
|
|
711
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql25}" 2>/dev/null`;
|
|
712
712
|
const result = await sshExec2(conn, cmd, proxy);
|
|
713
713
|
const output = result.stdout.trim();
|
|
714
714
|
if (!output) return "";
|
|
@@ -790,8 +790,8 @@ async function handleTriggerTool(name, args2, deps) {
|
|
|
790
790
|
switch (name) {
|
|
791
791
|
// -----------------------------------------------------------------
|
|
792
792
|
case "trigger-list": {
|
|
793
|
-
const
|
|
794
|
-
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${
|
|
793
|
+
const sql25 = 'SELECT slug, name FROM \\"Project\\" ORDER BY name';
|
|
794
|
+
const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql25}" 2>/dev/null`;
|
|
795
795
|
const result = await sshExec2(conn, cmd, proxy);
|
|
796
796
|
const output = result.stdout.trim();
|
|
797
797
|
if (!output) {
|
|
@@ -6188,6 +6188,145 @@ async function reconfigureQuickStages(profileId, releaseType, detectedApps) {
|
|
|
6188
6188
|
`);
|
|
6189
6189
|
}
|
|
6190
6190
|
}
|
|
6191
|
+
function normalizeRepoIdentifier(value) {
|
|
6192
|
+
if (!value) return null;
|
|
6193
|
+
let s = value.trim().toLowerCase();
|
|
6194
|
+
if (!s) return null;
|
|
6195
|
+
s = s.replace(/^ssh:\/\/git@github\.com\//, "");
|
|
6196
|
+
s = s.replace(/^git@github\.com:/, "");
|
|
6197
|
+
s = s.replace(/^https?:\/\/(www\.)?github\.com\//, "");
|
|
6198
|
+
s = s.replace(/^github\.com\//, "");
|
|
6199
|
+
s = s.replace(/\.git$/, "");
|
|
6200
|
+
s = s.replace(/[/]+$/, "");
|
|
6201
|
+
const parts = s.split("/").filter(Boolean);
|
|
6202
|
+
if (parts.length >= 2) {
|
|
6203
|
+
s = parts.slice(-2).join("/");
|
|
6204
|
+
}
|
|
6205
|
+
return s || null;
|
|
6206
|
+
}
|
|
6207
|
+
async function queueAgentRun(args2) {
|
|
6208
|
+
const db2 = getDb();
|
|
6209
|
+
const gitMode = args2.gitMode ?? "worktree";
|
|
6210
|
+
const model = args2.model ?? null;
|
|
6211
|
+
const metadata = args2.metadata ?? {};
|
|
6212
|
+
const conversationRows = await db2.execute(sql`
|
|
6213
|
+
INSERT INTO agent_conversation (
|
|
6214
|
+
user_id, host_id, workspace_id, runtime, source, title, status, model, metadata
|
|
6215
|
+
) VALUES (
|
|
6216
|
+
${args2.userId}, ${args2.workspace.host_id}, ${args2.workspace.id},
|
|
6217
|
+
'local', ${args2.source}, ${args2.title}, 'running', ${model},
|
|
6218
|
+
${JSON.stringify(metadata)}::jsonb
|
|
6219
|
+
)
|
|
6220
|
+
RETURNING id
|
|
6221
|
+
`);
|
|
6222
|
+
const conversationId = conversationRows[0]?.id;
|
|
6223
|
+
if (!conversationId) return null;
|
|
6224
|
+
const runRows = await db2.execute(sql`
|
|
6225
|
+
INSERT INTO agent_run (
|
|
6226
|
+
conversation_id, workspace_id, host_id, user_id, runtime, status,
|
|
6227
|
+
prompt, model, git_mode, source
|
|
6228
|
+
) VALUES (
|
|
6229
|
+
${conversationId}, ${args2.workspace.id}, ${args2.workspace.host_id},
|
|
6230
|
+
${args2.userId}, 'local', 'queued', ${args2.prompt}, ${model}, ${gitMode},
|
|
6231
|
+
${args2.source}
|
|
6232
|
+
)
|
|
6233
|
+
RETURNING id
|
|
6234
|
+
`);
|
|
6235
|
+
const runId = runRows[0]?.id;
|
|
6236
|
+
if (!runId) return null;
|
|
6237
|
+
const payload = {
|
|
6238
|
+
runId,
|
|
6239
|
+
conversationId,
|
|
6240
|
+
workspacePath: args2.workspace.path,
|
|
6241
|
+
prompt: args2.prompt,
|
|
6242
|
+
model,
|
|
6243
|
+
gitMode,
|
|
6244
|
+
sdkAgentId: null,
|
|
6245
|
+
contextReplay: null,
|
|
6246
|
+
...metadata
|
|
6247
|
+
};
|
|
6248
|
+
await db2.execute(sql`
|
|
6249
|
+
INSERT INTO agent_host_command (host_id, user_id, run_id, command_type, payload)
|
|
6250
|
+
VALUES (
|
|
6251
|
+
${args2.workspace.host_id}, ${args2.userId}, ${runId}, 'start_run',
|
|
6252
|
+
${JSON.stringify(payload)}::jsonb
|
|
6253
|
+
)
|
|
6254
|
+
`);
|
|
6255
|
+
return { conversationId, runId };
|
|
6256
|
+
}
|
|
6257
|
+
function toTarget(row) {
|
|
6258
|
+
return {
|
|
6259
|
+
workspaceId: row.workspace_id,
|
|
6260
|
+
hostId: row.host_id,
|
|
6261
|
+
hostName: row.host_name,
|
|
6262
|
+
hostOnline: row.host_online === true,
|
|
6263
|
+
ownerUserId: row.owner_user_id,
|
|
6264
|
+
ownerName: row.owner_name,
|
|
6265
|
+
ownerEmail: row.owner_email,
|
|
6266
|
+
path: row.path,
|
|
6267
|
+
name: row.name,
|
|
6268
|
+
gitRepository: row.git_repository,
|
|
6269
|
+
gitBranch: row.git_branch,
|
|
6270
|
+
defaultGitMode: row.default_git_mode === "in_place" ? "in_place" : "worktree",
|
|
6271
|
+
lastSyncedAt: row.last_synced_at
|
|
6272
|
+
};
|
|
6273
|
+
}
|
|
6274
|
+
async function listRemoteWorkspaces() {
|
|
6275
|
+
const rows = await getDb().execute(sql`
|
|
6276
|
+
SELECT
|
|
6277
|
+
w.id AS workspace_id,
|
|
6278
|
+
w.host_id,
|
|
6279
|
+
h.name AS host_name,
|
|
6280
|
+
(h.last_seen_at IS NOT NULL AND h.last_seen_at > now() - interval '45 seconds')
|
|
6281
|
+
AS host_online,
|
|
6282
|
+
w.user_id AS owner_user_id,
|
|
6283
|
+
u.full_name AS owner_name,
|
|
6284
|
+
u.email AS owner_email,
|
|
6285
|
+
w.path, w.name, w.git_repository, w.git_branch,
|
|
6286
|
+
w.default_git_mode, w.last_synced_at
|
|
6287
|
+
FROM agent_workspace w
|
|
6288
|
+
INNER JOIN agent_host h ON h.id = w.host_id
|
|
6289
|
+
LEFT JOIN "user" u ON u.id = w.user_id
|
|
6290
|
+
WHERE w.status = 'active' AND h.revoked_at IS NULL
|
|
6291
|
+
ORDER BY host_online DESC, w.last_synced_at DESC NULLS LAST
|
|
6292
|
+
`);
|
|
6293
|
+
return Array.from(rows).map(toTarget);
|
|
6294
|
+
}
|
|
6295
|
+
function matchesWho(target, who) {
|
|
6296
|
+
const needle = who.trim().toLowerCase();
|
|
6297
|
+
if (!needle) return true;
|
|
6298
|
+
const name = (target.ownerName ?? "").toLowerCase();
|
|
6299
|
+
const email = (target.ownerEmail ?? "").toLowerCase();
|
|
6300
|
+
return name.includes(needle) || email.includes(needle);
|
|
6301
|
+
}
|
|
6302
|
+
function matchesProject(target, project) {
|
|
6303
|
+
const repoTarget = normalizeRepoIdentifier(project);
|
|
6304
|
+
if (repoTarget && normalizeRepoIdentifier(target.gitRepository) === repoTarget) {
|
|
6305
|
+
return true;
|
|
6306
|
+
}
|
|
6307
|
+
const needle = project.trim().toLowerCase();
|
|
6308
|
+
if (!needle) return false;
|
|
6309
|
+
return target.name.toLowerCase().includes(needle) || target.path.toLowerCase().includes(needle);
|
|
6310
|
+
}
|
|
6311
|
+
async function resolveRemoteTarget(args2) {
|
|
6312
|
+
const online = (await listRemoteWorkspaces()).filter((t) => t.hostOnline);
|
|
6313
|
+
let matches = online.filter((t) => matchesProject(t, args2.project));
|
|
6314
|
+
if (args2.who) {
|
|
6315
|
+
matches = matches.filter((t) => matchesWho(t, args2.who));
|
|
6316
|
+
}
|
|
6317
|
+
if (matches.length === 0) {
|
|
6318
|
+
return { ok: false, reason: "none", candidates: online };
|
|
6319
|
+
}
|
|
6320
|
+
if (matches.length === 1) {
|
|
6321
|
+
return { ok: true, target: matches[0] };
|
|
6322
|
+
}
|
|
6323
|
+
if (args2.branch) {
|
|
6324
|
+
const onBranch = matches.filter((t) => t.gitBranch === args2.branch);
|
|
6325
|
+
if (onBranch.length === 1) return { ok: true, target: onBranch[0] };
|
|
6326
|
+
if (onBranch.length > 1) matches = onBranch;
|
|
6327
|
+
}
|
|
6328
|
+
return { ok: false, reason: "ambiguous", candidates: matches };
|
|
6329
|
+
}
|
|
6191
6330
|
var args = process.argv.slice(2);
|
|
6192
6331
|
function getArg2(name) {
|
|
6193
6332
|
return args.find((a) => a.startsWith(`--${name}=`))?.split("=").slice(1).join("=");
|
|
@@ -6370,7 +6509,9 @@ var MODULE_KEYS = [
|
|
|
6370
6509
|
"wiki",
|
|
6371
6510
|
"ci_cd",
|
|
6372
6511
|
"domains",
|
|
6373
|
-
"settings"
|
|
6512
|
+
"settings",
|
|
6513
|
+
"cursor_remote",
|
|
6514
|
+
"code_battle"
|
|
6374
6515
|
];
|
|
6375
6516
|
var FULL_PERMISSIONS = {
|
|
6376
6517
|
modules: Object.fromEntries(
|
|
@@ -6433,8 +6574,15 @@ var TOOL_MODULE_MAP = {
|
|
|
6433
6574
|
"domain-list": "domains",
|
|
6434
6575
|
"dns-list": "domains",
|
|
6435
6576
|
"dns-record": "domains",
|
|
6577
|
+
"cursor-remote-list": "cursor_remote",
|
|
6578
|
+
"cursor-remote-run": "cursor_remote",
|
|
6436
6579
|
...TRIGGER_TOOL_MODULE_MAP
|
|
6437
6580
|
};
|
|
6581
|
+
function normalizeAllowedTools(value) {
|
|
6582
|
+
if (!Array.isArray(value)) return null;
|
|
6583
|
+
const tools = value.filter((t) => typeof t === "string").map((t) => t.trim()).filter(Boolean);
|
|
6584
|
+
return tools.length > 0 ? tools : null;
|
|
6585
|
+
}
|
|
6438
6586
|
var authContext = null;
|
|
6439
6587
|
var currentClientIp;
|
|
6440
6588
|
function extractClientIp(req) {
|
|
@@ -6463,7 +6611,7 @@ async function validateApiKey(key) {
|
|
|
6463
6611
|
return null;
|
|
6464
6612
|
}
|
|
6465
6613
|
const apiKeyRows = await db.execute(sql`
|
|
6466
|
-
SELECT id, name, created_by, allowed_server_ids, is_active, expires_at
|
|
6614
|
+
SELECT id, name, created_by, allowed_server_ids, allowed_tools, is_active, expires_at
|
|
6467
6615
|
FROM dashboard_mcp_api_key
|
|
6468
6616
|
WHERE api_key_hash = ${keyHash} AND is_active = true
|
|
6469
6617
|
LIMIT 1
|
|
@@ -6515,6 +6663,7 @@ async function validateApiKey(key) {
|
|
|
6515
6663
|
apiKeyName: data.name || "Unknown",
|
|
6516
6664
|
userId: data.created_by,
|
|
6517
6665
|
allowedServerIds,
|
|
6666
|
+
allowedTools: normalizeAllowedTools(data.allowed_tools),
|
|
6518
6667
|
permissions,
|
|
6519
6668
|
roleName
|
|
6520
6669
|
};
|
|
@@ -6709,7 +6858,7 @@ async function validateSshKey(pubkeyPathInput, expectedApiKeyId) {
|
|
|
6709
6858
|
return null;
|
|
6710
6859
|
}
|
|
6711
6860
|
const apiRows = await db.execute(sql`
|
|
6712
|
-
SELECT id, name, created_by, allowed_server_ids, is_active, expires_at
|
|
6861
|
+
SELECT id, name, created_by, allowed_server_ids, allowed_tools, is_active, expires_at
|
|
6713
6862
|
FROM dashboard_mcp_api_key
|
|
6714
6863
|
WHERE id = ${keyRow.api_key_id} AND is_active = true
|
|
6715
6864
|
LIMIT 1
|
|
@@ -6763,6 +6912,7 @@ async function validateSshKey(pubkeyPathInput, expectedApiKeyId) {
|
|
|
6763
6912
|
apiKeyName: `${apiRow.name || "Unknown"} (ssh: ${keyRow.name})`,
|
|
6764
6913
|
userId: apiRow.created_by,
|
|
6765
6914
|
allowedServerIds,
|
|
6915
|
+
allowedTools: normalizeAllowedTools(apiRow.allowed_tools),
|
|
6766
6916
|
permissions,
|
|
6767
6917
|
roleName
|
|
6768
6918
|
};
|
|
@@ -8991,11 +9141,11 @@ CREATE TABLE IF NOT EXISTS _mcp_migrations (
|
|
|
8991
9141
|
applied_by TEXT
|
|
8992
9142
|
);
|
|
8993
9143
|
`.trim();
|
|
8994
|
-
function normaliseMigrationSql(
|
|
8995
|
-
return
|
|
9144
|
+
function normaliseMigrationSql(sql25) {
|
|
9145
|
+
return sql25.replace(/\r\n/g, "\n").trim() + "\n";
|
|
8996
9146
|
}
|
|
8997
|
-
function migrationSha256(
|
|
8998
|
-
return createHash("sha256").update(normaliseMigrationSql(
|
|
9147
|
+
function migrationSha256(sql25) {
|
|
9148
|
+
return createHash("sha256").update(normaliseMigrationSql(sql25), "utf8").digest("hex");
|
|
8999
9149
|
}
|
|
9000
9150
|
function dollarQuoteTag(value) {
|
|
9001
9151
|
let tag = "_mcp";
|
|
@@ -10200,13 +10350,64 @@ var TOOLS = [
|
|
|
10200
10350
|
}
|
|
10201
10351
|
}
|
|
10202
10352
|
},
|
|
10353
|
+
// ----- Cursor Remote (Agent Control) -----
|
|
10354
|
+
{
|
|
10355
|
+
name: "cursor-remote-list",
|
|
10356
|
+
description: "List the paired developer machines (agent hosts) and their workspaces that a headless Cursor agent run can be started on, across the whole team. Each workspace shows its owner, git repository + branch, online status and default git mode. Call this FIRST to discover valid `who` (owner) and `project` (repo/workspace) values before calling cursor-remote-run. Only online hosts can run.",
|
|
10357
|
+
inputSchema: {
|
|
10358
|
+
type: "object",
|
|
10359
|
+
properties: {
|
|
10360
|
+
onlineOnly: {
|
|
10361
|
+
type: "boolean",
|
|
10362
|
+
description: "Only return workspaces whose host is currently online (default false \u2014 shows all active pairings with an online flag)."
|
|
10363
|
+
}
|
|
10364
|
+
}
|
|
10365
|
+
}
|
|
10366
|
+
},
|
|
10367
|
+
{
|
|
10368
|
+
name: "cursor-remote-run",
|
|
10369
|
+
description: "Start a headless Cursor agent run on a teammate's machine to make a code change. Resolve `who` (workspace owner name/email) + `project` (repo 'org/repo', URL, or workspace name) to a single online workspace, then queue the run via the agent-host daemon (@cursor/sdk). Use this when an AI (e.g. a ticket assistant) should hand a self-contained, well-scoped fix prompt to a developer's PC. Default git mode is 'worktree': the change runs in an isolated worktree and ends in a DIFF APPROVAL the owner reviews on the dashboard/mobile \u2014 nothing lands on their branch automatically. Returns the conversationId + runId and a dashboard link to follow progress. Call cursor-remote-list first if unsure about who/project.",
|
|
10370
|
+
inputSchema: {
|
|
10371
|
+
type: "object",
|
|
10372
|
+
properties: {
|
|
10373
|
+
who: {
|
|
10374
|
+
type: "string",
|
|
10375
|
+
description: "Workspace owner to target, matched case-insensitively against full name or email (e.g. 'Sidney'). Optional, but recommended when several people have the same repo paired."
|
|
10376
|
+
},
|
|
10377
|
+
project: {
|
|
10378
|
+
type: "string",
|
|
10379
|
+
description: "The repository ('MGSoftwareBV/mg-dashboard', a GitHub URL), or the workspace name/path. Matched against the paired workspaces."
|
|
10380
|
+
},
|
|
10381
|
+
prompt: {
|
|
10382
|
+
type: "string",
|
|
10383
|
+
description: "The full, self-contained instruction for the agent. Write it as an optimized, scoped task: what to change, relevant files/areas, and acceptance criteria. The agent has no other context."
|
|
10384
|
+
},
|
|
10385
|
+
model: {
|
|
10386
|
+
type: "string",
|
|
10387
|
+
description: "Optional Cursor model slug to run with (e.g. a Sonnet-tier model). Omit to use the host default."
|
|
10388
|
+
},
|
|
10389
|
+
gitMode: {
|
|
10390
|
+
type: "string",
|
|
10391
|
+
enum: ["worktree", "in_place"],
|
|
10392
|
+
description: "'worktree' (default, recommended): isolated worktree + diff approval. 'in_place': applies directly in the working tree (no isolation) \u2014 use only when explicitly desired."
|
|
10393
|
+
},
|
|
10394
|
+
branch: {
|
|
10395
|
+
type: "string",
|
|
10396
|
+
description: "Optional: prefer a workspace already checked out on this branch when several match."
|
|
10397
|
+
}
|
|
10398
|
+
},
|
|
10399
|
+
required: ["project", "prompt"]
|
|
10400
|
+
}
|
|
10401
|
+
},
|
|
10203
10402
|
// ----- Trigger.dev -----
|
|
10204
10403
|
...TRIGGER_TOOLS
|
|
10205
10404
|
];
|
|
10206
10405
|
var MCP_VERSION = "7.0.5";
|
|
10207
10406
|
async function handleListTools() {
|
|
10208
10407
|
if (!authContext) return { tools: TOOLS };
|
|
10408
|
+
const allowedTools = authContext.allowedTools;
|
|
10209
10409
|
const accessible = TOOLS.filter((tool) => {
|
|
10410
|
+
if (allowedTools && !allowedTools.includes(tool.name)) return false;
|
|
10210
10411
|
const requiredModule = TOOL_MODULE_MAP[tool.name];
|
|
10211
10412
|
if (!requiredModule) return true;
|
|
10212
10413
|
return authContext.permissions.modules[requiredModule] === true;
|
|
@@ -10219,6 +10420,16 @@ async function handleCallTool(request) {
|
|
|
10219
10420
|
}
|
|
10220
10421
|
const { name, arguments: toolArgs } = request.params;
|
|
10221
10422
|
const a = toolArgs || {};
|
|
10423
|
+
if (authContext.allowedTools && !authContext.allowedTools.includes(name)) {
|
|
10424
|
+
return {
|
|
10425
|
+
content: [
|
|
10426
|
+
{
|
|
10427
|
+
type: "text",
|
|
10428
|
+
text: `Access denied: this API key is restricted to a specific tool allowlist and may not call "${name}". Allowed: ${authContext.allowedTools.join(", ")}`
|
|
10429
|
+
}
|
|
10430
|
+
]
|
|
10431
|
+
};
|
|
10432
|
+
}
|
|
10222
10433
|
const requiredModule = TOOL_MODULE_MAP[name];
|
|
10223
10434
|
if (requiredModule && authContext.permissions.modules[requiredModule] !== true) {
|
|
10224
10435
|
return {
|
|
@@ -10424,6 +10635,131 @@ ${facets.join(", ")}` : ""),
|
|
|
10424
10635
|
]
|
|
10425
10636
|
};
|
|
10426
10637
|
}
|
|
10638
|
+
// ----- Cursor Remote (Agent Control) -----
|
|
10639
|
+
case "cursor-remote-list": {
|
|
10640
|
+
const onlineOnly = a.onlineOnly === true;
|
|
10641
|
+
const all = await listRemoteWorkspaces();
|
|
10642
|
+
const workspaces = onlineOnly ? all.filter((w) => w.hostOnline) : all;
|
|
10643
|
+
if (workspaces.length === 0) {
|
|
10644
|
+
return {
|
|
10645
|
+
content: [
|
|
10646
|
+
{
|
|
10647
|
+
type: "text",
|
|
10648
|
+
text: onlineOnly ? "No online agent-host workspaces found. Ask the developer to start their MG Agent Host." : "No paired agent-host workspaces found."
|
|
10649
|
+
}
|
|
10650
|
+
]
|
|
10651
|
+
};
|
|
10652
|
+
}
|
|
10653
|
+
const payload = workspaces.map((w) => ({
|
|
10654
|
+
owner: w.ownerName ?? w.ownerEmail ?? w.ownerUserId,
|
|
10655
|
+
online: w.hostOnline,
|
|
10656
|
+
host: w.hostName,
|
|
10657
|
+
project: w.name,
|
|
10658
|
+
repo: w.gitRepository,
|
|
10659
|
+
branch: w.gitBranch,
|
|
10660
|
+
defaultGitMode: w.defaultGitMode,
|
|
10661
|
+
path: w.path,
|
|
10662
|
+
lastSyncedAt: w.lastSyncedAt
|
|
10663
|
+
}));
|
|
10664
|
+
return {
|
|
10665
|
+
content: [
|
|
10666
|
+
{
|
|
10667
|
+
type: "text",
|
|
10668
|
+
text: JSON.stringify(
|
|
10669
|
+
{
|
|
10670
|
+
count: payload.length,
|
|
10671
|
+
onlineCount: workspaces.filter((w) => w.hostOnline).length,
|
|
10672
|
+
workspaces: payload,
|
|
10673
|
+
hint: "Use `who` (owner) + `project` (repo or project name) with cursor-remote-run. Only online workspaces can run."
|
|
10674
|
+
},
|
|
10675
|
+
null,
|
|
10676
|
+
2
|
|
10677
|
+
)
|
|
10678
|
+
}
|
|
10679
|
+
]
|
|
10680
|
+
};
|
|
10681
|
+
}
|
|
10682
|
+
case "cursor-remote-run": {
|
|
10683
|
+
const project = typeof a.project === "string" ? a.project.trim() : "";
|
|
10684
|
+
const prompt = typeof a.prompt === "string" ? a.prompt.trim() : "";
|
|
10685
|
+
if (!project) {
|
|
10686
|
+
return { content: [{ type: "text", text: "Error: project is required" }] };
|
|
10687
|
+
}
|
|
10688
|
+
if (!prompt) {
|
|
10689
|
+
return { content: [{ type: "text", text: "Error: prompt is required" }] };
|
|
10690
|
+
}
|
|
10691
|
+
const who = typeof a.who === "string" ? a.who.trim() : null;
|
|
10692
|
+
const branch = typeof a.branch === "string" ? a.branch.trim() : null;
|
|
10693
|
+
const model = typeof a.model === "string" ? a.model.trim() : null;
|
|
10694
|
+
const gitMode = a.gitMode === "in_place" ? "in_place" : "worktree";
|
|
10695
|
+
const resolution = await resolveRemoteTarget({ who, project, branch });
|
|
10696
|
+
if (!resolution.ok) {
|
|
10697
|
+
const describe = (c) => ` - ${c.ownerName ?? c.ownerEmail ?? c.ownerUserId} \xB7 ${c.name}${c.gitRepository ? ` (${c.gitRepository}${c.gitBranch ? `@${c.gitBranch}` : ""})` : ""}`;
|
|
10698
|
+
const candidates = resolution.candidates.map(describe).join("\n");
|
|
10699
|
+
const intro = resolution.reason === "ambiguous" ? `Multiple online workspaces match \u2014 narrow it down with \`who\` (and/or \`branch\`):` : `No online workspace matched who=${who ?? "(any)"} project="${project}". Online candidates:`;
|
|
10700
|
+
return {
|
|
10701
|
+
content: [
|
|
10702
|
+
{
|
|
10703
|
+
type: "text",
|
|
10704
|
+
text: `${intro}
|
|
10705
|
+
${candidates || " (none online \u2014 start the MG Agent Host)"}`
|
|
10706
|
+
}
|
|
10707
|
+
]
|
|
10708
|
+
};
|
|
10709
|
+
}
|
|
10710
|
+
const target = resolution.target;
|
|
10711
|
+
const title = prompt.split("\n")[0]?.slice(0, 80) || "Remote agent run";
|
|
10712
|
+
const queued = await queueAgentRun({
|
|
10713
|
+
workspace: {
|
|
10714
|
+
id: target.workspaceId,
|
|
10715
|
+
host_id: target.hostId,
|
|
10716
|
+
path: target.path,
|
|
10717
|
+
git_branch: target.gitBranch,
|
|
10718
|
+
git_repository: target.gitRepository
|
|
10719
|
+
},
|
|
10720
|
+
userId: target.ownerUserId,
|
|
10721
|
+
prompt,
|
|
10722
|
+
title,
|
|
10723
|
+
source: "mcp_remote",
|
|
10724
|
+
model,
|
|
10725
|
+
gitMode,
|
|
10726
|
+
metadata: {
|
|
10727
|
+
requestedBy: ctx.userId,
|
|
10728
|
+
who: who ?? null,
|
|
10729
|
+
project
|
|
10730
|
+
}
|
|
10731
|
+
});
|
|
10732
|
+
if (!queued) {
|
|
10733
|
+
return {
|
|
10734
|
+
content: [{ type: "text", text: "Error: failed to queue the agent run" }]
|
|
10735
|
+
};
|
|
10736
|
+
}
|
|
10737
|
+
const conversationUrl = `${dashboardBaseUrl}/cursor-remote?conversation=${queued.conversationId}`;
|
|
10738
|
+
return {
|
|
10739
|
+
content: [
|
|
10740
|
+
{
|
|
10741
|
+
type: "text",
|
|
10742
|
+
text: JSON.stringify(
|
|
10743
|
+
{
|
|
10744
|
+
status: "queued",
|
|
10745
|
+
owner: target.ownerName ?? target.ownerEmail ?? target.ownerUserId,
|
|
10746
|
+
host: target.hostName,
|
|
10747
|
+
project: target.name,
|
|
10748
|
+
repo: target.gitRepository,
|
|
10749
|
+
branch: target.gitBranch,
|
|
10750
|
+
gitMode,
|
|
10751
|
+
conversationId: queued.conversationId,
|
|
10752
|
+
runId: queued.runId,
|
|
10753
|
+
conversationUrl,
|
|
10754
|
+
note: gitMode === "worktree" ? "Runs in an isolated worktree and ends in a diff approval the owner reviews \u2014 nothing lands on their branch automatically." : "Runs in_place in the working tree (no isolation)."
|
|
10755
|
+
},
|
|
10756
|
+
null,
|
|
10757
|
+
2
|
|
10758
|
+
)
|
|
10759
|
+
}
|
|
10760
|
+
]
|
|
10761
|
+
};
|
|
10762
|
+
}
|
|
10427
10763
|
// ----- Servers -----
|
|
10428
10764
|
case "list-servers": {
|
|
10429
10765
|
const data = ctx.allowedServerIds !== null ? await db.execute(sql`
|