@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/gep-mcp-server",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "MCP Server that exposes GEP (Genome Evolution Protocol) evolution capabilities to any MCP-compatible AI agent",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
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 runtime = new GepRuntime({ assetsDir: ASSETS_DIR, memoryDir: MEMORY_DIR });
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.1' },
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
- case 'gep_export':
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('GEP MCP Server running on stdio');
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({ context, intent }) {
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({ query, signals }) {
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({ geneId, signals, status, score, summary }) {
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({ gene }) {
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({ outputPath, agentName }) {
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.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 graphEvents = this._readGraphEvents(100);
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: graphEvents.length,
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 [];