@evomap/evolver-mcp 2.0.0-beta.1 → 2.0.0-beta.2
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/antigravityInstaller.d.ts +32 -0
- package/dist/antigravityInstaller.js +271 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/injection.d.ts +3 -3
- package/dist/injection.js +12 -4
- package/dist/installer.d.ts +14 -6
- package/dist/installer.js +14 -5
- package/dist/proxyClient.d.ts +17 -0
- package/dist/proxyClient.js +25 -0
- package/dist/stdio.js +0 -0
- package/dist/tools.js +61 -0
- package/package.json +3 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { InjectionPlan, McpServerCmd, RuntimeId } from './injection.js';
|
|
2
|
+
import { type InstallOptions, type InstallResult, type UninstallOptions } from './installer.js';
|
|
3
|
+
export declare const ANTIGRAVITY_NAMESPACES: readonly ["antigravity", "antigravity-ide"];
|
|
4
|
+
export interface AntigravityConfigTarget {
|
|
5
|
+
namespace: (typeof ANTIGRAVITY_NAMESPACES)[number];
|
|
6
|
+
root: string;
|
|
7
|
+
configPath: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class AntigravityConfigShapeError extends Error {
|
|
10
|
+
constructor(path: string, detail: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class AntigravityPathTypeError extends Error {
|
|
13
|
+
constructor(label: string, path: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class AntigravitySecretRefusedError extends Error {
|
|
16
|
+
constructor(detail: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve Antigravity's user-level config targets. Every existing runtime root is targeted. If neither namespace
|
|
20
|
+
* exists, installation falls back to the canonical ~/.gemini/antigravity root. This function never creates paths.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveAntigravityConfigTargets(homeDir?: string): AntigravityConfigTarget[];
|
|
23
|
+
export declare function antigravityMcpServerEntry(server: McpServerCmd): Record<string, unknown>;
|
|
24
|
+
export declare function mergeAntigravityConfig(current: Record<string, unknown>, serverEntry: Record<string, unknown>): Record<string, unknown>;
|
|
25
|
+
export declare function stripAntigravityManaged(current: Record<string, unknown>): {
|
|
26
|
+
changed: boolean;
|
|
27
|
+
data: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
/** Install MCP tool discovery only. Antigravity has no verified SessionStart hook contract. */
|
|
30
|
+
export declare function installAntigravity(plan: InjectionPlan, opts: InstallOptions): InstallResult;
|
|
31
|
+
/** Remove only mcpServers.evolver. The shared config file and all unrelated user content always remain. */
|
|
32
|
+
export declare function uninstallAntigravity(runtime: RuntimeId, opts: UninstallOptions): InstallResult;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { homedir as osHomedir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { util } from '@evomap/evolver-core';
|
|
6
|
+
import { EmptySharedConfigError, SymlinkRefusedError, UnparseableConfigError, } from './installer.js';
|
|
7
|
+
const ENV_FILE_KEY = 'EVOLVER_ENV_FILE';
|
|
8
|
+
const CONFIG_FILE = 'mcp_config.json';
|
|
9
|
+
const CONFIG_WRITE_RETRIES = 5;
|
|
10
|
+
const NEW_CONFIG_MODE = 0o600;
|
|
11
|
+
const NEW_DIR_MODE = 0o700;
|
|
12
|
+
export const ANTIGRAVITY_NAMESPACES = ['antigravity', 'antigravity-ide'];
|
|
13
|
+
export class AntigravityConfigShapeError extends Error {
|
|
14
|
+
constructor(path, detail) {
|
|
15
|
+
super(`[setup-hooks] refusing to overwrite Antigravity MCP config (${path}): ${detail}. Fix the shared config, then rerun.`);
|
|
16
|
+
this.name = 'AntigravityConfigShapeError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class AntigravityPathTypeError extends Error {
|
|
20
|
+
constructor(label, path) {
|
|
21
|
+
super(`[setup-hooks] refusing to operate: ${label} (${path}) exists but is not a directory.`);
|
|
22
|
+
this.name = 'AntigravityPathTypeError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class AntigravitySecretRefusedError extends Error {
|
|
26
|
+
constructor(detail) {
|
|
27
|
+
super(`[setup-hooks] refusing to write Antigravity MCP config: ${detail}; use only an ${ENV_FILE_KEY} pointer to a credential file.`);
|
|
28
|
+
this.name = 'AntigravitySecretRefusedError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const isObj = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
32
|
+
function lstatIfExists(path) {
|
|
33
|
+
try {
|
|
34
|
+
return lstatSync(path);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.code === 'ENOENT')
|
|
38
|
+
return undefined;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function assertDirectoryOrMissing(path, label) {
|
|
43
|
+
const stat = lstatIfExists(path);
|
|
44
|
+
if (stat === undefined)
|
|
45
|
+
return false;
|
|
46
|
+
if (stat.isSymbolicLink())
|
|
47
|
+
throw new SymlinkRefusedError(label, path);
|
|
48
|
+
if (!stat.isDirectory())
|
|
49
|
+
throw new AntigravityPathTypeError(label, path);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
function assertFileNotSymlink(path, label) {
|
|
53
|
+
const stat = lstatIfExists(path);
|
|
54
|
+
if (stat?.isSymbolicLink())
|
|
55
|
+
throw new SymlinkRefusedError(label, path);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve Antigravity's user-level config targets. Every existing runtime root is targeted. If neither namespace
|
|
59
|
+
* exists, installation falls back to the canonical ~/.gemini/antigravity root. This function never creates paths.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveAntigravityConfigTargets(homeDir = osHomedir()) {
|
|
62
|
+
assertDirectoryOrMissing(homeDir, 'home directory');
|
|
63
|
+
const geminiRoot = join(homeDir, '.gemini');
|
|
64
|
+
assertDirectoryOrMissing(geminiRoot, '~/.gemini');
|
|
65
|
+
const candidates = ANTIGRAVITY_NAMESPACES.map((namespace) => {
|
|
66
|
+
const root = join(geminiRoot, namespace);
|
|
67
|
+
return { namespace, root, configPath: join(root, CONFIG_FILE) };
|
|
68
|
+
});
|
|
69
|
+
const existing = candidates.filter((target) => assertDirectoryOrMissing(target.root, `~/.gemini/${target.namespace}`));
|
|
70
|
+
const targets = existing.length > 0 ? existing : [candidates[0]];
|
|
71
|
+
for (const target of targets) {
|
|
72
|
+
assertFileNotSymlink(target.configPath, `~/.gemini/${target.namespace}/${CONFIG_FILE}`);
|
|
73
|
+
}
|
|
74
|
+
return targets;
|
|
75
|
+
}
|
|
76
|
+
function validateConfigShape(data, path) {
|
|
77
|
+
const mcpServers = data['mcpServers'];
|
|
78
|
+
if (mcpServers !== undefined && !isObj(mcpServers)) {
|
|
79
|
+
throw new AntigravityConfigShapeError(path, 'mcpServers must be a JSON object');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function readStrictSnapshot(path) {
|
|
83
|
+
let raw;
|
|
84
|
+
try {
|
|
85
|
+
raw = readFileSync(path, 'utf8');
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error.code === 'ENOENT')
|
|
89
|
+
return { data: {}, raw: null };
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
const trimmed = raw.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
throw new EmptySharedConfigError('Antigravity MCP config', path, 'Antigravity');
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(trimmed);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw new UnparseableConfigError('Antigravity MCP config', path, 'Antigravity');
|
|
101
|
+
}
|
|
102
|
+
if (!isObj(parsed)) {
|
|
103
|
+
throw new AntigravityConfigShapeError(path, 'the top-level JSON value must be an object');
|
|
104
|
+
}
|
|
105
|
+
validateConfigShape(parsed, path);
|
|
106
|
+
return { data: parsed, raw };
|
|
107
|
+
}
|
|
108
|
+
function readRawIfExists(path) {
|
|
109
|
+
try {
|
|
110
|
+
return readFileSync(path, 'utf8');
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error.code === 'ENOENT')
|
|
114
|
+
return null;
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function existingFileMode(path) {
|
|
119
|
+
try {
|
|
120
|
+
return statSync(path).mode & 0o777;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error.code === 'ENOENT')
|
|
124
|
+
return undefined;
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function writeJsonAtomic(path, data) {
|
|
129
|
+
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
130
|
+
const mode = existingFileMode(path) ?? NEW_CONFIG_MODE;
|
|
131
|
+
const restoreMode = process.platform === 'win32' && existsSync(path) ? existingFileMode(path) : undefined;
|
|
132
|
+
try {
|
|
133
|
+
writeFileSync(tmp, `${JSON.stringify(data, null, 2)}\n`, { encoding: 'utf8', flag: 'wx', mode });
|
|
134
|
+
chmodSync(tmp, mode);
|
|
135
|
+
if (process.platform === 'win32' && existsSync(path))
|
|
136
|
+
chmodSync(path, mode | 0o200);
|
|
137
|
+
renameSync(tmp, path);
|
|
138
|
+
chmodSync(path, mode);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
rmSync(tmp, { force: true });
|
|
142
|
+
if (restoreMode !== undefined) {
|
|
143
|
+
try {
|
|
144
|
+
if (existsSync(path))
|
|
145
|
+
chmodSync(path, restoreMode);
|
|
146
|
+
}
|
|
147
|
+
catch { /* preserve the original error */ }
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function updateConfigWithRetry(path, update) {
|
|
153
|
+
const lockPath = `${path}.evolver.lock`;
|
|
154
|
+
util.acquireLock(lockPath);
|
|
155
|
+
try {
|
|
156
|
+
for (let attempt = 0; attempt < CONFIG_WRITE_RETRIES; attempt++) {
|
|
157
|
+
const snapshot = readStrictSnapshot(path);
|
|
158
|
+
const next = update(snapshot.data);
|
|
159
|
+
if (!next.changed)
|
|
160
|
+
return false;
|
|
161
|
+
if (readRawIfExists(path) !== snapshot.raw)
|
|
162
|
+
continue;
|
|
163
|
+
writeJsonAtomic(path, next.data);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
util.releaseLock(lockPath);
|
|
169
|
+
}
|
|
170
|
+
throw new Error(`[setup-hooks] refusing to overwrite Antigravity MCP config (${path}): the file changed repeatedly while evolver was merging it. Retry after Antigravity finishes writing it.`);
|
|
171
|
+
}
|
|
172
|
+
const SECRET_FLAG_RE = /(?:^|\s)--?(?:api[-_]?key|password|passwd|secret|token)(?:=|\s+)/i;
|
|
173
|
+
const SECRET_ASSIGNMENT_RE = /\b(?:A2A_NODE_SECRET|EVOMAP_ENTERPRISE_TOKEN|EVOMAP_PRIVATE_HUB_TOKEN|EVOMAP_NODE_SECRET|EVOLVER_IPC_TOKEN|EVOLVER_LLM_TOKEN|PHUB_ENTERPRISE_TOKEN|PRIVATE_HUB_ENTERPRISE_TOKEN)\b\s*=/;
|
|
174
|
+
const BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/i;
|
|
175
|
+
function looksLikeEnvFilePointer(value) {
|
|
176
|
+
return /[\\/]/.test(value)
|
|
177
|
+
|| /^[.~$%]/.test(value)
|
|
178
|
+
|| /\.(?:env|dotenv)(?:$|[._-])/i.test(value);
|
|
179
|
+
}
|
|
180
|
+
function validateServer(server) {
|
|
181
|
+
const commandLine = [server.command, ...(server.args ?? [])].join(' ');
|
|
182
|
+
if (SECRET_FLAG_RE.test(commandLine) || SECRET_ASSIGNMENT_RE.test(commandLine) || BEARER_RE.test(commandLine)) {
|
|
183
|
+
throw new AntigravitySecretRefusedError('the MCP command or args contain an inline secret-looking value');
|
|
184
|
+
}
|
|
185
|
+
if (server.env === undefined || Object.keys(server.env).length === 0)
|
|
186
|
+
return;
|
|
187
|
+
const keys = Object.keys(server.env);
|
|
188
|
+
if (keys.some((key) => key !== ENV_FILE_KEY)) {
|
|
189
|
+
throw new AntigravitySecretRefusedError(`env may contain only ${ENV_FILE_KEY}`);
|
|
190
|
+
}
|
|
191
|
+
const pointer = server.env[ENV_FILE_KEY]?.trim();
|
|
192
|
+
if (!pointer || !looksLikeEnvFilePointer(pointer)) {
|
|
193
|
+
throw new AntigravitySecretRefusedError(`${ENV_FILE_KEY} must contain a file path, not a credential value`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export function antigravityMcpServerEntry(server) {
|
|
197
|
+
validateServer(server);
|
|
198
|
+
const pointer = server.env?.[ENV_FILE_KEY]?.trim();
|
|
199
|
+
return {
|
|
200
|
+
command: server.command,
|
|
201
|
+
args: server.args ?? [],
|
|
202
|
+
...(pointer ? { env: { [ENV_FILE_KEY]: pointer } } : {}),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
export function mergeAntigravityConfig(current, serverEntry) {
|
|
206
|
+
const prior = current['mcpServers'];
|
|
207
|
+
const mcpServers = isObj(prior) ? { ...prior } : {};
|
|
208
|
+
mcpServers['evolver'] = serverEntry;
|
|
209
|
+
return { ...current, mcpServers };
|
|
210
|
+
}
|
|
211
|
+
export function stripAntigravityManaged(current) {
|
|
212
|
+
const prior = current['mcpServers'];
|
|
213
|
+
if (!isObj(prior) || !('evolver' in prior))
|
|
214
|
+
return { changed: false, data: current };
|
|
215
|
+
const mcpServers = { ...prior };
|
|
216
|
+
delete mcpServers['evolver'];
|
|
217
|
+
const data = { ...current };
|
|
218
|
+
if (Object.keys(mcpServers).length > 0)
|
|
219
|
+
data['mcpServers'] = mcpServers;
|
|
220
|
+
else
|
|
221
|
+
delete data['mcpServers'];
|
|
222
|
+
return { changed: true, data };
|
|
223
|
+
}
|
|
224
|
+
function hasEvolver(current) {
|
|
225
|
+
const mcpServers = current['mcpServers'];
|
|
226
|
+
return isObj(mcpServers) && 'evolver' in mcpServers;
|
|
227
|
+
}
|
|
228
|
+
function ensureTargetRoot(target) {
|
|
229
|
+
const geminiRoot = dirname(target.root);
|
|
230
|
+
assertDirectoryOrMissing(geminiRoot, '~/.gemini');
|
|
231
|
+
mkdirSync(dirname(target.configPath), { recursive: true, mode: NEW_DIR_MODE });
|
|
232
|
+
assertDirectoryOrMissing(geminiRoot, '~/.gemini');
|
|
233
|
+
assertDirectoryOrMissing(target.root, `~/.gemini/${target.namespace}`);
|
|
234
|
+
assertFileNotSymlink(target.configPath, `~/.gemini/${target.namespace}/${CONFIG_FILE}`);
|
|
235
|
+
}
|
|
236
|
+
/** Install MCP tool discovery only. Antigravity has no verified SessionStart hook contract. */
|
|
237
|
+
export function installAntigravity(plan, opts) {
|
|
238
|
+
const serverEntry = antigravityMcpServerEntry(opts.server);
|
|
239
|
+
const targets = resolveAntigravityConfigTargets(opts.homeDir);
|
|
240
|
+
// Validate every shared config before the first mutation so one corrupt namespace cannot leave a partial install.
|
|
241
|
+
const snapshots = targets.map((target) => ({ target, snapshot: readStrictSnapshot(target.configPath) }));
|
|
242
|
+
if (!opts.force && snapshots.every(({ snapshot }) => hasEvolver(snapshot.data))) {
|
|
243
|
+
return { ok: true, runtime: plan.runtime, mode: plan.mode, files: [], alreadyInstalled: true };
|
|
244
|
+
}
|
|
245
|
+
const files = [];
|
|
246
|
+
for (const { target } of snapshots) {
|
|
247
|
+
ensureTargetRoot(target);
|
|
248
|
+
const changed = updateConfigWithRetry(target.configPath, (current) => {
|
|
249
|
+
if (!opts.force && hasEvolver(current))
|
|
250
|
+
return { changed: false, data: current };
|
|
251
|
+
return { changed: true, data: mergeAntigravityConfig(current, serverEntry) };
|
|
252
|
+
});
|
|
253
|
+
if (changed)
|
|
254
|
+
files.push(target.configPath);
|
|
255
|
+
}
|
|
256
|
+
return { ok: true, runtime: plan.runtime, mode: plan.mode, files };
|
|
257
|
+
}
|
|
258
|
+
/** Remove only mcpServers.evolver. The shared config file and all unrelated user content always remain. */
|
|
259
|
+
export function uninstallAntigravity(runtime, opts) {
|
|
260
|
+
const targets = resolveAntigravityConfigTargets(opts.homeDir);
|
|
261
|
+
const snapshots = targets.map((target) => ({ target, snapshot: readStrictSnapshot(target.configPath) }));
|
|
262
|
+
const files = [];
|
|
263
|
+
for (const { target, snapshot } of snapshots) {
|
|
264
|
+
if (!hasEvolver(snapshot.data))
|
|
265
|
+
continue;
|
|
266
|
+
const changed = updateConfigWithRetry(target.configPath, stripAntigravityManaged);
|
|
267
|
+
if (changed)
|
|
268
|
+
files.push(target.configPath);
|
|
269
|
+
}
|
|
270
|
+
return { ok: true, runtime, mode: 'uninstall', files };
|
|
271
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/injection.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export type RuntimeId = 'claude-code' | 'codex' | 'cursor' | 'kiro' | 'opencode';
|
|
2
|
-
export type InjectionMode = 'mcp-hooks' | 'mcp-plugin' | 'cursor-rules' | 'passive';
|
|
1
|
+
export type RuntimeId = 'claude-code' | 'codex' | 'cursor' | 'antigravity' | 'kiro' | 'opencode';
|
|
2
|
+
export type InjectionMode = 'mcp-hooks' | 'mcp-plugin' | 'mcp-config' | 'cursor-rules' | 'passive';
|
|
3
3
|
/**
|
|
4
4
|
* Setup support contract (#217). A bootstrapper that delegates runtime onboarding to evolver v2 needs a
|
|
5
5
|
* DETERMINISTIC answer for every runtime it might ask about — not just the ones v2 can write config for.
|
|
6
6
|
* The three outcomes are the whole contract:
|
|
7
|
-
* - `installed` v2 can write the runtime config/hooks and verify it (claude-code, codex, cursor
|
|
7
|
+
* - `installed` v2 can write the runtime config/hooks and verify it (claude-code, codex, cursor, antigravity).
|
|
8
8
|
* - `manual` v2 cannot mutate this runtime's config, but the path is real: it prints precise MCP/HTTP
|
|
9
9
|
* wiring the operator does by hand (opencode, openclaw, mcp-generic, http-agent, server).
|
|
10
10
|
* - `unsupported` v2 refuses with a clear reason (an unrecognized runtime id).
|
package/dist/injection.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/** Every runtime in the setup matrix (#217), in a stable order — used for usage text and the unsupported reason. */
|
|
2
2
|
export const SETUP_RUNTIMES = [
|
|
3
|
-
'claude-code', 'codex', 'cursor', 'opencode', 'kiro', 'openclaw', 'mcp-generic', 'http-agent', 'server',
|
|
3
|
+
'claude-code', 'codex', 'cursor', 'antigravity', 'opencode', 'kiro', 'openclaw', 'mcp-generic', 'http-agent', 'server',
|
|
4
4
|
];
|
|
5
|
-
/** Runtimes v2 can write config/hooks for and verify. */
|
|
6
|
-
const INSTALLED_RUNTIMES = new Set(['claude-code', 'codex', 'cursor']);
|
|
5
|
+
/** Runtimes v2 can write config/hooks for and verify. Antigravity is MCP-config-only (no lifecycle hook). */
|
|
6
|
+
const INSTALLED_RUNTIMES = new Set(['claude-code', 'codex', 'cursor', 'antigravity']);
|
|
7
7
|
/** Runtimes with no v2 auto-installer but a real manual path. The reason is the short, honest "do it by hand"
|
|
8
8
|
* line; the precise wiring text is a separate concern (#217 slice 2), not hard-coded here. */
|
|
9
9
|
const MANUAL_RUNTIMES = new Map([
|
|
@@ -59,6 +59,14 @@ export function planInjection(runtime, server) {
|
|
|
59
59
|
config: {},
|
|
60
60
|
note: 'cursor: 渲染静默 top-gene hints 进 .cursor/rules/evolver.mdc (alwaysApply:true); daemon 在 gene 集变化时重写',
|
|
61
61
|
};
|
|
62
|
+
case 'antigravity':
|
|
63
|
+
return {
|
|
64
|
+
runtime, mode: 'mcp-config',
|
|
65
|
+
// Antigravity supports MCP tool discovery through its user-level JSON config. It has no verified
|
|
66
|
+
// SessionStart lifecycle contract, so this mode deliberately registers only the MCP server.
|
|
67
|
+
config: { mcpServers: { evolver: { command: server.command, args: server.args ?? [], ...(server.env ? { env: server.env } : {}) } } },
|
|
68
|
+
note: 'antigravity: write mcpServers.evolver in the user-level MCP config; tool discovery only (no SessionStart hook)',
|
|
69
|
+
};
|
|
62
70
|
case 'kiro':
|
|
63
71
|
case 'opencode':
|
|
64
72
|
return {
|
|
@@ -76,7 +84,7 @@ export function planInjection(runtime, server) {
|
|
|
76
84
|
* 故不计入此处;passive runtime 也为 false. */
|
|
77
85
|
export function injectsTools(runtime) {
|
|
78
86
|
const mode = planInjection(runtime, { command: 'x' }).mode;
|
|
79
|
-
return mode === 'mcp-hooks' || mode === 'mcp-plugin';
|
|
87
|
+
return mode === 'mcp-hooks' || mode === 'mcp-plugin' || mode === 'mcp-config';
|
|
80
88
|
}
|
|
81
89
|
/** 是否为 active 注入(任何把 gene 价值推回 runtime 的方式:MCP 工具发现 或 cursor rules 记忆注入). */
|
|
82
90
|
export function isActiveInjection(runtime) {
|
package/dist/installer.d.ts
CHANGED
|
@@ -37,6 +37,15 @@ export interface InstallOptions {
|
|
|
37
37
|
genes?: readonly CursorGene[];
|
|
38
38
|
/** Cursor only: cap on genes rendered into the always-on rules body (token-tax bound). */
|
|
39
39
|
maxGenes?: number;
|
|
40
|
+
/** Antigravity only: override the user home used to resolve ~/.gemini config roots. Intended for hermetic
|
|
41
|
+
* embedding/tests; normal callers omit it. configRoot and scope do not affect Antigravity's user config. */
|
|
42
|
+
homeDir?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface UninstallOptions {
|
|
45
|
+
configRoot: string;
|
|
46
|
+
scope?: InstallScope;
|
|
47
|
+
/** Antigravity only: override the user home used to resolve ~/.gemini config roots. */
|
|
48
|
+
homeDir?: string;
|
|
40
49
|
}
|
|
41
50
|
export interface InstallResult {
|
|
42
51
|
ok: boolean;
|
|
@@ -60,14 +69,14 @@ export declare class SymlinkRefusedError extends Error {
|
|
|
60
69
|
* .mcp.json/.claude/settings.json are evolver-owned, so their lenient fresh-start behavior stays unchanged.
|
|
61
70
|
*/
|
|
62
71
|
export declare class UnparseableConfigError extends Error {
|
|
63
|
-
constructor(label: string, path: string);
|
|
72
|
+
constructor(label: string, path: string, owner?: string);
|
|
64
73
|
}
|
|
65
74
|
/**
|
|
66
75
|
* Thrown when a SHARED user config exists but is empty or whitespace-only. Claude Code writes these files with a
|
|
67
76
|
* truncating write, so present-empty can be a concurrent-write window rather than a fresh config.
|
|
68
77
|
*/
|
|
69
78
|
export declare class EmptySharedConfigError extends Error {
|
|
70
|
-
constructor(label: string, path: string);
|
|
79
|
+
constructor(label: string, path: string, owner?: string);
|
|
71
80
|
}
|
|
72
81
|
type SharedConfigRaceHook = (path: string, attempt: number) => void;
|
|
73
82
|
export declare function _setSharedConfigRaceHookForTest(hook?: SharedConfigRaceHook): void;
|
|
@@ -90,6 +99,8 @@ export declare function stripManaged(data: Record<string, unknown>): {
|
|
|
90
99
|
* (delegated to codexInstaller; TOML, not JSON). Same hybrid value (tool discovery + session-start injection).
|
|
91
100
|
* - cursor (cursor-rules): renders top genes into <root>/.cursor/rules/evolver.mdc (alwaysApply:true) — gene
|
|
92
101
|
* memory injection, not MCP tool discovery (delegated to cursorRulesInstaller). The daemon keeps it fresh.
|
|
102
|
+
* - antigravity (mcp-config): writes mcpServers.evolver to every existing user-level Antigravity config root,
|
|
103
|
+
* or the canonical root when none exists. MCP tool discovery only; no SessionStart hook is installed.
|
|
93
104
|
* Idempotent + symlink-safe; passive runtimes (kiro/opencode) return ok:false (nothing to inject).
|
|
94
105
|
*/
|
|
95
106
|
export declare function installInjection(plan: InjectionPlan, opts: InstallOptions): InstallResult;
|
|
@@ -97,10 +108,7 @@ export declare function installInjection(plan: InjectionPlan, opts: InstallOptio
|
|
|
97
108
|
* Pass the SAME scope used at install: 'user' cleans ~/.claude.json + ~/.claude/settings.json; 'project'
|
|
98
109
|
* (default) cleans <configRoot>/.mcp.json + <configRoot>/.claude/settings.json. stripManaged only removes
|
|
99
110
|
* the mcpServers.evolver entry (and any evolver-owned hooks/marker), so it is safe on the shared ~/.claude.json. */
|
|
100
|
-
export declare function uninstallInjection(runtime: RuntimeId, opts:
|
|
101
|
-
configRoot: string;
|
|
102
|
-
scope?: InstallScope;
|
|
103
|
-
}): InstallResult;
|
|
111
|
+
export declare function uninstallInjection(runtime: RuntimeId, opts: UninstallOptions): InstallResult;
|
|
104
112
|
/** Convenience: plan + install in one call for a runtime. */
|
|
105
113
|
export declare function setupRuntime(runtime: RuntimeId, opts: InstallOptions): InstallResult;
|
|
106
114
|
export {};
|
package/dist/installer.js
CHANGED
|
@@ -22,6 +22,7 @@ import { installCodex, uninstallCodex } from './codexInstaller.js';
|
|
|
22
22
|
// cursor injection is a different mechanism again (a project rules file, not a config/MCP writer): it renders
|
|
23
23
|
// top genes into .cursor/rules/evolver.mdc. It plugs into the same install/uninstall dispatch below.
|
|
24
24
|
import { installCursorRules, uninstallCursorRules } from './cursorRulesInstaller.js';
|
|
25
|
+
import { installAntigravity, uninstallAntigravity } from './antigravityInstaller.js';
|
|
25
26
|
/** Marks a config file as containing evolver-managed entries, so uninstall only removes what we added. */
|
|
26
27
|
export const MANAGED_MARKER = '_evolver_managed';
|
|
27
28
|
/** A hook entry is evolver-owned if any of its commands mention this — used to replace-not-duplicate on reinstall. */
|
|
@@ -49,8 +50,8 @@ export class SymlinkRefusedError extends Error {
|
|
|
49
50
|
* .mcp.json/.claude/settings.json are evolver-owned, so their lenient fresh-start behavior stays unchanged.
|
|
50
51
|
*/
|
|
51
52
|
export class UnparseableConfigError extends Error {
|
|
52
|
-
constructor(label, path) {
|
|
53
|
-
super(`[setup-hooks] refusing to overwrite ${label} (${path}): the file exists and is non-empty but is not valid JSON. This is
|
|
53
|
+
constructor(label, path, owner = 'Claude Code') {
|
|
54
|
+
super(`[setup-hooks] refusing to overwrite ${label} (${path}): the file exists and is non-empty but is not valid JSON. This is ${owner}'s own shared config; merging into it would replace the whole file and could wipe its contents. Fix or remove the corrupt file, then rerun.`);
|
|
54
55
|
this.name = 'UnparseableConfigError';
|
|
55
56
|
}
|
|
56
57
|
}
|
|
@@ -59,8 +60,8 @@ export class UnparseableConfigError extends Error {
|
|
|
59
60
|
* truncating write, so present-empty can be a concurrent-write window rather than a fresh config.
|
|
60
61
|
*/
|
|
61
62
|
export class EmptySharedConfigError extends Error {
|
|
62
|
-
constructor(label, path) {
|
|
63
|
-
super(`[setup-hooks] refusing to overwrite ${label} (${path}): the file exists but is empty or contains only whitespace.
|
|
63
|
+
constructor(label, path, owner = 'Claude Code') {
|
|
64
|
+
super(`[setup-hooks] refusing to overwrite ${label} (${path}): the file exists but is empty or contains only whitespace. ${owner} may be in the middle of a truncating write, and treating it as fresh config could wipe shared config data. Fix the empty file or retry after ${owner} finishes writing it.`);
|
|
64
65
|
this.name = 'EmptySharedConfigError';
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -386,6 +387,8 @@ function claudeCodeTargets(scope, configRoot) {
|
|
|
386
387
|
* (delegated to codexInstaller; TOML, not JSON). Same hybrid value (tool discovery + session-start injection).
|
|
387
388
|
* - cursor (cursor-rules): renders top genes into <root>/.cursor/rules/evolver.mdc (alwaysApply:true) — gene
|
|
388
389
|
* memory injection, not MCP tool discovery (delegated to cursorRulesInstaller). The daemon keeps it fresh.
|
|
390
|
+
* - antigravity (mcp-config): writes mcpServers.evolver to every existing user-level Antigravity config root,
|
|
391
|
+
* or the canonical root when none exists. MCP tool discovery only; no SessionStart hook is installed.
|
|
389
392
|
* Idempotent + symlink-safe; passive runtimes (kiro/opencode) return ok:false (nothing to inject).
|
|
390
393
|
*/
|
|
391
394
|
export function installInjection(plan, opts) {
|
|
@@ -398,9 +401,12 @@ export function installInjection(plan, opts) {
|
|
|
398
401
|
if (plan.runtime === 'cursor') {
|
|
399
402
|
return installCursorRules({ configRoot: opts.configRoot, genes: opts.genes ?? [], ...(opts.maxGenes !== undefined ? { maxGenes: opts.maxGenes } : {}) });
|
|
400
403
|
}
|
|
404
|
+
if (plan.runtime === 'antigravity') {
|
|
405
|
+
return installAntigravity(plan, opts);
|
|
406
|
+
}
|
|
401
407
|
if (plan.runtime !== 'claude-code') {
|
|
402
408
|
// kiro/opencode are passive (handled above); any other active runtime is not yet ported.
|
|
403
|
-
return { ok: false, runtime: plan.runtime, mode: plan.mode, files: [], error: `installer not yet implemented for ${plan.runtime} (supported: claude-code, codex, cursor)` };
|
|
409
|
+
return { ok: false, runtime: plan.runtime, mode: plan.mode, files: [], error: `installer not yet implemented for ${plan.runtime} (supported: claude-code, codex, cursor, antigravity)` };
|
|
404
410
|
}
|
|
405
411
|
const hookCommand = opts.hookCommand ?? DEFAULT_HOOK_COMMAND;
|
|
406
412
|
const scope = opts.scope ?? 'project';
|
|
@@ -478,6 +484,9 @@ export function uninstallInjection(runtime, opts) {
|
|
|
478
484
|
if (runtime === 'cursor') {
|
|
479
485
|
return uninstallCursorRules(opts);
|
|
480
486
|
}
|
|
487
|
+
if (runtime === 'antigravity') {
|
|
488
|
+
return uninstallAntigravity(runtime, opts);
|
|
489
|
+
}
|
|
481
490
|
if (runtime !== 'claude-code') {
|
|
482
491
|
return { ok: false, runtime, mode: 'n/a', files: [], error: `uninstall not implemented for ${runtime}` };
|
|
483
492
|
}
|
package/dist/proxyClient.d.ts
CHANGED
|
@@ -41,6 +41,20 @@ export interface ProxyReuseResultArgs {
|
|
|
41
41
|
timeSavedSeconds?: number;
|
|
42
42
|
reason?: string;
|
|
43
43
|
}
|
|
44
|
+
export interface ProxyAgentSearchArgs {
|
|
45
|
+
query?: string;
|
|
46
|
+
signals?: string[];
|
|
47
|
+
availability?: string;
|
|
48
|
+
sort?: string;
|
|
49
|
+
order?: string;
|
|
50
|
+
cursor?: string;
|
|
51
|
+
limit?: number;
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface ProxyAgentDiscoverArgs extends ProxyAgentSearchArgs {
|
|
55
|
+
title: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
}
|
|
44
58
|
export declare class EvolverProxyClient {
|
|
45
59
|
private baseUrl;
|
|
46
60
|
private token;
|
|
@@ -52,6 +66,9 @@ export declare class EvolverProxyClient {
|
|
|
52
66
|
}): Promise<unknown>;
|
|
53
67
|
search(args: ProxySearchArgs): Promise<unknown>;
|
|
54
68
|
fetchAsset(args: ProxyFetchArgs): Promise<unknown>;
|
|
69
|
+
searchAgents(args: ProxyAgentSearchArgs): Promise<unknown>;
|
|
70
|
+
getAgentProfile(agentId: string, timeoutMs?: number): Promise<unknown>;
|
|
71
|
+
discoverAgentsForTask(args: ProxyAgentDiscoverArgs): Promise<unknown>;
|
|
55
72
|
submitAsset(asset: unknown): Promise<unknown>;
|
|
56
73
|
/** Pre-publish dry-run: the hub runs its quality + content-safety gate but stores nothing and charges no credits. */
|
|
57
74
|
validateAsset(asset: unknown): Promise<unknown>;
|
package/dist/proxyClient.js
CHANGED
|
@@ -31,6 +31,19 @@ export class EvolverProxyClient {
|
|
|
31
31
|
...(args.assetIds ? { asset_ids: args.assetIds } : {}),
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
searchAgents(args) {
|
|
35
|
+
return this.call('POST', '/agent/search', agentDirectoryBody(args));
|
|
36
|
+
}
|
|
37
|
+
getAgentProfile(agentId, timeoutMs) {
|
|
38
|
+
return this.call('POST', '/agent/profile', { agent_id: agentId, ...(timeoutMs !== undefined ? { timeout_ms: timeoutMs } : {}) });
|
|
39
|
+
}
|
|
40
|
+
discoverAgentsForTask(args) {
|
|
41
|
+
return this.call('POST', '/agent/discover', {
|
|
42
|
+
title: args.title,
|
|
43
|
+
...(args.description ? { description: args.description } : {}),
|
|
44
|
+
...agentDirectoryBody(args),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
34
47
|
submitAsset(asset) {
|
|
35
48
|
return this.call('POST', '/asset/submit', { assets: [asset] });
|
|
36
49
|
}
|
|
@@ -107,6 +120,18 @@ export class EvolverProxyClient {
|
|
|
107
120
|
return new Error(message);
|
|
108
121
|
}
|
|
109
122
|
}
|
|
123
|
+
function agentDirectoryBody(args) {
|
|
124
|
+
return {
|
|
125
|
+
...(args.query ? { query: args.query } : {}),
|
|
126
|
+
...(args.signals && args.signals.length > 0 ? { signals: args.signals } : {}),
|
|
127
|
+
...(args.availability ? { availability: args.availability } : {}),
|
|
128
|
+
...(args.sort ? { sort: args.sort } : {}),
|
|
129
|
+
...(args.order ? { order: args.order } : {}),
|
|
130
|
+
...(args.cursor ? { cursor: args.cursor } : {}),
|
|
131
|
+
...(args.limit !== undefined ? { limit: args.limit } : {}),
|
|
132
|
+
...(args.timeoutMs !== undefined ? { timeout_ms: args.timeoutMs } : {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
110
135
|
export function proxyClientFromEnv(env = process.env) {
|
|
111
136
|
const token = env['EVOLVER_IPC_TOKEN']?.trim();
|
|
112
137
|
if (!token)
|
package/dist/stdio.js
CHANGED
|
File without changes
|
package/dist/tools.js
CHANGED
|
@@ -375,6 +375,40 @@ export function buildEvolverTools(deps) {
|
|
|
375
375
|
}
|
|
376
376
|
if (deps.proxy) {
|
|
377
377
|
tools.push({
|
|
378
|
+
name: 'evolver_agent_search',
|
|
379
|
+
description: '按自然语言 query 或 capability signals 搜索可协作 agent;结果来自 Hub,不代表实时可用,availability=unknown 时不得推断在线。',
|
|
380
|
+
inputSchema: agentDirectorySearchSchema(),
|
|
381
|
+
handler: async (a) => deps.proxy.searchAgents(agentSearchArgs(a)),
|
|
382
|
+
}, {
|
|
383
|
+
name: 'evolver_agent_profile',
|
|
384
|
+
description: '读取 Hub 授权返回的最小安全 agent profile;不返回凭证、node secret、workspace path 或设备指纹。',
|
|
385
|
+
inputSchema: {
|
|
386
|
+
type: 'object',
|
|
387
|
+
required: ['agentId'],
|
|
388
|
+
properties: {
|
|
389
|
+
agentId: { type: 'string', minLength: 1, maxLength: hub.AGENT_DIRECTORY_MAX_AGENT_ID_LENGTH },
|
|
390
|
+
timeoutMs: { type: 'integer', minimum: 100, maximum: hub.AGENT_DIRECTORY_MAX_TIMEOUT_MS },
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
handler: async (a) => deps.proxy.getAgentProfile(str(a['agentId']), typeof a['timeoutMs'] === 'number' ? a['timeoutMs'] : undefined),
|
|
394
|
+
}, {
|
|
395
|
+
name: 'evolver_agent_discover',
|
|
396
|
+
description: '按任务标题、描述和 capability signals 发现候选 agent;分页和排序由 Hub 执行。',
|
|
397
|
+
inputSchema: {
|
|
398
|
+
...agentDirectorySearchSchema(),
|
|
399
|
+
required: ['title'],
|
|
400
|
+
properties: {
|
|
401
|
+
...agentDirectorySearchSchema()['properties'],
|
|
402
|
+
title: { type: 'string', minLength: 1, maxLength: hub.AGENT_DIRECTORY_MAX_QUERY_LENGTH },
|
|
403
|
+
description: { type: 'string', maxLength: hub.AGENT_DIRECTORY_MAX_QUERY_LENGTH },
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
handler: async (a) => deps.proxy.discoverAgentsForTask({
|
|
407
|
+
title: str(a['title']),
|
|
408
|
+
...(typeof a['description'] === 'string' ? { description: a['description'] } : {}),
|
|
409
|
+
...agentSearchArgs(a),
|
|
410
|
+
}),
|
|
411
|
+
}, {
|
|
378
412
|
name: 'evolver_asset_validate',
|
|
379
413
|
description: '通过本机 evolver-proxy 对 PHub 做发布前 dry-run 校验: 先执行与发布相同的本地脱敏/泄漏拦截, 再跑 hub 端质量门禁 + 内容安全扫描, 不落库、不计费. 返回 {valid, reason?}. 建议在 evolver_asset_publish 前调用. Capsule.gene 须非空或 ad-hoc.',
|
|
380
414
|
inputSchema: {
|
|
@@ -398,4 +432,31 @@ export function buildEvolverTools(deps) {
|
|
|
398
432
|
});
|
|
399
433
|
}
|
|
400
434
|
return tools;
|
|
435
|
+
}
|
|
436
|
+
function agentDirectorySearchSchema() {
|
|
437
|
+
return {
|
|
438
|
+
type: 'object',
|
|
439
|
+
properties: {
|
|
440
|
+
query: { type: 'string', minLength: 1, maxLength: hub.AGENT_DIRECTORY_MAX_QUERY_LENGTH },
|
|
441
|
+
signals: { type: 'array', maxItems: hub.AGENT_DIRECTORY_MAX_SIGNAL_COUNT, items: { type: 'string', minLength: 1, maxLength: hub.AGENT_DIRECTORY_MAX_SIGNAL_LENGTH } },
|
|
442
|
+
availability: { type: 'string', enum: ['online', 'busy', 'offline', 'unknown'] },
|
|
443
|
+
sort: { type: 'string', enum: ['relevance', 'reputation', 'recent', 'availability'] },
|
|
444
|
+
order: { type: 'string', enum: ['asc', 'desc'] },
|
|
445
|
+
cursor: { type: 'string', maxLength: hub.AGENT_DIRECTORY_MAX_CURSOR_LENGTH },
|
|
446
|
+
limit: { type: 'integer', minimum: 1, maximum: hub.AGENT_DIRECTORY_MAX_LIMIT },
|
|
447
|
+
timeoutMs: { type: 'integer', minimum: 100, maximum: hub.AGENT_DIRECTORY_MAX_TIMEOUT_MS },
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function agentSearchArgs(args) {
|
|
452
|
+
return {
|
|
453
|
+
...(typeof args['query'] === 'string' ? { query: args['query'] } : {}),
|
|
454
|
+
...(Array.isArray(args['signals']) ? { signals: strArray(args['signals']) ?? [] } : {}),
|
|
455
|
+
...(typeof args['availability'] === 'string' ? { availability: args['availability'] } : {}),
|
|
456
|
+
...(typeof args['sort'] === 'string' ? { sort: args['sort'] } : {}),
|
|
457
|
+
...(typeof args['order'] === 'string' ? { order: args['order'] } : {}),
|
|
458
|
+
...(typeof args['cursor'] === 'string' ? { cursor: args['cursor'] } : {}),
|
|
459
|
+
...(typeof args['limit'] === 'number' ? { limit: args['limit'] } : {}),
|
|
460
|
+
...(typeof args['timeoutMs'] === 'number' ? { timeoutMs: args['timeoutMs'] } : {}),
|
|
461
|
+
};
|
|
401
462
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver-mcp",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Evolver MCP server (agent 工具发现入口)",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@evomap/evolver-core": "2.0.0-beta.
|
|
23
|
+
"@evomap/evolver-core": "2.0.0-beta.2",
|
|
24
24
|
"smol-toml": "^1.6.1"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"dist/",
|
|
32
|
+
"assets/",
|
|
32
33
|
"README.md",
|
|
33
34
|
"package.json"
|
|
34
35
|
]
|