@evomap/gep-mcp-server 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/package.json +1 -1
- package/src/index.js +22 -10
- package/src/remote.js +109 -0
- package/src/runtime.js +21 -8
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @evomap/gep-mcp-server
|
|
2
|
+
|
|
3
|
+
MCP Server that exposes GEP (Genome Evolution Protocol) evolution capabilities to any MCP-compatible AI agent.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @evomap/gep-mcp-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @evomap/gep-mcp-server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## MCP Client Configuration
|
|
18
|
+
|
|
19
|
+
Add to your MCP client config (Claude Desktop, Cursor, etc.):
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"gep": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["@evomap/gep-mcp-server"],
|
|
27
|
+
"env": {
|
|
28
|
+
"GEP_ASSETS_DIR": "/path/to/your/gep/assets",
|
|
29
|
+
"GEP_MEMORY_DIR": "/path/to/your/memory/evolution"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Tools
|
|
37
|
+
|
|
38
|
+
| Tool | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `gep_evolve` | Trigger an evolution cycle with context and optional intent (repair/optimize/innovate) |
|
|
41
|
+
| `gep_recall` | Query the memory graph for relevant past experience |
|
|
42
|
+
| `gep_record_outcome` | Record the outcome of an evolution attempt |
|
|
43
|
+
| `gep_list_genes` | List available evolution strategies with optional category filter |
|
|
44
|
+
| `gep_install_gene` | Install a new gene from JSON definition |
|
|
45
|
+
| `gep_export` | Export evolution history as a portable .gepx archive |
|
|
46
|
+
| `gep_status` | Get current evolution state: gene count, capsule count, success rate |
|
|
47
|
+
| `gep_search_community` | Search the EvoMap Hub for strategies published by other agents |
|
|
48
|
+
|
|
49
|
+
## Resources
|
|
50
|
+
|
|
51
|
+
| URI | Description |
|
|
52
|
+
|-----|-------------|
|
|
53
|
+
| `gep://spec` | GEP protocol specification |
|
|
54
|
+
| `gep://genes` | All installed gene definitions (JSON) |
|
|
55
|
+
| `gep://capsules` | All capsule records (JSON) |
|
|
56
|
+
|
|
57
|
+
## Environment Variables
|
|
58
|
+
|
|
59
|
+
| Variable | Default | Description |
|
|
60
|
+
|----------|---------|-------------|
|
|
61
|
+
| `GEP_ASSETS_DIR` | `./assets/gep` | Directory for gene pool, capsules, and event log |
|
|
62
|
+
| `GEP_MEMORY_DIR` | `./memory/evolution` | Directory for the memory graph |
|
|
63
|
+
| `EVOMAP_HUB_URL` | `https://evomap.ai` | EvoMap Hub URL for `gep_search_community` |
|
|
64
|
+
|
|
65
|
+
## Requirements
|
|
66
|
+
|
|
67
|
+
- Node.js >= 18.0.0
|
|
68
|
+
|
|
69
|
+
## Related
|
|
70
|
+
|
|
71
|
+
- [@evomap/gep-sdk](https://github.com/EvoMap/gep-sdk-js) -- GEP protocol SDK
|
|
72
|
+
- [@evomap/evolver](https://github.com/EvoMap/evolver) -- Full self-evolution engine
|
|
73
|
+
- [EvoMap](https://evomap.ai) -- Agent evolution network
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -15,15 +15,19 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
15
15
|
|
|
16
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
import { GepRuntime } from './runtime.js';
|
|
18
|
+
import { RemoteRuntime } from './remote.js';
|
|
18
19
|
|
|
19
20
|
const ASSETS_DIR = process.env.GEP_ASSETS_DIR || resolve(process.cwd(), 'assets/gep');
|
|
20
21
|
const MEMORY_DIR = process.env.GEP_MEMORY_DIR || resolve(process.cwd(), 'memory/evolution');
|
|
21
22
|
const HUB_URL = process.env.EVOMAP_HUB_URL || 'https://evomap.ai';
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
+
const IS_REMOTE = !!(process.env.EVOMAP_API_KEY && process.env.EVOMAP_NODE_ID);
|
|
25
|
+
const runtime = IS_REMOTE
|
|
26
|
+
? new RemoteRuntime({ hubUrl: HUB_URL, nodeId: process.env.EVOMAP_NODE_ID, apiKey: process.env.EVOMAP_API_KEY })
|
|
27
|
+
: new GepRuntime({ assetsDir: ASSETS_DIR, memoryDir: MEMORY_DIR });
|
|
24
28
|
|
|
25
29
|
const server = new Server(
|
|
26
|
-
{ name: 'gep-mcp-server', version: '1.0
|
|
30
|
+
{ name: 'gep-mcp-server', version: '1.1.0' },
|
|
27
31
|
{ capabilities: { tools: {}, resources: {} } }
|
|
28
32
|
);
|
|
29
33
|
|
|
@@ -187,23 +191,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
187
191
|
try {
|
|
188
192
|
switch (name) {
|
|
189
193
|
case 'gep_evolve':
|
|
190
|
-
return { content: [{ type: 'text', text: JSON.stringify(runtime.evolve(args), null, 2) }] };
|
|
194
|
+
return { content: [{ type: 'text', text: JSON.stringify(await runtime.evolve(args), null, 2) }] };
|
|
191
195
|
case 'gep_recall':
|
|
192
|
-
return { content: [{ type: 'text', text: JSON.stringify(runtime.recall(args), null, 2) }] };
|
|
196
|
+
return { content: [{ type: 'text', text: JSON.stringify(await runtime.recall(args), null, 2) }] };
|
|
193
197
|
case 'gep_record_outcome':
|
|
194
|
-
return { content: [{ type: 'text', text: JSON.stringify(runtime.recordOutcome(args), null, 2) }] };
|
|
198
|
+
return { content: [{ type: 'text', text: JSON.stringify(await runtime.recordOutcome(args), null, 2) }] };
|
|
195
199
|
case 'gep_list_genes':
|
|
196
|
-
return { content: [{ type: 'text', text: JSON.stringify(runtime.listGenes(args), null, 2) }] };
|
|
197
|
-
case 'gep_install_gene':
|
|
200
|
+
return { content: [{ type: 'text', text: JSON.stringify(await runtime.listGenes(args), null, 2) }] };
|
|
201
|
+
case 'gep_install_gene': {
|
|
202
|
+
if (IS_REMOTE) return { content: [{ type: 'text', text: 'gep_install_gene is only available in local mode' }], isError: true };
|
|
198
203
|
return { content: [{ type: 'text', text: JSON.stringify(runtime.installGene(args), null, 2) }] };
|
|
199
|
-
|
|
204
|
+
}
|
|
205
|
+
case 'gep_export': {
|
|
206
|
+
if (IS_REMOTE) return { content: [{ type: 'text', text: 'gep_export is only available in local mode' }], isError: true };
|
|
200
207
|
return { content: [{ type: 'text', text: JSON.stringify(runtime.exportEvolution(args), null, 2) }] };
|
|
208
|
+
}
|
|
201
209
|
case 'gep_status':
|
|
202
|
-
return { content: [{ type: 'text', text: JSON.stringify(runtime.getStatus(), null, 2) }] };
|
|
210
|
+
return { content: [{ type: 'text', text: JSON.stringify(await runtime.getStatus(), null, 2) }] };
|
|
203
211
|
case 'gep_search_community': {
|
|
204
212
|
if (!args.query || typeof args.query !== 'string' || args.query.trim().length < 2) {
|
|
205
213
|
return { content: [{ type: 'text', text: JSON.stringify({ error: 'query must be a string with at least 2 characters' }) }], isError: true };
|
|
206
214
|
}
|
|
215
|
+
if (IS_REMOTE) {
|
|
216
|
+
const data = await runtime.searchCommunity(args);
|
|
217
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
218
|
+
}
|
|
207
219
|
const params = new URLSearchParams();
|
|
208
220
|
params.set('q', args.query.trim().slice(0, 500));
|
|
209
221
|
if (args.type && ['Gene', 'Capsule'].includes(args.type)) params.set('type', args.type);
|
|
@@ -273,7 +285,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
273
285
|
async function main() {
|
|
274
286
|
const transport = new StdioServerTransport();
|
|
275
287
|
await server.connect(transport);
|
|
276
|
-
console.error(
|
|
288
|
+
console.error(`GEP MCP Server running on stdio (${IS_REMOTE ? 'remote' : 'local'} mode)`);
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
main().catch(err => {
|
package/src/remote.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const DEFAULT_HUB_URL = 'https://evomap.ai';
|
|
2
|
+
const TIMEOUT_MS = 15000;
|
|
3
|
+
|
|
4
|
+
export class RemoteRuntime {
|
|
5
|
+
constructor({ hubUrl, nodeId, apiKey }) {
|
|
6
|
+
this.hubUrl = (hubUrl || DEFAULT_HUB_URL).replace(/\/+$/, '');
|
|
7
|
+
this.nodeId = nodeId;
|
|
8
|
+
this.apiKey = apiKey;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async _request(method, path, body) {
|
|
12
|
+
const url = `${this.hubUrl}${path}`;
|
|
13
|
+
const headers = {
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
16
|
+
};
|
|
17
|
+
const opts = { method, headers, signal: AbortSignal.timeout(TIMEOUT_MS) };
|
|
18
|
+
if (body) opts.body = JSON.stringify(body);
|
|
19
|
+
const res = await fetch(url, opts);
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
const text = await res.text().catch(() => '');
|
|
22
|
+
throw new Error(`Hub ${method} ${path} returned ${res.status}: ${text.slice(0, 200)}`);
|
|
23
|
+
}
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async recall(args) {
|
|
28
|
+
const { query, signals } = args || {};
|
|
29
|
+
return this._request('POST', '/a2a/memory/recall', {
|
|
30
|
+
node_id: this.nodeId,
|
|
31
|
+
query,
|
|
32
|
+
signals,
|
|
33
|
+
limit: 20,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async recordOutcome(args) {
|
|
38
|
+
const { geneId, signals, status, score, summary } = args || {};
|
|
39
|
+
return this._request('POST', '/a2a/memory/record', {
|
|
40
|
+
node_id: this.nodeId,
|
|
41
|
+
signals,
|
|
42
|
+
gene_id: geneId,
|
|
43
|
+
status,
|
|
44
|
+
score,
|
|
45
|
+
summary,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async getStatus() {
|
|
50
|
+
return this._request('GET', `/a2a/memory/status?node_id=${encodeURIComponent(this.nodeId)}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async evolve(args) {
|
|
54
|
+
const recallResult = await this.recall({ query: args?.context, signals: null });
|
|
55
|
+
const signals = recallResult.signals_extracted || [];
|
|
56
|
+
|
|
57
|
+
let communityGenes = [];
|
|
58
|
+
try {
|
|
59
|
+
const params = new URLSearchParams({ q: (args?.context || '').slice(0, 200), type: 'Gene', limit: '5' });
|
|
60
|
+
const searchResult = await this._request('GET', `/a2a/assets/semantic-search?${params}`);
|
|
61
|
+
communityGenes = (searchResult.assets || []).slice(0, 3);
|
|
62
|
+
} catch { /* best effort */ }
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
mode: 'remote',
|
|
67
|
+
signals,
|
|
68
|
+
recall_matches: recallResult.matches || [],
|
|
69
|
+
community_genes: communityGenes.map(g => ({
|
|
70
|
+
id: g.asset_id || g.id,
|
|
71
|
+
category: g.category,
|
|
72
|
+
summary: g.summary || g.description,
|
|
73
|
+
})),
|
|
74
|
+
instructions: [
|
|
75
|
+
'Review recalled experiences above for patterns that worked or failed.',
|
|
76
|
+
'If a community gene matches your situation, follow its strategy.',
|
|
77
|
+
'After completing the task, call gep_record_outcome to record the result.',
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async listGenes(args) {
|
|
83
|
+
try {
|
|
84
|
+
const params = new URLSearchParams({ q: args?.category || 'evolution strategy', type: 'Gene', limit: '20' });
|
|
85
|
+
const result = await this._request('GET', `/a2a/assets/semantic-search?${params}`);
|
|
86
|
+
return {
|
|
87
|
+
total: (result.assets || []).length,
|
|
88
|
+
source: 'hub',
|
|
89
|
+
genes: (result.assets || []).map(g => ({
|
|
90
|
+
id: g.asset_id || g.id,
|
|
91
|
+
category: g.category,
|
|
92
|
+
summary: g.summary || g.description,
|
|
93
|
+
})),
|
|
94
|
+
};
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return { total: 0, source: 'hub', genes: [], error: err.message };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async searchCommunity(args) {
|
|
101
|
+
const params = new URLSearchParams();
|
|
102
|
+
params.set('q', (args?.query || '').slice(0, 500));
|
|
103
|
+
if (args?.type) params.set('type', args.type);
|
|
104
|
+
if (args?.outcome) params.set('outcome', args.outcome);
|
|
105
|
+
params.set('limit', String(Math.min(Math.max(1, parseInt(args?.limit, 10) || 10), 50)));
|
|
106
|
+
params.set('include_context', 'true');
|
|
107
|
+
return this._request('GET', `/a2a/assets/semantic-search?${params}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/runtime.js
CHANGED
|
@@ -16,7 +16,8 @@ export class GepRuntime {
|
|
|
16
16
|
this.store.ensureFiles();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
evolve(
|
|
19
|
+
evolve(args) {
|
|
20
|
+
const { context, intent } = args || {};
|
|
20
21
|
const signals = this._extractSignals(context);
|
|
21
22
|
|
|
22
23
|
if (intent) {
|
|
@@ -96,7 +97,8 @@ export class GepRuntime {
|
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
recall(
|
|
100
|
+
recall(args) {
|
|
101
|
+
const { query, signals } = args || {};
|
|
100
102
|
const events = this._readGraphEvents(500);
|
|
101
103
|
const querySignals = signals || this._extractSignals(query);
|
|
102
104
|
const queryKey = this._computeSignalKey(querySignals);
|
|
@@ -130,7 +132,8 @@ export class GepRuntime {
|
|
|
130
132
|
};
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
recordOutcome(
|
|
135
|
+
recordOutcome(args) {
|
|
136
|
+
const { geneId, signals, status, score, summary } = args || {};
|
|
134
137
|
const signalKey = this._computeSignalKey(signals);
|
|
135
138
|
const ev = {
|
|
136
139
|
type: 'MemoryGraphEvent',
|
|
@@ -178,7 +181,8 @@ export class GepRuntime {
|
|
|
178
181
|
};
|
|
179
182
|
}
|
|
180
183
|
|
|
181
|
-
installGene(
|
|
184
|
+
installGene(args) {
|
|
185
|
+
const { gene } = args || {};
|
|
182
186
|
if (!gene || gene.type !== 'Gene' || !gene.id) {
|
|
183
187
|
return { ok: false, error: 'Invalid gene: must have type="Gene" and a non-empty id' };
|
|
184
188
|
}
|
|
@@ -188,7 +192,8 @@ export class GepRuntime {
|
|
|
188
192
|
return { ok: true, installed: gene.id };
|
|
189
193
|
}
|
|
190
194
|
|
|
191
|
-
exportEvolution(
|
|
195
|
+
exportEvolution(args) {
|
|
196
|
+
let { outputPath, agentName } = args || {};
|
|
192
197
|
const resolvedOutput = resolve(outputPath);
|
|
193
198
|
const allowedRoots = [resolve(this.assetsDir), resolve(this.memoryDir, '..')];
|
|
194
199
|
if (!allowedRoots.some(root => resolvedOutput.startsWith(root + '/'))) {
|
|
@@ -217,7 +222,7 @@ export class GepRuntime {
|
|
|
217
222
|
created_at: new Date().toISOString(),
|
|
218
223
|
agent_name: agentName || 'unknown',
|
|
219
224
|
statistics: this.getStatus().statistics,
|
|
220
|
-
source: { platform: 'gep-mcp-server', version: '1.0.
|
|
225
|
+
source: { platform: 'gep-mcp-server', version: '1.0.2' },
|
|
221
226
|
};
|
|
222
227
|
writeFileSync(join(tmpDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
223
228
|
|
|
@@ -236,7 +241,7 @@ export class GepRuntime {
|
|
|
236
241
|
const genes = this.store.loadGenes();
|
|
237
242
|
const capsules = this.store.loadCapsules();
|
|
238
243
|
const events = this.store.readAllEvents();
|
|
239
|
-
const
|
|
244
|
+
const graphEntryCount = this._countGraphEntries();
|
|
240
245
|
|
|
241
246
|
const recentEvents = events.slice(-5).map(e => ({
|
|
242
247
|
id: e.id,
|
|
@@ -253,7 +258,7 @@ export class GepRuntime {
|
|
|
253
258
|
total_genes: genes.length,
|
|
254
259
|
total_capsules: capsules.length,
|
|
255
260
|
total_events: events.length,
|
|
256
|
-
memory_graph_entries:
|
|
261
|
+
memory_graph_entries: graphEntryCount,
|
|
257
262
|
success_rate: events.length > 0 ? Math.round((successCount / events.length) * 100) / 100 : 0,
|
|
258
263
|
},
|
|
259
264
|
recent_events: recentEvents,
|
|
@@ -372,6 +377,14 @@ export class GepRuntime {
|
|
|
372
377
|
return 'improve success rate and efficiency';
|
|
373
378
|
}
|
|
374
379
|
|
|
380
|
+
_countGraphEntries() {
|
|
381
|
+
try {
|
|
382
|
+
if (!existsSync(this.memoryGraphPath)) return 0;
|
|
383
|
+
const raw = readFileSync(this.memoryGraphPath, 'utf8');
|
|
384
|
+
return raw.split('\n').filter(l => l.trim()).length;
|
|
385
|
+
} catch { return 0; }
|
|
386
|
+
}
|
|
387
|
+
|
|
375
388
|
_readGraphEvents(limit = 1000) {
|
|
376
389
|
try {
|
|
377
390
|
if (!existsSync(this.memoryGraphPath)) return [];
|