mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-23 02:23:33 +08:00
fix: install native Cursor hook and MCP config (#1543)
* fix: install native cursor hook and MCP config * fix: avoid false healthy stdio mcp probes
This commit is contained in:
@@ -184,6 +184,41 @@ function addFileCopyOperation(operations, options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function readJsonObject(filePath, label) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse ${label} at ${filePath}: ${error.message}`);
|
||||
}
|
||||
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error(`Invalid ${label} at ${filePath}: expected a JSON object`);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function addJsonMergeOperation(operations, options) {
|
||||
const sourcePath = path.join(options.sourceRoot, options.sourceRelativePath);
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
operations.push({
|
||||
kind: 'merge-json',
|
||||
moduleId: options.moduleId,
|
||||
sourceRelativePath: options.sourceRelativePath,
|
||||
destinationPath: options.destinationPath,
|
||||
strategy: 'merge-json',
|
||||
ownership: 'managed',
|
||||
scaffoldOnly: false,
|
||||
mergePayload: readJsonObject(sourcePath, options.sourceRelativePath),
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function addMatchingRuleOperations(operations, options) {
|
||||
const sourceDir = path.join(options.sourceRoot, options.sourceRelativeDir);
|
||||
if (!fs.existsSync(sourceDir)) {
|
||||
@@ -342,10 +377,10 @@ function planCursorLegacyInstall(context) {
|
||||
sourceRelativePath: path.join('.cursor', 'hooks.json'),
|
||||
destinationPath: path.join(targetRoot, 'hooks.json'),
|
||||
});
|
||||
addFileCopyOperation(operations, {
|
||||
addJsonMergeOperation(operations, {
|
||||
moduleId: 'legacy-cursor-install',
|
||||
sourceRoot: context.sourceRoot,
|
||||
sourceRelativePath: path.join('.cursor', 'mcp.json'),
|
||||
sourceRelativePath: '.mcp.json',
|
||||
destinationPath: path.join(targetRoot, 'mcp.json'),
|
||||
});
|
||||
|
||||
@@ -540,6 +575,22 @@ function createLegacyCompatInstallPlan(options = {}) {
|
||||
}
|
||||
|
||||
function materializeScaffoldOperation(sourceRoot, operation) {
|
||||
if (operation.kind === 'merge-json') {
|
||||
return [{
|
||||
kind: 'merge-json',
|
||||
moduleId: operation.moduleId,
|
||||
sourceRelativePath: operation.sourceRelativePath,
|
||||
destinationPath: operation.destinationPath,
|
||||
strategy: operation.strategy || 'merge-json',
|
||||
ownership: operation.ownership || 'managed',
|
||||
scaffoldOnly: Object.hasOwn(operation, 'scaffoldOnly') ? operation.scaffoldOnly : false,
|
||||
mergePayload: readJsonObject(
|
||||
path.join(sourceRoot, operation.sourceRelativePath),
|
||||
operation.sourceRelativePath
|
||||
),
|
||||
}];
|
||||
}
|
||||
|
||||
const sourcePath = path.join(sourceRoot, operation.sourceRelativePath);
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
return [];
|
||||
|
||||
@@ -18,6 +18,39 @@ function toCursorRuleFileName(fileName, sourceRelativeFile) {
|
||||
: fileName;
|
||||
}
|
||||
|
||||
function readJsonObject(filePath, label) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse ${label} at ${filePath}: ${error.message}`);
|
||||
}
|
||||
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error(`Invalid ${label} at ${filePath}: expected a JSON object`);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function createJsonMergeOperation({ moduleId, repoRoot, sourceRelativePath, destinationPath }) {
|
||||
const sourcePath = path.join(repoRoot, sourceRelativePath);
|
||||
if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createManagedOperation({
|
||||
kind: 'merge-json',
|
||||
moduleId,
|
||||
sourceRelativePath,
|
||||
destinationPath,
|
||||
strategy: 'merge-json',
|
||||
ownership: 'managed',
|
||||
scaffoldOnly: false,
|
||||
mergePayload: readJsonObject(sourcePath, sourceRelativePath),
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = createInstallTargetAdapter({
|
||||
id: 'cursor-project',
|
||||
target: 'cursor',
|
||||
@@ -93,6 +126,13 @@ module.exports = createInstallTargetAdapter({
|
||||
}
|
||||
|
||||
return entries.flatMap(({ module, sourceRelativePath }) => {
|
||||
const cursorMcpOperation = createJsonMergeOperation({
|
||||
moduleId: module.id,
|
||||
repoRoot,
|
||||
sourceRelativePath: '.mcp.json',
|
||||
destinationPath: path.join(targetRoot, 'mcp.json'),
|
||||
});
|
||||
|
||||
if (sourceRelativePath === 'rules') {
|
||||
return takeUniqueOperations(createFlatRuleOperations({
|
||||
moduleId: module.id,
|
||||
@@ -127,7 +167,21 @@ module.exports = createInstallTargetAdapter({
|
||||
destinationNameTransform: toCursorRuleFileName,
|
||||
});
|
||||
|
||||
return takeUniqueOperations([...childOperations, ...ruleOperations]);
|
||||
return takeUniqueOperations([
|
||||
...childOperations,
|
||||
...(cursorMcpOperation ? [cursorMcpOperation] : []),
|
||||
...ruleOperations,
|
||||
]);
|
||||
}
|
||||
|
||||
if (sourceRelativePath === 'mcp-configs') {
|
||||
const operations = [
|
||||
adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput),
|
||||
];
|
||||
if (cursorMcpOperation) {
|
||||
operations.push(cursorMcpOperation);
|
||||
}
|
||||
return takeUniqueOperations(operations);
|
||||
}
|
||||
|
||||
return takeUniqueOperations([
|
||||
|
||||
@@ -21,6 +21,38 @@ function readJsonObject(filePath, label) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function cloneJsonValue(value) {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function deepMergeJson(baseValue, patchValue) {
|
||||
if (!isPlainObject(baseValue) || !isPlainObject(patchValue)) {
|
||||
return cloneJsonValue(patchValue);
|
||||
}
|
||||
|
||||
const merged = { ...baseValue };
|
||||
for (const [key, value] of Object.entries(patchValue)) {
|
||||
if (isPlainObject(value) && isPlainObject(merged[key])) {
|
||||
merged[key] = deepMergeJson(merged[key], value);
|
||||
} else {
|
||||
merged[key] = cloneJsonValue(value);
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
function formatJson(value) {
|
||||
return `${JSON.stringify(value, null, 2)}\n`;
|
||||
}
|
||||
|
||||
function replacePluginRootPlaceholders(value, pluginRoot) {
|
||||
if (!pluginRoot) {
|
||||
return value;
|
||||
@@ -56,44 +88,6 @@ function isMcpConfigPath(filePath) {
|
||||
return basename === '.mcp.json' || basename === 'mcp.json';
|
||||
}
|
||||
|
||||
function buildFilteredMcpWrites(plan) {
|
||||
const disabledServers = parseDisabledMcpServers(process.env.ECC_DISABLED_MCPS);
|
||||
if (disabledServers.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const writes = [];
|
||||
|
||||
for (const operation of plan.operations) {
|
||||
if (!isMcpConfigPath(operation.destinationPath) || !operation.sourcePath || !fs.existsSync(operation.sourcePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let sourceConfig;
|
||||
try {
|
||||
sourceConfig = readJsonObject(operation.sourcePath, 'MCP config');
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sourceConfig.mcpServers || typeof sourceConfig.mcpServers !== 'object' || Array.isArray(sourceConfig.mcpServers)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filtered = filterMcpConfig(sourceConfig, disabledServers);
|
||||
if (filtered.removed.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
writes.push({
|
||||
destinationPath: operation.destinationPath,
|
||||
filteredConfig: filtered.config,
|
||||
});
|
||||
}
|
||||
|
||||
return writes;
|
||||
}
|
||||
|
||||
function buildResolvedClaudeHooks(plan) {
|
||||
if (!plan.adapter || plan.adapter.target !== 'claude') {
|
||||
return null;
|
||||
@@ -123,10 +117,38 @@ function buildResolvedClaudeHooks(plan) {
|
||||
|
||||
function applyInstallPlan(plan) {
|
||||
const resolvedClaudeHooksPlan = buildResolvedClaudeHooks(plan);
|
||||
const filteredMcpWrites = buildFilteredMcpWrites(plan);
|
||||
const disabledServers = parseDisabledMcpServers(process.env.ECC_DISABLED_MCPS);
|
||||
|
||||
for (const operation of plan.operations) {
|
||||
fs.mkdirSync(path.dirname(operation.destinationPath), { recursive: true });
|
||||
|
||||
if (operation.kind === 'merge-json') {
|
||||
const payload = cloneJsonValue(operation.mergePayload);
|
||||
if (payload === undefined) {
|
||||
throw new Error(`Missing merge payload for ${operation.destinationPath}`);
|
||||
}
|
||||
|
||||
const filteredPayload = (
|
||||
isMcpConfigPath(operation.destinationPath) && disabledServers.length > 0
|
||||
)
|
||||
? filterMcpConfig(payload, disabledServers).config
|
||||
: payload;
|
||||
|
||||
const currentValue = fs.existsSync(operation.destinationPath)
|
||||
? readJsonObject(operation.destinationPath, 'existing JSON config')
|
||||
: {};
|
||||
const mergedValue = deepMergeJson(currentValue, filteredPayload);
|
||||
fs.writeFileSync(operation.destinationPath, formatJson(mergedValue), 'utf8');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operation.kind === 'copy-file' && isMcpConfigPath(operation.destinationPath) && disabledServers.length > 0) {
|
||||
const sourceConfig = readJsonObject(operation.sourcePath, 'MCP config');
|
||||
const filteredConfig = filterMcpConfig(sourceConfig, disabledServers).config;
|
||||
fs.writeFileSync(operation.destinationPath, formatJson(filteredConfig), 'utf8');
|
||||
continue;
|
||||
}
|
||||
|
||||
fs.copyFileSync(operation.sourcePath, operation.destinationPath);
|
||||
}
|
||||
|
||||
@@ -139,15 +161,6 @@ function applyInstallPlan(plan) {
|
||||
);
|
||||
}
|
||||
|
||||
for (const writePlan of filteredMcpWrites) {
|
||||
fs.mkdirSync(path.dirname(writePlan.destinationPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
writePlan.destinationPath,
|
||||
JSON.stringify(writePlan.filteredConfig, null, 2) + '\n',
|
||||
'utf8'
|
||||
);
|
||||
}
|
||||
|
||||
writeInstallState(plan.installStatePath, plan.statePreview);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user