@synapsor/runner 1.6.4 → 1.6.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/CHANGELOG.md +25 -3
- package/README.md +36 -23
- package/dist/cli.d.ts.map +1 -1
- package/dist/runner.mjs +382 -242
- package/docs/agent-guided-setup.md +8 -6
- package/docs/auto-boundary-and-scoped-explore.md +12 -8
- package/docs/client-recipes.md +40 -11
- package/docs/getting-started-own-database.md +16 -14
- package/docs/guided-onboarding.md +6 -4
- package/docs/host-compatibility.md +24 -11
- package/docs/mcp-client-setup.md +19 -4
- package/docs/mcp-clients.md +46 -10
- package/docs/release-notes.md +24 -10
- package/docs/troubleshooting-first-run.md +12 -8
- package/package.json +2 -1
package/dist/runner.mjs
CHANGED
|
@@ -22743,7 +22743,7 @@ var require_svgPath = __commonJS({
|
|
|
22743
22743
|
["Z", 0],
|
|
22744
22744
|
["z", 0]
|
|
22745
22745
|
]);
|
|
22746
|
-
var
|
|
22746
|
+
var parse2 = function(path30) {
|
|
22747
22747
|
var cmd;
|
|
22748
22748
|
var ret = [];
|
|
22749
22749
|
var args = [];
|
|
@@ -23065,7 +23065,7 @@ var require_svgPath = __commonJS({
|
|
|
23065
23065
|
return result;
|
|
23066
23066
|
};
|
|
23067
23067
|
exports.svgPathToOperators = function(path30) {
|
|
23068
|
-
return apply2(
|
|
23068
|
+
return apply2(parse2(path30));
|
|
23069
23069
|
};
|
|
23070
23070
|
}
|
|
23071
23071
|
});
|
|
@@ -50839,15 +50839,16 @@ import path16 from "node:path";
|
|
|
50839
50839
|
init_src();
|
|
50840
50840
|
init_src2();
|
|
50841
50841
|
|
|
50842
|
-
// apps/runner/src/
|
|
50842
|
+
// apps/runner/src/managed-mcp-project.ts
|
|
50843
50843
|
import crypto7 from "node:crypto";
|
|
50844
50844
|
import fs3 from "node:fs/promises";
|
|
50845
50845
|
import path3 from "node:path";
|
|
50846
|
+
import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser";
|
|
50846
50847
|
|
|
50847
50848
|
// apps/runner/package.json
|
|
50848
50849
|
var package_default = {
|
|
50849
50850
|
name: "@synapsor/runner",
|
|
50850
|
-
version: "1.6.
|
|
50851
|
+
version: "1.6.5",
|
|
50851
50852
|
description: "Open-source MCP runtime for governed Postgres/MySQL access with reviewed capabilities, proposals, guarded writes, receipts, and replay.",
|
|
50852
50853
|
license: "Apache-2.0",
|
|
50853
50854
|
type: "module",
|
|
@@ -50949,6 +50950,7 @@ var package_default = {
|
|
|
50949
50950
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
50950
50951
|
"@synapsor/spec": "workspace:^1.7.0",
|
|
50951
50952
|
jose: "6.2.3",
|
|
50953
|
+
"jsonc-parser": "3.3.1",
|
|
50952
50954
|
mysql2: "^3.11.0",
|
|
50953
50955
|
"pdf-lib": "1.17.1",
|
|
50954
50956
|
pg: "^8.13.0",
|
|
@@ -50976,61 +50978,102 @@ var package_default = {
|
|
|
50976
50978
|
}
|
|
50977
50979
|
};
|
|
50978
50980
|
|
|
50979
|
-
// apps/runner/src/
|
|
50980
|
-
var markerVersion = "synapsor.cursor-project.v1";
|
|
50981
|
+
// apps/runner/src/managed-mcp-project.ts
|
|
50981
50982
|
var serverName = "synapsor";
|
|
50982
|
-
|
|
50983
|
-
|
|
50984
|
-
|
|
50985
|
-
|
|
50986
|
-
|
|
50987
|
-
|
|
50988
|
-
|
|
50989
|
-
|
|
50990
|
-
|
|
50991
|
-
|
|
50983
|
+
var managedClients = {
|
|
50984
|
+
cursor: {
|
|
50985
|
+
client: "cursor",
|
|
50986
|
+
displayName: "Cursor",
|
|
50987
|
+
destination: ".cursor/mcp.json",
|
|
50988
|
+
marker: ".synapsor/cursor-project.json",
|
|
50989
|
+
markerVersion: "synapsor.cursor-project.v1",
|
|
50990
|
+
serversKey: "mcpServers",
|
|
50991
|
+
reloadInstruction: "Restart or reload Cursor"
|
|
50992
|
+
},
|
|
50993
|
+
"claude-code": {
|
|
50994
|
+
client: "claude-code",
|
|
50995
|
+
displayName: "Claude Code",
|
|
50996
|
+
destination: ".mcp.json",
|
|
50997
|
+
marker: ".synapsor/claude-code-project.json",
|
|
50998
|
+
markerVersion: "synapsor.claude-code-project.v1",
|
|
50999
|
+
serversKey: "mcpServers",
|
|
51000
|
+
reloadInstruction: "Restart Claude Code or begin a new project session, then approve the project MCP server when Claude Code prompts"
|
|
51001
|
+
},
|
|
51002
|
+
vscode: {
|
|
51003
|
+
client: "vscode",
|
|
51004
|
+
displayName: "VS Code",
|
|
51005
|
+
destination: ".vscode/mcp.json",
|
|
51006
|
+
marker: ".synapsor/vscode-project.json",
|
|
51007
|
+
markerVersion: "synapsor.vscode-project.v1",
|
|
51008
|
+
serversKey: "servers",
|
|
51009
|
+
reloadInstruction: "Reload the VS Code window and start the Synapsor MCP server"
|
|
51010
|
+
}
|
|
51011
|
+
};
|
|
51012
|
+
function parseManagedMcpProjectClient(value) {
|
|
51013
|
+
if (value === "cursor") return "cursor";
|
|
51014
|
+
if (value === "claude-code" || value === "claude") return "claude-code";
|
|
51015
|
+
if (value === "vscode" || value === "vs-code") return "vscode";
|
|
51016
|
+
throw new Error("managed MCP project client must be cursor, claude-code, or vscode");
|
|
51017
|
+
}
|
|
51018
|
+
function managedMcpProjectDefinition(client) {
|
|
51019
|
+
return managedClients[client];
|
|
51020
|
+
}
|
|
51021
|
+
async function previewManagedMcpProjectInstall(input) {
|
|
51022
|
+
const definition = managedClients[input.client];
|
|
51023
|
+
const paths = await resolveInstallPaths(input, definition);
|
|
51024
|
+
const existing = await readOptionalJsonDocument(paths.destination);
|
|
51025
|
+
const marker = await readMarker(paths.marker, definition);
|
|
51026
|
+
const entry = managedEntry(paths.configArgument, paths.storeArgument, input.packageSpec, input.authoring === true);
|
|
51027
|
+
assertSecretFree(entry, definition);
|
|
51028
|
+
const existingServersValue = existing?.value[definition.serversKey];
|
|
51029
|
+
if (existingServersValue !== void 0 && !isRecord7(existingServersValue)) {
|
|
51030
|
+
throw new Error(`${displayPath(paths.projectRoot, paths.destination)} must contain an object at ${definition.serversKey}.`);
|
|
51031
|
+
}
|
|
51032
|
+
const existingServers = isRecord7(existingServersValue) ? existingServersValue : {};
|
|
50992
51033
|
const existingEntry = isRecord7(existingServers[serverName]) ? existingServers[serverName] : void 0;
|
|
50993
51034
|
if (marker && !existingEntry) {
|
|
50994
|
-
throw new Error(`Runner ownership marker exists, but the
|
|
51035
|
+
throw new Error(`Runner ownership marker exists, but the ${definition.displayName} ${serverName} MCP entry is missing. Review the files manually.`);
|
|
50995
51036
|
}
|
|
50996
51037
|
if (existingEntry && !marker) {
|
|
50997
51038
|
if (!deepEqual(existingEntry, entry)) {
|
|
50998
|
-
throw new Error(
|
|
51039
|
+
throw new Error(`${definition.displayName} project already has an unowned ${serverName} MCP entry at ${displayPath(paths.projectRoot, paths.destination)}. Rename it or remove it explicitly before installing.`);
|
|
50999
51040
|
}
|
|
51000
|
-
throw new Error(
|
|
51041
|
+
throw new Error(`${definition.displayName} project has a matching but unowned ${serverName} MCP entry. Remove it explicitly or restore ${displayPath(paths.projectRoot, paths.marker)} before Runner manages it.`);
|
|
51001
51042
|
}
|
|
51002
51043
|
if (existingEntry && marker && digest3(existingEntry) !== marker.entry_digest) {
|
|
51003
|
-
throw new Error(
|
|
51044
|
+
throw new Error(`${definition.displayName} ${serverName} MCP entry changed after Runner installed it. Refusing to overwrite user edits; review ${displayPath(paths.projectRoot, paths.destination)}.`);
|
|
51004
51045
|
}
|
|
51005
51046
|
const merged = {
|
|
51006
|
-
...existing ?? {},
|
|
51007
|
-
|
|
51047
|
+
...existing?.value ?? {},
|
|
51048
|
+
[definition.serversKey]: { ...existingServers, [serverName]: entry }
|
|
51008
51049
|
};
|
|
51009
51050
|
const unchanged = Boolean(existingEntry && marker && deepEqual(existingEntry, entry));
|
|
51010
51051
|
return {
|
|
51011
51052
|
state: unchanged ? "installed" : existingEntry ? "installed" : "not_installed",
|
|
51012
51053
|
paths,
|
|
51013
51054
|
entry,
|
|
51014
|
-
message: unchanged ?
|
|
51055
|
+
message: unchanged ? `${definition.displayName} project MCP entry already matches the reviewed Runner wiring.` : `${definition.displayName} project MCP entry is ready to install.`,
|
|
51015
51056
|
action: unchanged ? "unchanged" : existingEntry ? "update" : "install",
|
|
51016
|
-
merged
|
|
51057
|
+
merged,
|
|
51058
|
+
updatedDocument: setManagedEntry(existing?.text, definition, entry)
|
|
51017
51059
|
};
|
|
51018
51060
|
}
|
|
51019
|
-
async function
|
|
51020
|
-
const
|
|
51061
|
+
async function installManagedMcpProject(input) {
|
|
51062
|
+
const definition = managedClients[input.client];
|
|
51063
|
+
const preview = await previewManagedMcpProjectInstall(input);
|
|
51021
51064
|
if (preview.action === "unchanged") return preview;
|
|
51022
51065
|
const hadDestination = await pathExists(preview.paths.destination);
|
|
51023
51066
|
const backup = hadDestination ? await backupFile(preview.paths.destination, input.now) : void 0;
|
|
51024
51067
|
const marker = {
|
|
51025
|
-
schema_version: markerVersion,
|
|
51068
|
+
schema_version: definition.markerVersion,
|
|
51026
51069
|
server_name: serverName,
|
|
51027
|
-
destination:
|
|
51070
|
+
destination: definition.destination,
|
|
51028
51071
|
entry_digest: digest3(preview.entry),
|
|
51029
51072
|
config_path: preview.paths.configArgument,
|
|
51030
51073
|
store_path: preview.paths.storeArgument,
|
|
51031
51074
|
installed_at: input.now ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
51032
51075
|
};
|
|
51033
|
-
await
|
|
51076
|
+
await writeTextAtomic(preview.paths.destination, preview.updatedDocument);
|
|
51034
51077
|
try {
|
|
51035
51078
|
await writeJsonAtomic(preview.paths.marker, marker);
|
|
51036
51079
|
} catch (error) {
|
|
@@ -51040,49 +51083,76 @@ async function installCursorProject(input = {}) {
|
|
|
51040
51083
|
}
|
|
51041
51084
|
return { ...preview, state: "installed", ...backup ? { backup } : {} };
|
|
51042
51085
|
}
|
|
51043
|
-
async function
|
|
51044
|
-
const
|
|
51045
|
-
const
|
|
51046
|
-
const
|
|
51047
|
-
const
|
|
51086
|
+
async function uninstallManagedMcpProject(input) {
|
|
51087
|
+
const definition = managedClients[input.client];
|
|
51088
|
+
const base = await resolveBasePaths(input.projectRoot, definition);
|
|
51089
|
+
const existing = await readOptionalJsonDocument(base.destination);
|
|
51090
|
+
const marker = await readMarker(base.marker, definition);
|
|
51091
|
+
const paths = marker ? await pathsFromMarker(base, marker, definition) : defaultPaths(base);
|
|
51048
51092
|
if (!existing && !marker) return { changed: false, paths };
|
|
51049
|
-
if (!marker)
|
|
51050
|
-
|
|
51051
|
-
|
|
51093
|
+
if (!marker) {
|
|
51094
|
+
throw new Error(`Refusing to uninstall an unowned ${definition.displayName} ${serverName} entry without ${displayPath(paths.projectRoot, paths.marker)}.`);
|
|
51095
|
+
}
|
|
51096
|
+
const serversValue = existing?.value[definition.serversKey];
|
|
51097
|
+
if (serversValue !== void 0 && !isRecord7(serversValue)) {
|
|
51098
|
+
throw new Error(`${displayPath(paths.projectRoot, paths.destination)} must contain an object at ${definition.serversKey}.`);
|
|
51052
51099
|
}
|
|
51053
|
-
const servers = isRecord7(
|
|
51100
|
+
const servers = isRecord7(serversValue) ? serversValue : {};
|
|
51054
51101
|
const entry = isRecord7(servers[serverName]) ? servers[serverName] : void 0;
|
|
51055
|
-
if (!entry)
|
|
51102
|
+
if (!entry) {
|
|
51103
|
+
throw new Error(`Runner ownership marker exists, but ${definition.displayName} ${serverName} entry is missing. Review the files manually.`);
|
|
51104
|
+
}
|
|
51056
51105
|
if (digest3(entry) !== marker.entry_digest) {
|
|
51057
|
-
throw new Error(
|
|
51106
|
+
throw new Error(`${definition.displayName} ${serverName} entry changed after installation. Refusing to remove user edits.`);
|
|
51058
51107
|
}
|
|
51059
|
-
const remainingServers = { ...servers };
|
|
51060
|
-
delete remainingServers[serverName];
|
|
51061
|
-
const updated = { ...existing, mcpServers: remainingServers };
|
|
51062
51108
|
const backup = await backupFile(paths.destination, input.now);
|
|
51063
|
-
|
|
51109
|
+
const updatedDocument = removeManagedEntry(existing?.text ?? "{}\n", definition);
|
|
51110
|
+
await writeTextAtomic(paths.destination, updatedDocument);
|
|
51064
51111
|
await fs3.rm(paths.marker, { force: true });
|
|
51065
51112
|
return { changed: true, paths, backup };
|
|
51066
51113
|
}
|
|
51067
|
-
async function
|
|
51068
|
-
const
|
|
51069
|
-
const
|
|
51070
|
-
const
|
|
51071
|
-
const
|
|
51072
|
-
|
|
51073
|
-
|
|
51074
|
-
|
|
51075
|
-
|
|
51114
|
+
async function managedMcpProjectStatus(client, projectRoot = process.cwd()) {
|
|
51115
|
+
const definition = managedClients[client];
|
|
51116
|
+
const base = await resolveBasePaths(projectRoot, definition);
|
|
51117
|
+
const existing = await readOptionalJsonDocument(base.destination);
|
|
51118
|
+
const marker = await readMarker(base.marker, definition);
|
|
51119
|
+
const paths = marker ? await pathsFromMarker(base, marker, definition) : defaultPaths(base);
|
|
51120
|
+
const serversValue = existing?.value[definition.serversKey];
|
|
51121
|
+
if (serversValue !== void 0 && !isRecord7(serversValue)) {
|
|
51122
|
+
return { state: "tampered", paths, message: `${definition.displayName} ${definition.serversKey} is not a JSON object.` };
|
|
51123
|
+
}
|
|
51124
|
+
const servers = isRecord7(serversValue) ? serversValue : {};
|
|
51076
51125
|
const entry = isRecord7(servers[serverName]) ? servers[serverName] : void 0;
|
|
51077
|
-
if (!entry && !marker)
|
|
51078
|
-
|
|
51079
|
-
|
|
51080
|
-
|
|
51081
|
-
|
|
51082
|
-
|
|
51083
|
-
|
|
51084
|
-
|
|
51085
|
-
|
|
51126
|
+
if (!entry && !marker) {
|
|
51127
|
+
return { state: "not_installed", paths, message: `No Runner-owned ${definition.displayName} project entry is installed.` };
|
|
51128
|
+
}
|
|
51129
|
+
if (!entry || !marker) {
|
|
51130
|
+
return {
|
|
51131
|
+
state: "unowned",
|
|
51132
|
+
paths,
|
|
51133
|
+
...entry ? { entry } : {},
|
|
51134
|
+
message: `${definition.displayName} entry and Runner ownership marker do not agree.`
|
|
51135
|
+
};
|
|
51136
|
+
}
|
|
51137
|
+
if (digest3(entry) !== marker.entry_digest) {
|
|
51138
|
+
return {
|
|
51139
|
+
state: "tampered",
|
|
51140
|
+
paths,
|
|
51141
|
+
entry,
|
|
51142
|
+
message: `${definition.displayName} entry changed after Runner installation.`
|
|
51143
|
+
};
|
|
51144
|
+
}
|
|
51145
|
+
return {
|
|
51146
|
+
state: "installed",
|
|
51147
|
+
paths,
|
|
51148
|
+
entry,
|
|
51149
|
+
message: `Runner-owned ${definition.displayName} project entry is intact.`
|
|
51150
|
+
};
|
|
51151
|
+
}
|
|
51152
|
+
async function resolveInstallPaths(input, definition) {
|
|
51153
|
+
const base = await resolveBasePaths(input.projectRoot, definition);
|
|
51154
|
+
const config = resolveContained(base.projectRoot, input.configPath ?? "./synapsor.runner.json", "Runner config", definition);
|
|
51155
|
+
const store = resolveContained(base.projectRoot, input.storePath ?? "./.synapsor/local.db", "Runner store", definition);
|
|
51086
51156
|
await rejectSymlinkChain(base.projectRoot, config, "Runner config");
|
|
51087
51157
|
await rejectSymlinkChain(base.projectRoot, store, "Runner store");
|
|
51088
51158
|
if (!input.authoring) await requireRegularFile(config, "Runner config");
|
|
@@ -51092,14 +51162,14 @@ async function resolveInstallPaths(input) {
|
|
|
51092
51162
|
storeArgument: projectArgument(base.projectRoot, store)
|
|
51093
51163
|
};
|
|
51094
51164
|
}
|
|
51095
|
-
async function resolveBasePaths(projectRootInput) {
|
|
51165
|
+
async function resolveBasePaths(projectRootInput, definition) {
|
|
51096
51166
|
const projectRoot = path3.resolve(projectRootInput ?? process.cwd());
|
|
51097
|
-
await requireRealDirectory(projectRoot,
|
|
51098
|
-
const destination = path3.join(projectRoot,
|
|
51099
|
-
const marker = path3.join(projectRoot,
|
|
51100
|
-
await rejectSymlinkChain(projectRoot, destination,
|
|
51101
|
-
await rejectSymlinkChain(projectRoot, marker,
|
|
51102
|
-
return { projectRoot, destination, marker };
|
|
51167
|
+
await requireRealDirectory(projectRoot, `${definition.displayName} project root`);
|
|
51168
|
+
const destination = path3.join(projectRoot, definition.destination);
|
|
51169
|
+
const marker = path3.join(projectRoot, definition.marker);
|
|
51170
|
+
await rejectSymlinkChain(projectRoot, destination, `${definition.displayName} project config`);
|
|
51171
|
+
await rejectSymlinkChain(projectRoot, marker, `${definition.displayName} ownership marker`);
|
|
51172
|
+
return { client: definition.client, projectRoot, destination, marker };
|
|
51103
51173
|
}
|
|
51104
51174
|
function defaultPaths(base) {
|
|
51105
51175
|
return {
|
|
@@ -51108,9 +51178,9 @@ function defaultPaths(base) {
|
|
|
51108
51178
|
storeArgument: "./.synapsor/local.db"
|
|
51109
51179
|
};
|
|
51110
51180
|
}
|
|
51111
|
-
async function pathsFromMarker(base, marker) {
|
|
51112
|
-
const config = resolveContained(base.projectRoot, marker.config_path, "Recorded Runner config");
|
|
51113
|
-
const store = resolveContained(base.projectRoot, marker.store_path, "Recorded Runner store");
|
|
51181
|
+
async function pathsFromMarker(base, marker, definition) {
|
|
51182
|
+
const config = resolveContained(base.projectRoot, marker.config_path, "Recorded Runner config", definition);
|
|
51183
|
+
const store = resolveContained(base.projectRoot, marker.store_path, "Recorded Runner store", definition);
|
|
51114
51184
|
await rejectSymlinkChain(base.projectRoot, config, "Recorded Runner config");
|
|
51115
51185
|
await rejectSymlinkChain(base.projectRoot, store, "Recorded Runner store");
|
|
51116
51186
|
return {
|
|
@@ -51119,10 +51189,10 @@ async function pathsFromMarker(base, marker) {
|
|
|
51119
51189
|
storeArgument: projectArgument(base.projectRoot, store)
|
|
51120
51190
|
};
|
|
51121
51191
|
}
|
|
51122
|
-
function
|
|
51192
|
+
function managedEntry(configPath, storePath, packageSpec, authoring) {
|
|
51123
51193
|
const resolvedPackage = packageSpec ?? `@synapsor/runner@${package_default.version}`;
|
|
51124
51194
|
if (!resolvedPackage.trim() || resolvedPackage.length > 2048 || /[\u0000-\u001f\u007f]/.test(resolvedPackage)) {
|
|
51125
|
-
throw new Error("
|
|
51195
|
+
throw new Error("Runner package spec must be a non-empty package reference without control characters");
|
|
51126
51196
|
}
|
|
51127
51197
|
return {
|
|
51128
51198
|
type: "stdio",
|
|
@@ -51130,11 +51200,24 @@ function cursorEntry(configPath, storePath, packageSpec, authoring) {
|
|
|
51130
51200
|
args: authoring ? ["-y", resolvedPackage, "mcp", "serve", "--authoring", "--project-root", "."] : ["-y", resolvedPackage, "mcp", "serve", "--config", configPath, "--store", storePath]
|
|
51131
51201
|
};
|
|
51132
51202
|
}
|
|
51133
|
-
function
|
|
51203
|
+
function setManagedEntry(existingText, definition, entry) {
|
|
51204
|
+
const source = existingText ?? "{}\n";
|
|
51205
|
+
const edits = modify(source, [definition.serversKey, serverName], entry, {
|
|
51206
|
+
formattingOptions: { insertSpaces: true, tabSize: 2, eol: "\n" }
|
|
51207
|
+
});
|
|
51208
|
+
return finalNewline(applyEdits(source, edits));
|
|
51209
|
+
}
|
|
51210
|
+
function removeManagedEntry(existingText, definition) {
|
|
51211
|
+
const edits = modify(existingText, [definition.serversKey, serverName], void 0, {
|
|
51212
|
+
formattingOptions: { insertSpaces: true, tabSize: 2, eol: "\n" }
|
|
51213
|
+
});
|
|
51214
|
+
return finalNewline(applyEdits(existingText, edits));
|
|
51215
|
+
}
|
|
51216
|
+
function resolveContained(root, value, label, definition) {
|
|
51134
51217
|
const resolved = path3.resolve(root, value);
|
|
51135
51218
|
const relative2 = path3.relative(root, resolved);
|
|
51136
51219
|
if (relative2 === "" || !relative2.startsWith("..") && !path3.isAbsolute(relative2)) return resolved;
|
|
51137
|
-
throw new Error(`${label} must stay inside the project for project-scoped
|
|
51220
|
+
throw new Error(`${label} must stay inside the project for project-scoped ${definition.displayName} installation: ${value}`);
|
|
51138
51221
|
}
|
|
51139
51222
|
function projectArgument(root, value) {
|
|
51140
51223
|
const relative2 = path3.relative(root, value).split(path3.sep).join("/");
|
|
@@ -51162,26 +51245,33 @@ async function rejectSymlinkChain(root, value, label) {
|
|
|
51162
51245
|
}
|
|
51163
51246
|
}
|
|
51164
51247
|
}
|
|
51165
|
-
async function
|
|
51248
|
+
async function readOptionalJsonDocument(value) {
|
|
51166
51249
|
try {
|
|
51167
|
-
const
|
|
51250
|
+
const text = await fs3.readFile(value, "utf8");
|
|
51251
|
+
const errors = [];
|
|
51252
|
+
const parsed = parse(text, errors, { allowTrailingComma: true, disallowComments: false });
|
|
51253
|
+
if (errors.length > 0) {
|
|
51254
|
+
const first = errors[0];
|
|
51255
|
+
throw new Error(`${value} contains invalid JSON/JSONC at offset ${first.offset}: ${printParseErrorCode(first.error)}`);
|
|
51256
|
+
}
|
|
51168
51257
|
if (!isRecord7(parsed)) throw new Error(`${value} must contain a JSON object`);
|
|
51169
|
-
return parsed;
|
|
51258
|
+
return { value: parsed, text };
|
|
51170
51259
|
} catch (error) {
|
|
51171
51260
|
if (error.code === "ENOENT") return void 0;
|
|
51172
51261
|
throw error;
|
|
51173
51262
|
}
|
|
51174
51263
|
}
|
|
51175
|
-
async function readMarker(value) {
|
|
51176
|
-
const
|
|
51264
|
+
async function readMarker(value, definition) {
|
|
51265
|
+
const document2 = await readOptionalJsonDocument(value);
|
|
51266
|
+
const parsed = document2?.value;
|
|
51177
51267
|
if (!parsed) return void 0;
|
|
51178
|
-
if (parsed.schema_version !== markerVersion || parsed.server_name !== serverName || parsed.destination !==
|
|
51179
|
-
throw new Error(`Invalid
|
|
51268
|
+
if (parsed.schema_version !== definition.markerVersion || parsed.server_name !== serverName || parsed.destination !== definition.destination || typeof parsed.entry_digest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(parsed.entry_digest) || typeof parsed.config_path !== "string" || typeof parsed.store_path !== "string" || typeof parsed.installed_at !== "string") {
|
|
51269
|
+
throw new Error(`Invalid ${definition.displayName} ownership marker: ${value}`);
|
|
51180
51270
|
}
|
|
51181
51271
|
return {
|
|
51182
|
-
schema_version: markerVersion,
|
|
51272
|
+
schema_version: definition.markerVersion,
|
|
51183
51273
|
server_name: serverName,
|
|
51184
|
-
destination:
|
|
51274
|
+
destination: definition.destination,
|
|
51185
51275
|
entry_digest: parsed.entry_digest,
|
|
51186
51276
|
config_path: parsed.config_path,
|
|
51187
51277
|
store_path: parsed.store_path,
|
|
@@ -51189,11 +51279,14 @@ async function readMarker(value) {
|
|
|
51189
51279
|
};
|
|
51190
51280
|
}
|
|
51191
51281
|
async function writeJsonAtomic(destination, value) {
|
|
51282
|
+
await writeTextAtomic(destination, `${JSON.stringify(value, null, 2)}
|
|
51283
|
+
`);
|
|
51284
|
+
}
|
|
51285
|
+
async function writeTextAtomic(destination, value) {
|
|
51192
51286
|
await fs3.mkdir(path3.dirname(destination), { recursive: true });
|
|
51193
51287
|
const temporary = `${destination}.tmp-${process.pid}-${crypto7.randomBytes(6).toString("hex")}`;
|
|
51194
51288
|
try {
|
|
51195
|
-
await fs3.writeFile(temporary,
|
|
51196
|
-
`, { encoding: "utf8", mode: 384 });
|
|
51289
|
+
await fs3.writeFile(temporary, value, { encoding: "utf8", mode: 384 });
|
|
51197
51290
|
await fs3.rename(temporary, destination);
|
|
51198
51291
|
} finally {
|
|
51199
51292
|
await fs3.rm(temporary, { force: true });
|
|
@@ -51218,10 +51311,10 @@ function stableJson(value) {
|
|
|
51218
51311
|
function deepEqual(left, right) {
|
|
51219
51312
|
return stableJson(left) === stableJson(right);
|
|
51220
51313
|
}
|
|
51221
|
-
function assertSecretFree(value) {
|
|
51314
|
+
function assertSecretFree(value, definition) {
|
|
51222
51315
|
const text = JSON.stringify(value);
|
|
51223
51316
|
if (/postgres(?:ql)?:\/\/|mysql:\/\/|password|bearer\s+[a-z0-9._~+/=-]+|syn_wbr_|api[_-]?key/i.test(text)) {
|
|
51224
|
-
throw new Error(
|
|
51317
|
+
throw new Error(`${definition.displayName} MCP configuration must contain command paths only, never credentials or database URLs`);
|
|
51225
51318
|
}
|
|
51226
51319
|
}
|
|
51227
51320
|
function displayPath(root, value) {
|
|
@@ -51236,10 +51329,19 @@ async function pathExists(value) {
|
|
|
51236
51329
|
throw error;
|
|
51237
51330
|
}
|
|
51238
51331
|
}
|
|
51332
|
+
function finalNewline(value) {
|
|
51333
|
+
return value.endsWith("\n") ? value : `${value}
|
|
51334
|
+
`;
|
|
51335
|
+
}
|
|
51239
51336
|
function isRecord7(value) {
|
|
51240
51337
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
51241
51338
|
}
|
|
51242
51339
|
|
|
51340
|
+
// apps/runner/src/cursor-project.ts
|
|
51341
|
+
function cursorProjectStatus(projectRoot = process.cwd()) {
|
|
51342
|
+
return managedMcpProjectStatus("cursor", projectRoot);
|
|
51343
|
+
}
|
|
51344
|
+
|
|
51243
51345
|
// apps/runner/src/workbench-syntax.ts
|
|
51244
51346
|
var synapsorDslKeywordValues = [
|
|
51245
51347
|
"ABSOLUTE",
|
|
@@ -53296,12 +53398,12 @@ function renderBoundaryWorkbench(csrfToken) {
|
|
|
53296
53398
|
}
|
|
53297
53399
|
}
|
|
53298
53400
|
|
|
53299
|
-
|
|
53300
|
-
|
|
53301
|
-
|
|
53302
|
-
|
|
53303
|
-
|
|
53304
|
-
|
|
53401
|
+
function renderClientConfigs(){
|
|
53402
|
+
const command="npx -y @synapsor/runner mcp serve --authoring --project-root .";
|
|
53403
|
+
const config={mcpServers:{synapsor_authoring:{command:"npx",args:["-y","@synapsor/runner","mcp","serve","--authoring","--project-root","."]}}};
|
|
53404
|
+
const codex='[mcp_servers.synapsor_authoring]\\ncommand = "npx"\\nargs = '+JSON.stringify(config.mcpServers.synapsor_authoring.args);
|
|
53405
|
+
byId("client-configs").innerHTML='<p>Every path receives the same two local authoring tools and no approval or commit tool. Runner never puts database credentials in these files.</p><h3>Managed project installers</h3><p>Each command previews and owns only the <code>synapsor</code> entry, preserves other project settings, and creates a backup before editing an existing file.</p><pre>'+esc("synapsor-runner mcp install cursor --project --authoring --project-root . --yes\\nsynapsor-runner mcp install claude-code --project --authoring --project-root . --yes\\nsynapsor-runner mcp install vscode --project --authoring --project-root . --yes")+'</pre><h3>Generic stdio MCP</h3><pre>'+esc(JSON.stringify(config,null,2))+'</pre><h3>Direct server command</h3><p>Use this in another local MCP client. No model API key is needed by Runner.</p><pre>'+esc(command)+'</pre><h3>Codex</h3><pre>'+esc(codex)+'</pre>';
|
|
53406
|
+
}
|
|
53305
53407
|
|
|
53306
53408
|
async function loadProtect(preferredRef=preferredProtectQueryRef){
|
|
53307
53409
|
const status=byId("protect-message");
|
|
@@ -57485,7 +57587,7 @@ function protectedQueryTests(capability, plan, boundaryDigest) {
|
|
|
57485
57587
|
async function writeDraftArtifacts(input) {
|
|
57486
57588
|
await fs9.mkdir(input.outputRoot, { recursive: true, mode: 448 });
|
|
57487
57589
|
const markerPath = path9.join(input.outputRoot, ".synapsor-protected-query.json");
|
|
57488
|
-
const existing = await
|
|
57590
|
+
const existing = await readOptionalJson(markerPath);
|
|
57489
57591
|
if (existing && existing.schema_version !== PROTECTED_QUERY_VERSION) {
|
|
57490
57592
|
throw new Error(`Refusing to overwrite unmanaged protected-query directory ${input.outputRoot}.`);
|
|
57491
57593
|
}
|
|
@@ -57497,7 +57599,7 @@ async function writeDraftArtifacts(input) {
|
|
|
57497
57599
|
await writeAtomic(markerPath, json3({ schema_version: PROTECTED_QUERY_VERSION, capability: input.draft.capability }), 384);
|
|
57498
57600
|
}
|
|
57499
57601
|
async function addProtectedContractToRuntimeConfig(input) {
|
|
57500
|
-
const existing = await
|
|
57602
|
+
const existing = await readOptionalJson(input.configPath);
|
|
57501
57603
|
const relativeContract = relativeConfigPath(path9.dirname(input.configPath), input.contractPath);
|
|
57502
57604
|
const config = existing ?? {
|
|
57503
57605
|
version: 1,
|
|
@@ -57710,7 +57812,7 @@ async function writeAtomic(filePath, content, mode) {
|
|
|
57710
57812
|
throw error;
|
|
57711
57813
|
}
|
|
57712
57814
|
}
|
|
57713
|
-
async function
|
|
57815
|
+
async function readOptionalJson(filePath) {
|
|
57714
57816
|
try {
|
|
57715
57817
|
return JSON.parse(await fs9.readFile(filePath, "utf8"));
|
|
57716
57818
|
} catch (error) {
|
|
@@ -58560,14 +58662,14 @@ async function guidedActionDraftDetails(projectRootInput, capabilityName2) {
|
|
|
58560
58662
|
}
|
|
58561
58663
|
async function guidedActionStatus(projectRootInput) {
|
|
58562
58664
|
const projectRoot = path11.resolve(projectRootInput);
|
|
58563
|
-
const index = await
|
|
58665
|
+
const index = await readOptionalJson2(path11.join(projectRoot, ".synapsor/guided-action-drafts.json"));
|
|
58564
58666
|
const drafts = Array.isArray(index?.drafts) ? index.drafts.filter((item) => isRecord13(item) && item.schema_version === GUIDED_ACTION_VERSION && item.state === "disabled" && typeof item.capability === "string") : [];
|
|
58565
58667
|
const activeRoot = path11.join(projectRoot, GUIDED_ACTION_ROOT, "active");
|
|
58566
58668
|
const activations = [];
|
|
58567
58669
|
try {
|
|
58568
58670
|
for (const entry of await fs11.readdir(activeRoot, { withFileTypes: true })) {
|
|
58569
58671
|
if (!entry.isFile() || !entry.name.endsWith(".active.json")) continue;
|
|
58570
|
-
const parsed = await
|
|
58672
|
+
const parsed = await readOptionalJson2(path11.join(activeRoot, entry.name));
|
|
58571
58673
|
if (parsed && parsed.schema_version === GUIDED_ACTION_VERSION && parsed.state === "active" && typeof parsed.capability === "string") {
|
|
58572
58674
|
activations.push(parsed);
|
|
58573
58675
|
}
|
|
@@ -58966,7 +59068,7 @@ function guidedActionReview(draft, action, resource) {
|
|
|
58966
59068
|
}
|
|
58967
59069
|
async function writeGuidedActionArtifacts(input) {
|
|
58968
59070
|
const markerPath = path11.join(input.outputRoot, ".synapsor-guided-action.json");
|
|
58969
|
-
const existing = await
|
|
59071
|
+
const existing = await readOptionalJson2(markerPath);
|
|
58970
59072
|
if (existing && (existing.schema_version !== GUIDED_ACTION_VERSION || existing.capability !== input.draft.capability)) {
|
|
58971
59073
|
throw new Error(`GUIDED_ACTION_OUTPUT_UNMANAGED: refusing to overwrite ${input.outputRoot}.`);
|
|
58972
59074
|
}
|
|
@@ -58981,7 +59083,7 @@ async function writeGuidedActionArtifacts(input) {
|
|
|
58981
59083
|
}
|
|
58982
59084
|
async function updateActionIndex(projectRoot, draft) {
|
|
58983
59085
|
const indexPath = path11.join(projectRoot, ".synapsor/guided-action-drafts.json");
|
|
58984
|
-
const existing = await
|
|
59086
|
+
const existing = await readOptionalJson2(indexPath);
|
|
58985
59087
|
if (existing && existing.schema_version !== "synapsor.guided-action-drafts.v1" && existing.schema_version !== GUIDED_ACTION_INDEX_VERSION) {
|
|
58986
59088
|
throw new Error("GUIDED_ACTION_INDEX_UNMANAGED: refusing to replace an unknown action index.");
|
|
58987
59089
|
}
|
|
@@ -59131,7 +59233,7 @@ function relativeConfigPath2(configDirectory, target2) {
|
|
|
59131
59233
|
const relative2 = path11.relative(configDirectory, target2).replace(/\\/g, "/");
|
|
59132
59234
|
return relative2 === "." || relative2.startsWith("./") || relative2.startsWith("../") ? relative2 : `./${relative2}`;
|
|
59133
59235
|
}
|
|
59134
|
-
async function
|
|
59236
|
+
async function readOptionalJson2(filePath) {
|
|
59135
59237
|
try {
|
|
59136
59238
|
const parsed = JSON.parse(await fs11.readFile(filePath, "utf8"));
|
|
59137
59239
|
if (!isRecord13(parsed)) throw new Error(`Expected a JSON object at ${filePath}.`);
|
|
@@ -60670,7 +60772,7 @@ async function activateSafeActionDraft(input) {
|
|
|
60670
60772
|
activated_at: activatedAt
|
|
60671
60773
|
});
|
|
60672
60774
|
} catch (error) {
|
|
60673
|
-
await
|
|
60775
|
+
await writeTextAtomic2(configPath, previousConfigText).catch(() => void 0);
|
|
60674
60776
|
throw error;
|
|
60675
60777
|
}
|
|
60676
60778
|
return active;
|
|
@@ -61190,10 +61292,10 @@ async function writeDigestArtifact(filePath, content, immutable = false) {
|
|
|
61190
61292
|
if (immutable) await fs15.chmod(filePath, 256).catch(() => void 0);
|
|
61191
61293
|
}
|
|
61192
61294
|
async function writeJsonAtomic2(filePath, value) {
|
|
61193
|
-
await
|
|
61295
|
+
await writeTextAtomic2(filePath, `${JSON.stringify(value, null, 2)}
|
|
61194
61296
|
`);
|
|
61195
61297
|
}
|
|
61196
|
-
async function
|
|
61298
|
+
async function writeTextAtomic2(filePath, value) {
|
|
61197
61299
|
await fs15.mkdir(path15.dirname(filePath), { recursive: true, mode: 448 });
|
|
61198
61300
|
const temporary = path15.join(path15.dirname(filePath), `.${path15.basename(filePath)}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
61199
61301
|
try {
|
|
@@ -61213,7 +61315,7 @@ async function writeNewText(filePath, value, force) {
|
|
|
61213
61315
|
} catch (error) {
|
|
61214
61316
|
if (error.code !== "ENOENT") throw error;
|
|
61215
61317
|
}
|
|
61216
|
-
await
|
|
61318
|
+
await writeTextAtomic2(filePath, value);
|
|
61217
61319
|
}
|
|
61218
61320
|
function relativeProjectPath3(projectRoot, filePath) {
|
|
61219
61321
|
const relative2 = normalizePath(path15.relative(projectRoot, filePath));
|
|
@@ -62786,7 +62888,7 @@ function createScopedExploreMcpServer(runtime) {
|
|
|
62786
62888
|
}).strict().optional()
|
|
62787
62889
|
}).strict();
|
|
62788
62890
|
const server = new McpServer2(
|
|
62789
|
-
{ name: "synapsor-runner-authoring", version:
|
|
62891
|
+
{ name: "synapsor-runner-authoring", version: package_default.version },
|
|
62790
62892
|
{ capabilities: { tools: {} } }
|
|
62791
62893
|
);
|
|
62792
62894
|
server.registerTool(SCOPED_EXPLORE_DESCRIBE_TOOL, {
|
|
@@ -63005,7 +63107,7 @@ async function connectAuthoringSurface(input) {
|
|
|
63005
63107
|
async function connectSurface(kind, server, closeRuntime) {
|
|
63006
63108
|
const client = new Client({
|
|
63007
63109
|
name: `synapsor-workbench-ask-${kind}`,
|
|
63008
|
-
version:
|
|
63110
|
+
version: package_default.version
|
|
63009
63111
|
});
|
|
63010
63112
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
63011
63113
|
try {
|
|
@@ -63229,7 +63331,7 @@ async function handleRequest(input) {
|
|
|
63229
63331
|
const candidate = progress?.candidate ?? recommendedWorkbenchCandidate(draft);
|
|
63230
63332
|
const reviewDecisions = boundaryReviewDecisions(candidate);
|
|
63231
63333
|
const confirmedDecisions = progress?.confirmed_decisions ?? [];
|
|
63232
|
-
const active = await
|
|
63334
|
+
const active = await readOptionalJson3(path16.join(projectRoot, ".synapsor/exploration-boundary.active.json"));
|
|
63233
63335
|
const instantCandidate = instantWorkbenchCandidate(draft);
|
|
63234
63336
|
sendJson(response, 200, {
|
|
63235
63337
|
ok: true,
|
|
@@ -63444,7 +63546,7 @@ async function handleRequest(input) {
|
|
|
63444
63546
|
path16.join(boundaryRoot, "exploration-boundary.draft.json"),
|
|
63445
63547
|
"utf8"
|
|
63446
63548
|
));
|
|
63447
|
-
const active = await
|
|
63549
|
+
const active = await readOptionalJson3(
|
|
63448
63550
|
path16.join(projectRoot, ".synapsor/exploration-boundary.active.json")
|
|
63449
63551
|
);
|
|
63450
63552
|
if (!active || active.activation.digest !== expectedActiveDigest) {
|
|
@@ -63549,7 +63651,7 @@ async function handleRequest(input) {
|
|
|
63549
63651
|
);
|
|
63550
63652
|
const previousProgress = await readBoundaryReviewProgress(projectRoot, previousDraft);
|
|
63551
63653
|
const previousActive = activeBoundaryEventMetadata(
|
|
63552
|
-
await
|
|
63654
|
+
await readOptionalJson3(path16.join(projectRoot, ".synapsor/exploration-boundary.active.json"))
|
|
63553
63655
|
);
|
|
63554
63656
|
const overrides = applyManagedBoundaryReviewDecision(
|
|
63555
63657
|
await loadAutoBoundaryReviewOverrides(projectRoot),
|
|
@@ -63706,7 +63808,7 @@ async function handleRequest(input) {
|
|
|
63706
63808
|
throw new Error("Schema or review inputs changed after preview; preview the rescan again.");
|
|
63707
63809
|
}
|
|
63708
63810
|
const previousActive = activeBoundaryEventMetadata(
|
|
63709
|
-
await
|
|
63811
|
+
await readOptionalJson3(path16.join(projectRoot, ".synapsor/exploration-boundary.active.json"))
|
|
63710
63812
|
);
|
|
63711
63813
|
const previousDraft = JSON.parse(
|
|
63712
63814
|
await fs16.readFile(path16.join(boundaryRoot, "exploration-boundary.draft.json"), "utf8")
|
|
@@ -63802,7 +63904,7 @@ async function handleRequest(input) {
|
|
|
63802
63904
|
throw new Error("Start over requires the exact confirmation START OVER REVIEW.");
|
|
63803
63905
|
}
|
|
63804
63906
|
const previousActive = activeBoundaryEventMetadata(
|
|
63805
|
-
await
|
|
63907
|
+
await readOptionalJson3(path16.join(projectRoot, ".synapsor/exploration-boundary.active.json"))
|
|
63806
63908
|
);
|
|
63807
63909
|
const prepared = await prepareAutoBoundaryRescan({
|
|
63808
63910
|
projectRoot,
|
|
@@ -64030,7 +64132,7 @@ async function handleRequest(input) {
|
|
|
64030
64132
|
throw new Error("Protected-capability activation requires capability_name, expected_digest, confirmation, and actor.");
|
|
64031
64133
|
}
|
|
64032
64134
|
const previousExploreAuthority = activeBoundaryEventMetadata(
|
|
64033
|
-
await
|
|
64135
|
+
await readOptionalJson3(path16.join(projectRoot, ".synapsor/exploration-boundary.active.json"))
|
|
64034
64136
|
);
|
|
64035
64137
|
const active = await activateProtectedQuery({
|
|
64036
64138
|
projectRoot,
|
|
@@ -64096,7 +64198,7 @@ async function handleRequest(input) {
|
|
|
64096
64198
|
return;
|
|
64097
64199
|
}
|
|
64098
64200
|
const previousActive = activeBoundaryEventMetadata(
|
|
64099
|
-
await
|
|
64201
|
+
await readOptionalJson3(path16.join(projectRoot, ".synapsor/exploration-boundary.active.json"))
|
|
64100
64202
|
);
|
|
64101
64203
|
const disabled = await disableScopedExplore(projectRoot);
|
|
64102
64204
|
if (disabled.disabled && previousActive) {
|
|
@@ -66564,13 +66666,13 @@ function workbenchReceiptRecord(record) {
|
|
|
66564
66666
|
}
|
|
66565
66667
|
async function resolveWorkbenchDeploymentProfile(projectRoot, configured) {
|
|
66566
66668
|
if (configured) return configured;
|
|
66567
|
-
const active = await
|
|
66568
|
-
const draft = await
|
|
66669
|
+
const active = await readOptionalJson3(path16.join(projectRoot, ".synapsor/exploration-boundary.active.json"));
|
|
66670
|
+
const draft = await readOptionalJson3(path16.join(projectRoot, "synapsor/generated/exploration-boundary.draft.json"));
|
|
66569
66671
|
const profile = isRecord18(active) ? active.deployment_profile : isRecord18(draft) ? draft.deployment_profile : void 0;
|
|
66570
66672
|
return profile === "development" || profile === "staging" || profile === "production" ? profile : "unknown";
|
|
66571
66673
|
}
|
|
66572
66674
|
async function workbenchOperatorPosture(configPath) {
|
|
66573
|
-
const rawConfig = await
|
|
66675
|
+
const rawConfig = await readOptionalJson3(path16.resolve(configPath));
|
|
66574
66676
|
const operator = isRecord18(rawConfig) ? asRecord4(rawConfig.operator_identity) : {};
|
|
66575
66677
|
const provider = operator.provider === "signed_key" || operator.provider === "jwt_oidc" ? operator.provider : "dev_env";
|
|
66576
66678
|
const applyRoles = Array.isArray(operator.apply_roles) ? operator.apply_roles.filter((role) => typeof role === "string") : [];
|
|
@@ -66622,7 +66724,7 @@ async function readJsonBody(request) {
|
|
|
66622
66724
|
if (!isRecord18(parsed)) throw new Error("request body must be a JSON object");
|
|
66623
66725
|
return parsed;
|
|
66624
66726
|
}
|
|
66625
|
-
async function
|
|
66727
|
+
async function readOptionalJson3(filePath) {
|
|
66626
66728
|
try {
|
|
66627
66729
|
return JSON.parse(await fs16.readFile(filePath, "utf8"));
|
|
66628
66730
|
} catch (error) {
|
|
@@ -66687,7 +66789,7 @@ function instantActivationActor() {
|
|
|
66687
66789
|
}
|
|
66688
66790
|
async function readBoundaryReviewProgress(projectRoot, draft) {
|
|
66689
66791
|
const filePath = path16.join(projectRoot, ".synapsor/boundary-review-progress.json");
|
|
66690
|
-
const raw = await
|
|
66792
|
+
const raw = await readOptionalJson3(filePath);
|
|
66691
66793
|
if (raw === null) return void 0;
|
|
66692
66794
|
if (!isRecord18(raw) || raw.schema_version !== BOUNDARY_REVIEW_PROGRESS_VERSION && raw.schema_version !== LEGACY_BOUNDARY_REVIEW_PROGRESS_VERSION || !isRecord18(raw.candidate) || !Array.isArray(raw.confirmed_decisions) || raw.confirmed_decisions.some((decision) => typeof decision !== "string") || typeof raw.updated_at !== "string") {
|
|
66693
66795
|
throw new Error("Saved boundary-review progress is invalid; use explicit Rescan or Start over rather than trusting it.");
|
|
@@ -67025,11 +67127,11 @@ function askWorkbenchAccessFailure(profile, host) {
|
|
|
67025
67127
|
return void 0;
|
|
67026
67128
|
}
|
|
67027
67129
|
async function workbenchAskAuthority(input) {
|
|
67028
|
-
const active = await
|
|
67130
|
+
const active = await readOptionalJson3(path16.join(input.projectRoot, ".synapsor/exploration-boundary.active.json"));
|
|
67029
67131
|
const activation2 = isRecord18(active) ? asRecord4(active.activation) : {};
|
|
67030
67132
|
const activeBoundaryDigest = typeof activation2.digest === "string" && /^sha256:[a-f0-9]{64}$/.test(activation2.digest) ? activation2.digest : void 0;
|
|
67031
67133
|
const toolSurfaceDigest = askToolSurfaceDigest(input.tools);
|
|
67032
|
-
const runtimeConfig = await
|
|
67134
|
+
const runtimeConfig = await readOptionalJson3(input.configPath);
|
|
67033
67135
|
if (!isRecord18(runtimeConfig)) {
|
|
67034
67136
|
throw new AskError("ASK_RUNTIME_CONFIG_UNAVAILABLE", "Ask could not bind consent to the active Runner configuration.", 409);
|
|
67035
67137
|
}
|
|
@@ -75156,7 +75258,7 @@ async function writeGeneratedOnboardingFiles(output, generated, selection, force
|
|
|
75156
75258
|
process2.stdout.write(`created onboarding manifest ${displayPath2(manifestPath)}
|
|
75157
75259
|
`);
|
|
75158
75260
|
if (options.printNext !== false) {
|
|
75159
|
-
process2.stdout.write(`Next: set the referenced environment variables, run \`${cliCommandName()} config validate --config ${configArgument}\`, then add the reviewed tools to
|
|
75261
|
+
process2.stdout.write(`Next: set the referenced environment variables, run \`${cliCommandName()} config validate --config ${configArgument}\`, then add the reviewed tools to a project client with \`${cliCommandName()} mcp install <cursor|claude-code|vscode> --project --config ${configArgument}\`.
|
|
75160
75262
|
`);
|
|
75161
75263
|
}
|
|
75162
75264
|
return artifacts;
|
|
@@ -77756,7 +77858,7 @@ async function localDoctor(args) {
|
|
|
77756
77858
|
level: forbiddenTools.length === 0 ? "pass" : "fail",
|
|
77757
77859
|
message: forbiddenTools.length === 0 ? "MCP tool catalog is semantic-only." : `Forbidden model-facing tools: ${forbiddenTools.join(", ")}`
|
|
77758
77860
|
});
|
|
77759
|
-
checks.push(...await
|
|
77861
|
+
checks.push(...await managedMcpProjectDoctorChecks(configPath, tools2, args));
|
|
77760
77862
|
const report = {
|
|
77761
77863
|
ok: checks.every((check) => check.level !== "fail"),
|
|
77762
77864
|
mode: String(parsed.mode),
|
|
@@ -77781,78 +77883,100 @@ async function localDoctor(args) {
|
|
|
77781
77883
|
}
|
|
77782
77884
|
return report.ok ? 0 : 1;
|
|
77783
77885
|
}
|
|
77784
|
-
async function
|
|
77886
|
+
async function managedMcpProjectDoctorChecks(configPath, expectedTools, args) {
|
|
77785
77887
|
const projectRoot = path29.resolve(optionalArg(args, "--project-root") ?? path29.dirname(path29.resolve(configPath)));
|
|
77786
|
-
|
|
77787
|
-
|
|
77788
|
-
|
|
77789
|
-
|
|
77790
|
-
|
|
77791
|
-
|
|
77792
|
-
|
|
77793
|
-
|
|
77794
|
-
|
|
77795
|
-
|
|
77796
|
-
|
|
77797
|
-
|
|
77798
|
-
|
|
77799
|
-
|
|
77800
|
-
|
|
77801
|
-
message: status.message
|
|
77802
|
-
}];
|
|
77803
|
-
}
|
|
77804
|
-
const authoring = isCursorAuthoringEntry(status.entry);
|
|
77805
|
-
const authoringTools = ["app.describe_data", "app.explore_data"];
|
|
77806
|
-
const reviewedTools = authoring ? authoringTools : expectedTools;
|
|
77807
|
-
const recordedConfig = path29.resolve(projectRoot, status.paths.configArgument);
|
|
77808
|
-
const configMatches = recordedConfig === path29.resolve(configPath);
|
|
77809
|
-
const checks = [{
|
|
77810
|
-
name: "cursor-project:installation",
|
|
77811
|
-
ok: authoring || configMatches,
|
|
77812
|
-
level: authoring || configMatches ? "pass" : "fail",
|
|
77813
|
-
message: authoring ? "Runner owns an intact local authoring Cursor entry; it does not depend on a production Runner config." : configMatches ? `Runner owns an intact project Cursor entry for ${status.paths.configArgument}.` : `Cursor entry points to ${status.paths.configArgument}, but doctor inspected ${path29.resolve(configPath)}.`
|
|
77814
|
-
}, {
|
|
77815
|
-
name: "cursor-project:model-tools",
|
|
77816
|
-
ok: reviewedTools.length > 0,
|
|
77817
|
-
level: reviewedTools.length > 0 ? "pass" : "fail",
|
|
77818
|
-
message: authoring ? `Authoring tools: ${authoringTools.join(", ")}. Scoped Explore remains local development/staging only; activation, approval, apply, revert, credentials, and trusted identity remain outside MCP.` : `Reviewed model-facing tools: ${expectedTools.join(", ") || "none"}. Approval, apply, revert, policy, credentials, and trusted identity remain outside MCP.`
|
|
77888
|
+
const clients = ["cursor", "claude-code", "vscode"];
|
|
77889
|
+
const requestedLaunch = args.includes("--check-cursor") ? "cursor" : optionalArg(args, "--check-mcp-client");
|
|
77890
|
+
const launchClient = requestedLaunch === void 0 ? void 0 : parseManagedMcpProjectClient(requestedLaunch);
|
|
77891
|
+
const statuses = await Promise.all(clients.map(async (client) => ({
|
|
77892
|
+
client,
|
|
77893
|
+
definition: managedMcpProjectDefinition(client),
|
|
77894
|
+
status: await managedMcpProjectStatus(client, projectRoot)
|
|
77895
|
+
})));
|
|
77896
|
+
const relevant = statuses.filter(({ status, client }) => status.state !== "not_installed" || client === launchClient);
|
|
77897
|
+
if (relevant.length === 0) {
|
|
77898
|
+
return [{
|
|
77899
|
+
name: "mcp-project:installation",
|
|
77900
|
+
ok: true,
|
|
77901
|
+
level: "warn",
|
|
77902
|
+
message: `No Runner-owned project MCP entry is installed. Preview one with ${cliCommandName()} mcp install <cursor|claude-code|vscode> --project --project-root ${projectRoot} --dry-run.`
|
|
77819
77903
|
}];
|
|
77820
|
-
|
|
77904
|
+
}
|
|
77905
|
+
const checks = [];
|
|
77906
|
+
for (const { client, definition, status } of relevant) {
|
|
77907
|
+
const prefix = `${client}-project`;
|
|
77908
|
+
try {
|
|
77909
|
+
if (status.state === "not_installed") {
|
|
77910
|
+
checks.push({
|
|
77911
|
+
name: `${prefix}:installation`,
|
|
77912
|
+
ok: false,
|
|
77913
|
+
level: "fail",
|
|
77914
|
+
message: `No Runner-owned ${definition.displayName} project entry is installed, so the requested launch check cannot run.`
|
|
77915
|
+
});
|
|
77916
|
+
continue;
|
|
77917
|
+
}
|
|
77918
|
+
if (status.state !== "installed") {
|
|
77919
|
+
checks.push({
|
|
77920
|
+
name: `${prefix}:installation`,
|
|
77921
|
+
ok: false,
|
|
77922
|
+
level: "fail",
|
|
77923
|
+
message: status.message
|
|
77924
|
+
});
|
|
77925
|
+
continue;
|
|
77926
|
+
}
|
|
77927
|
+
const authoring = isManagedAuthoringEntry(status.entry);
|
|
77928
|
+
const authoringTools = ["app.describe_data", "app.explore_data"];
|
|
77929
|
+
const reviewedTools = authoring ? authoringTools : expectedTools;
|
|
77930
|
+
const recordedConfig = path29.resolve(projectRoot, status.paths.configArgument);
|
|
77931
|
+
const configMatches = recordedConfig === path29.resolve(configPath);
|
|
77821
77932
|
checks.push({
|
|
77822
|
-
name:
|
|
77823
|
-
ok:
|
|
77824
|
-
level: "
|
|
77825
|
-
message: `
|
|
77933
|
+
name: `${prefix}:installation`,
|
|
77934
|
+
ok: authoring || configMatches,
|
|
77935
|
+
level: authoring || configMatches ? "pass" : "fail",
|
|
77936
|
+
message: authoring ? `Runner owns an intact local authoring ${definition.displayName} entry; it does not depend on a production Runner config.` : configMatches ? `Runner owns an intact ${definition.displayName} project entry for ${status.paths.configArgument}.` : `${definition.displayName} entry points to ${status.paths.configArgument}, but doctor inspected ${path29.resolve(configPath)}.`
|
|
77937
|
+
}, {
|
|
77938
|
+
name: `${prefix}:model-tools`,
|
|
77939
|
+
ok: reviewedTools.length > 0,
|
|
77940
|
+
level: reviewedTools.length > 0 ? "pass" : "fail",
|
|
77941
|
+
message: authoring ? `Authoring tools: ${authoringTools.join(", ")}. Scoped Explore remains local development/staging only; activation, approval, apply, revert, credentials, and trusted identity remain outside MCP.` : `Reviewed model-facing tools: ${expectedTools.join(", ") || "none"}. Approval, apply, revert, policy, credentials, and trusted identity remain outside MCP.`
|
|
77826
77942
|
});
|
|
77827
|
-
|
|
77828
|
-
|
|
77829
|
-
|
|
77830
|
-
|
|
77831
|
-
|
|
77832
|
-
|
|
77833
|
-
|
|
77834
|
-
|
|
77835
|
-
|
|
77836
|
-
|
|
77837
|
-
|
|
77838
|
-
|
|
77839
|
-
|
|
77840
|
-
|
|
77841
|
-
|
|
77842
|
-
|
|
77843
|
-
|
|
77844
|
-
|
|
77845
|
-
|
|
77846
|
-
|
|
77847
|
-
|
|
77848
|
-
|
|
77849
|
-
|
|
77850
|
-
|
|
77851
|
-
|
|
77852
|
-
|
|
77853
|
-
|
|
77854
|
-
}
|
|
77943
|
+
if (launchClient !== client) {
|
|
77944
|
+
checks.push({
|
|
77945
|
+
name: `${prefix}:launch`,
|
|
77946
|
+
ok: true,
|
|
77947
|
+
level: "warn",
|
|
77948
|
+
message: `${definition.displayName} command was not launched. Rerun with --check-mcp-client ${client} to perform a real stdio initialize + tools/list handshake.`
|
|
77949
|
+
});
|
|
77950
|
+
continue;
|
|
77951
|
+
}
|
|
77952
|
+
const entry = status.entry;
|
|
77953
|
+
const command = typeof entry?.command === "string" ? entry.command : "";
|
|
77954
|
+
const commandArgs = Array.isArray(entry?.args) && entry.args.every((value) => typeof value === "string") ? entry.args : [];
|
|
77955
|
+
if (!command) {
|
|
77956
|
+
checks.push({ name: `${prefix}:launch`, ok: false, level: "fail", message: `${definition.displayName} Synapsor entry has no valid command.` });
|
|
77957
|
+
continue;
|
|
77958
|
+
}
|
|
77959
|
+
const timeoutMs = Number(optionalArg(args, "--timeout-ms") ?? "10000");
|
|
77960
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 12e4) throw new Error("--timeout-ms must be an integer from 100 to 120000");
|
|
77961
|
+
const response = await fetchStdioMcpToolsCommand(command, commandArgs, timeoutMs, projectRoot);
|
|
77962
|
+
const liveTools = mcpAuditToolNames(response);
|
|
77963
|
+
const matches = stableStringArray(liveTools).join("\n") === stableStringArray(reviewedTools).join("\n");
|
|
77964
|
+
checks.push({
|
|
77965
|
+
name: `${prefix}:launch`,
|
|
77966
|
+
ok: matches,
|
|
77967
|
+
level: matches ? "pass" : "fail",
|
|
77968
|
+
message: matches ? `Configured ${definition.displayName} command started and exposed exactly ${liveTools.length} reviewed tool(s).` : `Configured command exposed ${liveTools.join(", ") || "no tools"}; expected ${reviewedTools.join(", ") || "no tools"}.`
|
|
77969
|
+
});
|
|
77970
|
+
} catch (error) {
|
|
77971
|
+
checks.push({
|
|
77972
|
+
name: `${prefix}:installation`,
|
|
77973
|
+
ok: false,
|
|
77974
|
+
level: "fail",
|
|
77975
|
+
message: `${definition.displayName} project verification failed: ${safeErrorMessage(error)}`
|
|
77976
|
+
});
|
|
77977
|
+
}
|
|
77855
77978
|
}
|
|
77979
|
+
return checks;
|
|
77856
77980
|
}
|
|
77857
77981
|
async function httpSecurityDoctorChecks(config, args) {
|
|
77858
77982
|
const requestedTransport = optionalArg(args, "--transport") ?? "stdio";
|
|
@@ -81851,11 +81975,12 @@ async function mcp(args) {
|
|
|
81851
81975
|
return 2;
|
|
81852
81976
|
}
|
|
81853
81977
|
async function mcpProjectInstall(args) {
|
|
81854
|
-
const [
|
|
81855
|
-
|
|
81856
|
-
|
|
81978
|
+
const [clientValue, ...rest] = args;
|
|
81979
|
+
const client = parseManagedMcpProjectClient(clientValue);
|
|
81980
|
+
const definition = managedMcpProjectDefinition(client);
|
|
81981
|
+
assertKnownOptions(rest, /* @__PURE__ */ new Set(["--project", "--project-root", "--config", "--store", "--authoring", "--dry-run", "--yes", "--json"]), `mcp install ${client}`);
|
|
81857
81982
|
if (!rest.includes("--project")) {
|
|
81858
|
-
throw new Error(
|
|
81983
|
+
throw new Error(`mcp install ${client} requires --project so Runner changes only the current project's ${definition.destination}`);
|
|
81859
81984
|
}
|
|
81860
81985
|
const projectRoot = path29.resolve(optionalArg(rest, "--project-root") ?? process2.cwd());
|
|
81861
81986
|
const authoring = rest.includes("--authoring");
|
|
@@ -81870,42 +81995,44 @@ async function mcpProjectInstall(args) {
|
|
|
81870
81995
|
await readRuntimeConfig(absoluteConfig);
|
|
81871
81996
|
const boundary = await inspectMcpToolBoundary(["--config", absoluteConfig, "--store", ":memory:"]);
|
|
81872
81997
|
if (!boundary.ok) {
|
|
81873
|
-
throw new Error(
|
|
81998
|
+
throw new Error(`${definition.displayName} install refused because the reviewed model-facing boundary failed: ${boundary.checks.filter((check) => !check.ok).map((check) => check.name).join(", ")}`);
|
|
81874
81999
|
}
|
|
81875
82000
|
toolNames = boundary.names;
|
|
81876
82001
|
}
|
|
81877
|
-
const preview = await
|
|
81878
|
-
const report =
|
|
82002
|
+
const preview = await previewManagedMcpProjectInstall({ client, projectRoot, configPath, storePath, authoring });
|
|
82003
|
+
const report = managedMcpProjectLifecycleReport(client, preview, toolNames, authoring);
|
|
81879
82004
|
const json7 = rest.includes("--json");
|
|
81880
82005
|
if (rest.includes("--dry-run") || preview.action === "unchanged") {
|
|
81881
82006
|
if (json7) process2.stdout.write(`${JSON.stringify(report, null, 2)}
|
|
81882
82007
|
`);
|
|
81883
|
-
else process2.stdout.write(
|
|
82008
|
+
else process2.stdout.write(formatManagedMcpProjectPreview(report));
|
|
81884
82009
|
return 0;
|
|
81885
82010
|
}
|
|
81886
|
-
if (!json7) process2.stdout.write(
|
|
82011
|
+
if (!json7) process2.stdout.write(formatManagedMcpProjectPreview(report));
|
|
81887
82012
|
await confirmDangerousAction(rest, `Install the reviewed Synapsor MCP entry in ${path29.relative(projectRoot, preview.paths.destination)}?`);
|
|
81888
|
-
const installed = await
|
|
82013
|
+
const installed = await installManagedMcpProject({ client, projectRoot, configPath, storePath, authoring });
|
|
81889
82014
|
if (json7) {
|
|
81890
82015
|
process2.stdout.write(`${JSON.stringify({ ...report, action: installed.action, installed: true, backup: installed.backup ?? null }, null, 2)}
|
|
81891
82016
|
`);
|
|
81892
82017
|
} else {
|
|
81893
|
-
process2.stdout.write(
|
|
82018
|
+
process2.stdout.write(`${definition.displayName} project MCP entry installed at ${path29.relative(projectRoot, installed.paths.destination)}.
|
|
81894
82019
|
`);
|
|
81895
82020
|
if (installed.backup) process2.stdout.write(`Backup: ${installed.backup}
|
|
81896
82021
|
`);
|
|
81897
|
-
process2.stdout.write(
|
|
82022
|
+
process2.stdout.write(`${definition.reloadInstruction}, then run \`synapsor-runner mcp status ${client} --project\` to verify the reviewed tool boundary.
|
|
82023
|
+
`);
|
|
81898
82024
|
}
|
|
81899
82025
|
return 0;
|
|
81900
82026
|
}
|
|
81901
82027
|
async function mcpProjectStatus(args) {
|
|
81902
|
-
const [
|
|
81903
|
-
|
|
81904
|
-
|
|
81905
|
-
|
|
82028
|
+
const [clientValue, ...rest] = args;
|
|
82029
|
+
const client = parseManagedMcpProjectClient(clientValue);
|
|
82030
|
+
const definition = managedMcpProjectDefinition(client);
|
|
82031
|
+
assertKnownOptions(rest, /* @__PURE__ */ new Set(["--project", "--project-root", "--json", "--check-launch", "--timeout-ms"]), `mcp status ${client}`);
|
|
82032
|
+
if (!rest.includes("--project")) throw new Error(`mcp status ${client} requires --project`);
|
|
81906
82033
|
const projectRoot = path29.resolve(optionalArg(rest, "--project-root") ?? process2.cwd());
|
|
81907
|
-
const status = await
|
|
81908
|
-
const authoring = status.state === "installed" &&
|
|
82034
|
+
const status = await managedMcpProjectStatus(client, projectRoot);
|
|
82035
|
+
const authoring = status.state === "installed" && isManagedAuthoringEntry(status.entry);
|
|
81909
82036
|
let tools2 = [];
|
|
81910
82037
|
let launch = {
|
|
81911
82038
|
checked: false,
|
|
@@ -81929,7 +82056,7 @@ async function mcpProjectStatus(args) {
|
|
|
81929
82056
|
const entry = status.entry;
|
|
81930
82057
|
const command = typeof entry?.command === "string" ? entry.command : "";
|
|
81931
82058
|
const commandArgs = Array.isArray(entry?.args) && entry.args.every((value) => typeof value === "string") ? entry.args : [];
|
|
81932
|
-
if (!command) throw new Error(
|
|
82059
|
+
if (!command) throw new Error(`${definition.displayName} Synapsor entry does not contain a valid command`);
|
|
81933
82060
|
const timeoutMs = Number(optionalArg(rest, "--timeout-ms") ?? "10000");
|
|
81934
82061
|
if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 12e4) throw new Error("--timeout-ms must be an integer from 100 to 120000");
|
|
81935
82062
|
const response = await fetchStdioMcpToolsCommand(command, commandArgs, timeoutMs, projectRoot);
|
|
@@ -81938,12 +82065,14 @@ async function mcpProjectStatus(args) {
|
|
|
81938
82065
|
launch = {
|
|
81939
82066
|
checked: true,
|
|
81940
82067
|
ok: matches,
|
|
81941
|
-
message: matches ?
|
|
82068
|
+
message: matches ? `${definition.displayName} command started and exposed exactly ${liveNames.length} reviewed tool(s).` : `Started command exposed ${liveNames.join(", ") || "no tools"}; expected ${tools2.join(", ") || "no tools"}.`
|
|
81942
82069
|
};
|
|
81943
82070
|
}
|
|
81944
82071
|
}
|
|
81945
82072
|
const report = {
|
|
81946
82073
|
ok: status.state === "installed" && launch.ok,
|
|
82074
|
+
client,
|
|
82075
|
+
client_name: definition.displayName,
|
|
81947
82076
|
state: status.state,
|
|
81948
82077
|
mode: authoring ? "authoring" : "runtime",
|
|
81949
82078
|
message: status.message,
|
|
@@ -81956,18 +82085,21 @@ async function mcpProjectStatus(args) {
|
|
|
81956
82085
|
};
|
|
81957
82086
|
if (rest.includes("--json")) process2.stdout.write(`${JSON.stringify(report, null, 2)}
|
|
81958
82087
|
`);
|
|
81959
|
-
else process2.stdout.write(
|
|
82088
|
+
else process2.stdout.write(formatManagedMcpProjectStatus(report));
|
|
81960
82089
|
return report.ok ? 0 : 1;
|
|
81961
82090
|
}
|
|
81962
82091
|
async function mcpProjectUninstall(args) {
|
|
81963
|
-
const [
|
|
81964
|
-
|
|
81965
|
-
|
|
81966
|
-
|
|
82092
|
+
const [clientValue, ...rest] = args;
|
|
82093
|
+
const client = parseManagedMcpProjectClient(clientValue);
|
|
82094
|
+
const definition = managedMcpProjectDefinition(client);
|
|
82095
|
+
assertKnownOptions(rest, /* @__PURE__ */ new Set(["--project", "--project-root", "--dry-run", "--yes", "--json"]), `mcp uninstall ${client}`);
|
|
82096
|
+
if (!rest.includes("--project")) throw new Error(`mcp uninstall ${client} requires --project`);
|
|
81967
82097
|
const projectRoot = path29.resolve(optionalArg(rest, "--project-root") ?? process2.cwd());
|
|
81968
|
-
const status = await
|
|
82098
|
+
const status = await managedMcpProjectStatus(client, projectRoot);
|
|
81969
82099
|
const preview = {
|
|
81970
82100
|
ok: status.state === "installed" || status.state === "not_installed",
|
|
82101
|
+
client,
|
|
82102
|
+
client_name: definition.displayName,
|
|
81971
82103
|
state: status.state,
|
|
81972
82104
|
changed: status.state === "installed",
|
|
81973
82105
|
destination: path29.relative(projectRoot, status.paths.destination),
|
|
@@ -81986,45 +82118,50 @@ ${preview.changed ? `Will remove only the Runner-owned Synapsor entry from ${pre
|
|
|
81986
82118
|
if (!json7) process2.stdout.write(`${preview.message}
|
|
81987
82119
|
Will remove only the Runner-owned Synapsor entry from ${preview.destination}.
|
|
81988
82120
|
`);
|
|
81989
|
-
if (!preview.ok) throw new Error(
|
|
82121
|
+
if (!preview.ok) throw new Error(`${definition.displayName} project entry is unowned or changed; refusing to remove it automatically`);
|
|
81990
82122
|
await confirmDangerousAction(rest, `Remove only the Runner-owned Synapsor MCP entry from ${preview.destination}?`);
|
|
81991
|
-
const removed = await
|
|
82123
|
+
const removed = await uninstallManagedMcpProject({ client, projectRoot });
|
|
81992
82124
|
if (json7) process2.stdout.write(`${JSON.stringify({ ...preview, changed: removed.changed, backup: removed.backup ?? null }, null, 2)}
|
|
81993
82125
|
`);
|
|
81994
|
-
else process2.stdout.write(`Removed Runner-owned
|
|
82126
|
+
else process2.stdout.write(`Removed Runner-owned ${definition.displayName} project MCP entry. Backup: ${removed.backup}
|
|
81995
82127
|
`);
|
|
81996
82128
|
return 0;
|
|
81997
82129
|
}
|
|
81998
|
-
function
|
|
82130
|
+
function managedMcpProjectLifecycleReport(client, preview, tools2, authoring = false) {
|
|
82131
|
+
const definition = managedMcpProjectDefinition(client);
|
|
81999
82132
|
return {
|
|
82000
82133
|
ok: true,
|
|
82134
|
+
client,
|
|
82135
|
+
client_name: definition.displayName,
|
|
82001
82136
|
action: preview.action,
|
|
82002
82137
|
mode: authoring ? "authoring" : "runtime",
|
|
82003
82138
|
destination: path29.relative(preview.paths.projectRoot, preview.paths.destination),
|
|
82004
82139
|
config: authoring ? null : preview.paths.configArgument,
|
|
82005
82140
|
store: authoring ? null : preview.paths.storeArgument,
|
|
82006
82141
|
preserves_other_servers: true,
|
|
82007
|
-
|
|
82142
|
+
credentials_in_client_config: false,
|
|
82143
|
+
...client === "cursor" ? { credentials_in_cursor_config: false } : {},
|
|
82008
82144
|
tools: tools2,
|
|
82009
82145
|
not_exposed_to_mcp: defaultBlockedToolSurface()
|
|
82010
82146
|
};
|
|
82011
82147
|
}
|
|
82012
|
-
function
|
|
82148
|
+
function formatManagedMcpProjectPreview(report) {
|
|
82013
82149
|
const tools2 = Array.isArray(report.tools) ? report.tools.join(", ") : "";
|
|
82150
|
+
const clientName = String(report.client_name);
|
|
82014
82151
|
return [
|
|
82015
|
-
|
|
82152
|
+
`${clientName} project MCP ${String(report.action)} preview`,
|
|
82016
82153
|
`Mode: ${String(report.mode)}`,
|
|
82017
82154
|
`Destination: ${String(report.destination)}`,
|
|
82018
82155
|
...report.mode === "authoring" ? ["Project root: .", "Transport: local stdio only"] : [`Runner config: ${String(report.config)}`, `Runner store: ${String(report.store)}`],
|
|
82019
82156
|
`Model-facing tools: ${tools2 || "none"}`,
|
|
82020
82157
|
"Approval, apply, revert, policy, credentials, and trusted identity stay outside MCP.",
|
|
82021
|
-
|
|
82158
|
+
`Other ${clientName} MCP servers and project settings are preserved.`,
|
|
82022
82159
|
""
|
|
82023
82160
|
].join("\n");
|
|
82024
82161
|
}
|
|
82025
|
-
function
|
|
82162
|
+
function formatManagedMcpProjectStatus(report) {
|
|
82026
82163
|
return [
|
|
82027
|
-
|
|
82164
|
+
`${report.client_name} project MCP: ${report.state}`,
|
|
82028
82165
|
report.message,
|
|
82029
82166
|
`Mode: ${report.mode}`,
|
|
82030
82167
|
`Destination: ${report.destination}`,
|
|
@@ -82035,7 +82172,7 @@ function formatCursorProjectStatus(report) {
|
|
|
82035
82172
|
""
|
|
82036
82173
|
].join("\n");
|
|
82037
82174
|
}
|
|
82038
|
-
function
|
|
82175
|
+
function isManagedAuthoringEntry(entry) {
|
|
82039
82176
|
return Array.isArray(entry?.args) && entry.args.every((value) => typeof value === "string") && entry.args.includes("--authoring");
|
|
82040
82177
|
}
|
|
82041
82178
|
function stableStringArray(values) {
|
|
@@ -91605,7 +91742,7 @@ the local reviewed contract and proposal before writeback.
|
|
|
91605
91742
|
${cmd} boundary activate --headless --review-bundle boundary-review.json --config ./synapsor/synapsor.runner.json --confirm "ACTIVATE sha256:..." --identity reviewer --identity-key ./reviewer.pem --required-role boundary_reviewer --reason "Reviewed staging authority"
|
|
91606
91743
|
${cmd} boundary status [--project-root .] [--json]
|
|
91607
91744
|
${cmd} boundary diff [--project-root .] [--json]
|
|
91608
|
-
${cmd} mcp install cursor --project --authoring --project-root . --yes
|
|
91745
|
+
${cmd} mcp install <cursor|claude-code|vscode> --project --authoring --project-root . --yes
|
|
91609
91746
|
|
|
91610
91747
|
Draft the whole deterministic application boundary without opening a browser,
|
|
91611
91748
|
inspect/export its disabled review state, and compare the generation lock with
|
|
@@ -91618,7 +91755,8 @@ exact digest confirmation, a short-lived nonce-bound decision, and a configured
|
|
|
91618
91755
|
signed_key or jwt_oidc operator identity carrying the required role. --yes and
|
|
91619
91756
|
an actor string are never sufficient. Workbench and CLI converge on the same
|
|
91620
91757
|
activation checks. After activation, --authoring installs exactly
|
|
91621
|
-
app.describe_data and app.explore_data in the
|
|
91758
|
+
app.describe_data and app.explore_data in the selected Cursor, Claude Code, or
|
|
91759
|
+
VS Code project. Scoped
|
|
91622
91760
|
Explore remains local stdio only and is absent from production and remote HTTP.
|
|
91623
91761
|
`,
|
|
91624
91762
|
action: `Usage:
|
|
@@ -91681,10 +91819,10 @@ Drizzle input is parsed as a bounded TypeScript AST and is never imported or run
|
|
|
91681
91819
|
${cmd} mcp serve-http --config ./synapsor.runner.json --store ./.synapsor/local.db --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
|
|
91682
91820
|
${cmd} mcp config --absolute-paths --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
91683
91821
|
${cmd} mcp client-config --client openai-agents --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
91684
|
-
${cmd} mcp install cursor --project --authoring [--project-root .] [--dry-run]
|
|
91685
|
-
${cmd} mcp install cursor --project [--dry-run] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
|
|
91686
|
-
${cmd} mcp status cursor --project [--check-launch]
|
|
91687
|
-
${cmd} mcp uninstall cursor --project [--dry-run]
|
|
91822
|
+
${cmd} mcp install <cursor|claude-code|vscode> --project --authoring [--project-root .] [--dry-run]
|
|
91823
|
+
${cmd} mcp install <cursor|claude-code|vscode> --project [--dry-run] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db]
|
|
91824
|
+
${cmd} mcp status <cursor|claude-code|vscode> --project [--check-launch]
|
|
91825
|
+
${cmd} mcp uninstall <cursor|claude-code|vscode> --project [--dry-run]
|
|
91688
91826
|
${cmd} mcp audit --example dangerous-db-mcp
|
|
91689
91827
|
${cmd} mcp audit ./tools-list.json
|
|
91690
91828
|
${cmd} mcp audit generate ./tools-list.json --output ./synapsor-audit-candidates
|
|
@@ -91694,10 +91832,11 @@ Stdio opens no network socket and needs no HTTP credential. Networked MCP is aut
|
|
|
91694
91832
|
MCP clients see semantic tools. They do not receive raw SQL, write credentials, approval tools, or commit tools.
|
|
91695
91833
|
`,
|
|
91696
91834
|
"mcp install": `Usage:
|
|
91697
|
-
${cmd} mcp install cursor --project --authoring [--project-root .] [--dry-run] [--yes]
|
|
91698
|
-
${cmd} mcp install cursor --project [--project-root .] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db] [--dry-run] [--yes]
|
|
91835
|
+
${cmd} mcp install <cursor|claude-code|vscode> --project --authoring [--project-root .] [--dry-run] [--yes]
|
|
91836
|
+
${cmd} mcp install <cursor|claude-code|vscode> --project [--project-root .] [--config ./synapsor.runner.json] [--store ./.synapsor/local.db] [--dry-run] [--yes]
|
|
91699
91837
|
|
|
91700
|
-
Preview, confirm, and merge a project-scoped Synapsor entry into
|
|
91838
|
+
Preview, confirm, and merge a project-scoped Synapsor entry into
|
|
91839
|
+
.cursor/mcp.json, .mcp.json, or .vscode/mcp.json.
|
|
91701
91840
|
Runner preserves other servers/settings, creates a backup before changing an
|
|
91702
91841
|
existing file, records explicit ownership, and never writes database URLs,
|
|
91703
91842
|
credentials, trusted identity, approval, apply, revert, or policy authority.
|
|
@@ -91708,16 +91847,16 @@ runtime config. Re-run without --authoring after Protect activates a production
|
|
|
91708
91847
|
named capability.
|
|
91709
91848
|
`,
|
|
91710
91849
|
"mcp status": `Usage:
|
|
91711
|
-
${cmd} mcp status cursor --project [--project-root .] [--check-launch] [--timeout-ms 10000] [--json]
|
|
91850
|
+
${cmd} mcp status <cursor|claude-code|vscode> --project [--project-root .] [--check-launch] [--timeout-ms 10000] [--json]
|
|
91712
91851
|
|
|
91713
|
-
Verify Runner's project-scoped
|
|
91852
|
+
Verify Runner's project-scoped client ownership marker and print the exact
|
|
91714
91853
|
reviewed model-facing tools. --check-launch performs a real stdio initialize +
|
|
91715
91854
|
tools/list handshake with the configured command.
|
|
91716
91855
|
`,
|
|
91717
91856
|
"mcp uninstall": `Usage:
|
|
91718
|
-
${cmd} mcp uninstall cursor --project [--project-root .] [--dry-run] [--yes] [--json]
|
|
91857
|
+
${cmd} mcp uninstall <cursor|claude-code|vscode> --project [--project-root .] [--dry-run] [--yes] [--json]
|
|
91719
91858
|
|
|
91720
|
-
Remove only the Runner-owned Synapsor entry. Other
|
|
91859
|
+
Remove only the Runner-owned Synapsor entry. Other client MCP servers and
|
|
91721
91860
|
project settings are preserved, and edited/unowned entries fail closed.
|
|
91722
91861
|
`,
|
|
91723
91862
|
tools: `Usage:
|
|
@@ -91922,6 +92061,7 @@ security guarantee.
|
|
|
91922
92061
|
${cmd} doctor --config synapsor.runner.json --check-handlers
|
|
91923
92062
|
${cmd} doctor --config synapsor.runner.json --check-writeback
|
|
91924
92063
|
${cmd} doctor --config synapsor.runner.json --check-rls
|
|
92064
|
+
${cmd} doctor --config synapsor.runner.json --check-mcp-client <cursor|claude-code|vscode>
|
|
91925
92065
|
${cmd} doctor --config synapsor.runner.json --transport streamable-http --host 127.0.0.1 --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
|
|
91926
92066
|
${cmd} doctor --config synapsor.runner.json --transport streamable-http --host 0.0.0.0 --trusted-tls-proxy
|
|
91927
92067
|
${cmd} doctor --config synapsor.runner.json --transport streamable-http --host 0.0.0.0 --tls-cert-env SYNAPSOR_TLS_CERT_PEM --tls-key-env SYNAPSOR_TLS_KEY_PEM
|