agentkit-mesh 1.0.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 +153 -0
- package/dist/delegation.d.ts +11 -0
- package/dist/delegation.js +61 -0
- package/dist/discovery.d.ts +13 -0
- package/dist/discovery.js +36 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +23 -0
- package/dist/lore-discovery.d.ts +12 -0
- package/dist/lore-discovery.js +71 -0
- package/dist/registry.d.ts +28 -0
- package/dist/registry.js +80 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +58 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# agentkit-mesh
|
|
2
|
+
|
|
3
|
+
Agent-to-agent discovery and delegation via [MCP](https://modelcontextprotocol.io) (Model Context Protocol).
|
|
4
|
+
|
|
5
|
+
Agents register their capabilities, discover each other by semantic search, and delegate tasks — all through standard MCP tools.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx agentkit-mesh
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This starts an MCP server over stdio, ready to connect to Claude Desktop, OpenClaw, or any MCP client.
|
|
14
|
+
|
|
15
|
+
## MCP Configuration
|
|
16
|
+
|
|
17
|
+
### Claude Desktop
|
|
18
|
+
|
|
19
|
+
Add to `claude_desktop_config.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"agentkit-mesh": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["agentkit-mesh"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### OpenClaw
|
|
33
|
+
|
|
34
|
+
Add to your OpenClaw config:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
mcp:
|
|
38
|
+
agentkit-mesh:
|
|
39
|
+
command: npx agentkit-mesh
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Architecture
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
┌─────────────┐ MCP ┌──────────────────┐
|
|
46
|
+
│ AI Agent A │◄────────────►│ │
|
|
47
|
+
└─────────────┘ │ agentkit-mesh │
|
|
48
|
+
│ │
|
|
49
|
+
┌─────────────┐ MCP │ ┌────────────┐ │
|
|
50
|
+
│ AI Agent B │◄────────────►│ │ Registry │ │
|
|
51
|
+
└─────────────┘ │ │ (SQLite) │ │
|
|
52
|
+
│ └────────────┘ │
|
|
53
|
+
┌─────────────┐ MCP │ ┌────────────┐ │
|
|
54
|
+
│ AI Agent C │◄────────────►│ │ Discovery │ │
|
|
55
|
+
└─────────────┘ │ └────────────┘ │
|
|
56
|
+
│ ┌────────────┐ │
|
|
57
|
+
│ │ Delegation │ │
|
|
58
|
+
│ └────────────┘ │
|
|
59
|
+
└──────────────────┘
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## MCP Tools
|
|
63
|
+
|
|
64
|
+
### `mesh_register`
|
|
65
|
+
|
|
66
|
+
Register an agent with its capabilities.
|
|
67
|
+
|
|
68
|
+
| Parameter | Type | Description |
|
|
69
|
+
|-----------|------|-------------|
|
|
70
|
+
| `name` | string | Unique agent name |
|
|
71
|
+
| `description` | string | What this agent does |
|
|
72
|
+
| `capabilities` | string[] | List of capabilities |
|
|
73
|
+
| `endpoint` | string | Agent's MCP endpoint URL |
|
|
74
|
+
|
|
75
|
+
### `mesh_discover`
|
|
76
|
+
|
|
77
|
+
Discover agents matching a natural language query.
|
|
78
|
+
|
|
79
|
+
| Parameter | Type | Description |
|
|
80
|
+
|-----------|------|-------------|
|
|
81
|
+
| `query` | string | Search query (e.g. "budget management") |
|
|
82
|
+
| `limit` | number? | Max results to return |
|
|
83
|
+
|
|
84
|
+
Returns agents ranked by relevance score with matched capability terms.
|
|
85
|
+
|
|
86
|
+
### `mesh_unregister`
|
|
87
|
+
|
|
88
|
+
Remove an agent from the registry.
|
|
89
|
+
|
|
90
|
+
| Parameter | Type | Description |
|
|
91
|
+
|-----------|------|-------------|
|
|
92
|
+
| `name` | string | Agent name to remove |
|
|
93
|
+
|
|
94
|
+
### `mesh_delegate`
|
|
95
|
+
|
|
96
|
+
Delegate a task to another agent by name.
|
|
97
|
+
|
|
98
|
+
| Parameter | Type | Description |
|
|
99
|
+
|-----------|------|-------------|
|
|
100
|
+
| `targetName` | string | Name of the target agent |
|
|
101
|
+
| `task` | string | Task description to delegate |
|
|
102
|
+
| `context` | string? | Optional JSON context |
|
|
103
|
+
|
|
104
|
+
Connects to the target agent's MCP endpoint and calls its `handle_task` tool.
|
|
105
|
+
|
|
106
|
+
## Use Case: FormBridge
|
|
107
|
+
|
|
108
|
+
An HR agent filling an expense form discovers the Finance agent:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { AgentRegistry, DiscoveryEngine } from 'agentkit-mesh';
|
|
112
|
+
|
|
113
|
+
const registry = new AgentRegistry();
|
|
114
|
+
|
|
115
|
+
// Agents register themselves
|
|
116
|
+
registry.register({
|
|
117
|
+
name: 'finance-agent',
|
|
118
|
+
description: 'Budget management and expense approval',
|
|
119
|
+
capabilities: ['budget', 'cost_center', 'expense_approval'],
|
|
120
|
+
endpoint: 'http://localhost:4002/mcp',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// HR agent discovers who can help with budget fields
|
|
124
|
+
const discovery = new DiscoveryEngine();
|
|
125
|
+
const results = discovery.discover('budget cost center', registry);
|
|
126
|
+
// → [{ agent: finance-agent, score: 0.67, matchedTerms: ['budget', 'cost', 'center'] }]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
See [examples/](examples/) for a runnable demo.
|
|
130
|
+
|
|
131
|
+
## Optional: Lore Integration
|
|
132
|
+
|
|
133
|
+
For semantic search beyond keyword matching, connect to a [Lore](https://github.com/openclaw/lore) server:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { LoreDiscoveryEngine } from 'agentkit-mesh';
|
|
137
|
+
|
|
138
|
+
const engine = new LoreDiscoveryEngine('http://lore:8080', registry, 'api-key');
|
|
139
|
+
const results = await engine.discover('financial planning');
|
|
140
|
+
// Falls back to text matching if Lore is unavailable
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Programmatic API
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { AgentRegistry, DiscoveryEngine, DelegationClient, createServer } from 'agentkit-mesh';
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
All classes are exported for direct use without the MCP server layer.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
ISC
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
2
|
+
export interface DelegationResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
result?: string;
|
|
5
|
+
error?: string;
|
|
6
|
+
latencyMs: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class DelegationClient {
|
|
9
|
+
delegateViaTransport(transport: Transport, task: string, context?: Record<string, any>, timeout?: number): Promise<DelegationResult>;
|
|
10
|
+
delegate(targetEndpoint: string, task: string, context?: Record<string, any>, timeout?: number): Promise<DelegationResult>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
const MAX_DEPTH = 3;
|
|
3
|
+
const DEFAULT_TIMEOUT = 60_000;
|
|
4
|
+
function withTimeout(promise, ms) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const timer = setTimeout(() => reject(new Error(`Delegation timed out after ${ms}ms`)), ms);
|
|
7
|
+
promise.then(resolve, reject).finally(() => clearTimeout(timer));
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export class DelegationClient {
|
|
11
|
+
async delegateViaTransport(transport, task, context, timeout) {
|
|
12
|
+
const depth = context?.depth ?? 0;
|
|
13
|
+
if (depth > MAX_DEPTH) {
|
|
14
|
+
return { success: false, error: `Delegation depth ${depth} exceeds max ${MAX_DEPTH}`, latencyMs: 0 };
|
|
15
|
+
}
|
|
16
|
+
const start = Date.now();
|
|
17
|
+
const client = new Client({ name: 'delegation-client', version: '0.1.0' });
|
|
18
|
+
try {
|
|
19
|
+
await client.connect(transport);
|
|
20
|
+
const nextContext = { ...context, depth: depth + 1 };
|
|
21
|
+
const response = await withTimeout(client.callTool({ name: 'handle_task', arguments: { task, context: nextContext } }), timeout ?? DEFAULT_TIMEOUT);
|
|
22
|
+
const text = response.content?.[0]?.text ?? '';
|
|
23
|
+
return { success: true, result: text, latencyMs: Date.now() - start };
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
return { success: false, error: err.message ?? String(err), latencyMs: Date.now() - start };
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
try {
|
|
30
|
+
await client.close();
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async delegate(targetEndpoint, task, context, timeout) {
|
|
36
|
+
const depth = context?.depth ?? 0;
|
|
37
|
+
if (depth > MAX_DEPTH) {
|
|
38
|
+
return { success: false, error: `Delegation depth ${depth} exceeds max ${MAX_DEPTH}`, latencyMs: 0 };
|
|
39
|
+
}
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
const client = new Client({ name: 'delegation-client', version: '0.1.0' });
|
|
42
|
+
try {
|
|
43
|
+
const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js');
|
|
44
|
+
const transport = new SSEClientTransport(new URL(targetEndpoint));
|
|
45
|
+
await client.connect(transport);
|
|
46
|
+
const nextContext = { ...context, depth: depth + 1 };
|
|
47
|
+
const response = await withTimeout(client.callTool({ name: 'handle_task', arguments: { task, context: nextContext } }), timeout ?? DEFAULT_TIMEOUT);
|
|
48
|
+
const text = response.content?.[0]?.text ?? '';
|
|
49
|
+
return { success: true, result: text, latencyMs: Date.now() - start };
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
return { success: false, error: err.message ?? String(err), latencyMs: Date.now() - start };
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
try {
|
|
56
|
+
await client.close();
|
|
57
|
+
}
|
|
58
|
+
catch { }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AgentRecord, AgentRegistry } from './registry.js';
|
|
2
|
+
export interface DiscoveryResult {
|
|
3
|
+
agent: AgentRecord;
|
|
4
|
+
score: number;
|
|
5
|
+
matchedTerms: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface DiscoveryProvider {
|
|
8
|
+
discover(query: string, limit?: number): Promise<DiscoveryResult[]>;
|
|
9
|
+
}
|
|
10
|
+
export declare class DiscoveryEngine {
|
|
11
|
+
discover(query: string, registry: AgentRegistry, limit?: number): DiscoveryResult[];
|
|
12
|
+
private tokenize;
|
|
13
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export class DiscoveryEngine {
|
|
2
|
+
discover(query, registry, limit) {
|
|
3
|
+
const tokens = this.tokenize(query);
|
|
4
|
+
if (tokens.length === 0)
|
|
5
|
+
return [];
|
|
6
|
+
const agents = registry.list();
|
|
7
|
+
const results = [];
|
|
8
|
+
for (const agent of agents) {
|
|
9
|
+
const searchText = [
|
|
10
|
+
agent.description.toLowerCase(),
|
|
11
|
+
...agent.capabilities.map(c => c.toLowerCase()),
|
|
12
|
+
].join(' ');
|
|
13
|
+
const matchedTerms = [];
|
|
14
|
+
for (const token of tokens) {
|
|
15
|
+
if (searchText.includes(token)) {
|
|
16
|
+
matchedTerms.push(token);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (matchedTerms.length > 0) {
|
|
20
|
+
results.push({
|
|
21
|
+
agent,
|
|
22
|
+
score: matchedTerms.length / tokens.length,
|
|
23
|
+
matchedTerms,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
results.sort((a, b) => b.score - a.score);
|
|
28
|
+
return limit ? results.slice(0, limit) : results;
|
|
29
|
+
}
|
|
30
|
+
tokenize(query) {
|
|
31
|
+
return query
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.split(/[\s\W]+/)
|
|
34
|
+
.filter(t => t.length > 0);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { AgentRegistry } from './registry.js';
|
|
3
|
+
export type { AgentRecord, RegisterInput } from './registry.js';
|
|
4
|
+
export { DiscoveryEngine } from './discovery.js';
|
|
5
|
+
export type { DiscoveryResult, DiscoveryProvider } from './discovery.js';
|
|
6
|
+
export { DelegationClient } from './delegation.js';
|
|
7
|
+
export type { DelegationResult } from './delegation.js';
|
|
8
|
+
export { createServer } from './server.js';
|
|
9
|
+
export { LoreDiscoveryEngine } from './lore-discovery.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { AgentRegistry } from './registry.js';
|
|
3
|
+
export { DiscoveryEngine } from './discovery.js';
|
|
4
|
+
export { DelegationClient } from './delegation.js';
|
|
5
|
+
export { createServer } from './server.js';
|
|
6
|
+
export { LoreDiscoveryEngine } from './lore-discovery.js';
|
|
7
|
+
// CLI entry point: only start server when run directly via the bin script
|
|
8
|
+
const scriptPath = process.argv[1] ?? '';
|
|
9
|
+
const isDirectRun = scriptPath.endsWith('/agentkit-mesh') ||
|
|
10
|
+
scriptPath.endsWith('\\agentkit-mesh') ||
|
|
11
|
+
(scriptPath.endsWith('/index.js') && scriptPath.includes('agentkit-mesh'));
|
|
12
|
+
if (isDirectRun) {
|
|
13
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
14
|
+
const { AgentRegistry: Registry } = await import('./registry.js');
|
|
15
|
+
const { createServer: create } = await import('./server.js');
|
|
16
|
+
const dbPath = process.argv[2] || undefined;
|
|
17
|
+
const registry = new Registry(dbPath);
|
|
18
|
+
const server = create(registry);
|
|
19
|
+
const transport = new StdioServerTransport();
|
|
20
|
+
await server.connect(transport);
|
|
21
|
+
process.on('SIGINT', () => { registry.close(); process.exit(0); });
|
|
22
|
+
process.on('SIGTERM', () => { registry.close(); process.exit(0); });
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AgentRecord, AgentRegistry } from './registry.js';
|
|
2
|
+
import { DiscoveryResult, DiscoveryProvider } from './discovery.js';
|
|
3
|
+
export declare class LoreDiscoveryEngine implements DiscoveryProvider {
|
|
4
|
+
private loreUrl;
|
|
5
|
+
private loreApiKey?;
|
|
6
|
+
private registry;
|
|
7
|
+
private fallback;
|
|
8
|
+
constructor(loreUrl: string, registry: AgentRegistry, loreApiKey?: string);
|
|
9
|
+
discover(query: string, limit?: number): Promise<DiscoveryResult[]>;
|
|
10
|
+
private discoverViaLore;
|
|
11
|
+
publishCapabilities(agent: AgentRecord): Promise<boolean>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { DiscoveryEngine } from './discovery.js';
|
|
2
|
+
export class LoreDiscoveryEngine {
|
|
3
|
+
loreUrl;
|
|
4
|
+
loreApiKey;
|
|
5
|
+
registry;
|
|
6
|
+
fallback;
|
|
7
|
+
constructor(loreUrl, registry, loreApiKey) {
|
|
8
|
+
this.loreUrl = loreUrl.replace(/\/+$/, '');
|
|
9
|
+
this.loreApiKey = loreApiKey;
|
|
10
|
+
this.registry = registry;
|
|
11
|
+
this.fallback = new DiscoveryEngine();
|
|
12
|
+
}
|
|
13
|
+
async discover(query, limit) {
|
|
14
|
+
try {
|
|
15
|
+
return await this.discoverViaLore(query, limit);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Fall back to text matching
|
|
19
|
+
return this.fallback.discover(query, this.registry, limit);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async discoverViaLore(query, limit) {
|
|
23
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
24
|
+
if (this.loreApiKey) {
|
|
25
|
+
headers['Authorization'] = `Bearer ${this.loreApiKey}`;
|
|
26
|
+
}
|
|
27
|
+
const response = await fetch(`${this.loreUrl}/v1/lessons/search`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers,
|
|
30
|
+
body: JSON.stringify({ query, limit: limit ?? 10 }),
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(`Lore API returned ${response.status}`);
|
|
34
|
+
}
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
return data.lessons.map((lesson) => ({
|
|
37
|
+
agent: {
|
|
38
|
+
name: lesson.title,
|
|
39
|
+
description: lesson.content,
|
|
40
|
+
capabilities: lesson.tags ?? [],
|
|
41
|
+
endpoint: '',
|
|
42
|
+
protocol: 'mcp',
|
|
43
|
+
registered_at: '',
|
|
44
|
+
last_seen: '',
|
|
45
|
+
},
|
|
46
|
+
score: lesson.score ?? 0.5,
|
|
47
|
+
matchedTerms: lesson.tags ?? [],
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
async publishCapabilities(agent) {
|
|
51
|
+
try {
|
|
52
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
53
|
+
if (this.loreApiKey) {
|
|
54
|
+
headers['Authorization'] = `Bearer ${this.loreApiKey}`;
|
|
55
|
+
}
|
|
56
|
+
const response = await fetch(`${this.loreUrl}/v1/lessons`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers,
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
title: agent.name,
|
|
61
|
+
content: `${agent.description}. Capabilities: ${agent.capabilities.join(', ')}`,
|
|
62
|
+
tags: agent.capabilities,
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
return response.ok;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface AgentRecord {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
capabilities: string[];
|
|
5
|
+
endpoint: string;
|
|
6
|
+
protocol: string;
|
|
7
|
+
registered_at: string;
|
|
8
|
+
last_seen: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RegisterInput {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
capabilities: string[];
|
|
14
|
+
endpoint: string;
|
|
15
|
+
protocol?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class AgentRegistry {
|
|
18
|
+
private db;
|
|
19
|
+
constructor(dbPath?: string);
|
|
20
|
+
private initSchema;
|
|
21
|
+
register(agent: RegisterInput): AgentRecord;
|
|
22
|
+
unregister(name: string): boolean;
|
|
23
|
+
list(): AgentRecord[];
|
|
24
|
+
get(name: string): AgentRecord | null;
|
|
25
|
+
heartbeat(name: string): boolean;
|
|
26
|
+
close(): void;
|
|
27
|
+
private rowToRecord;
|
|
28
|
+
}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
export class AgentRegistry {
|
|
6
|
+
db;
|
|
7
|
+
constructor(dbPath) {
|
|
8
|
+
if (!dbPath) {
|
|
9
|
+
const dir = path.join(os.homedir(), '.agentkit-mesh');
|
|
10
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
+
dbPath = path.join(dir, 'registry.db');
|
|
12
|
+
}
|
|
13
|
+
this.db = new Database(dbPath);
|
|
14
|
+
this.db.pragma('journal_mode = WAL');
|
|
15
|
+
this.initSchema();
|
|
16
|
+
}
|
|
17
|
+
initSchema() {
|
|
18
|
+
this.db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
20
|
+
name TEXT PRIMARY KEY,
|
|
21
|
+
description TEXT NOT NULL,
|
|
22
|
+
capabilities TEXT NOT NULL,
|
|
23
|
+
endpoint TEXT NOT NULL,
|
|
24
|
+
protocol TEXT NOT NULL DEFAULT 'mcp',
|
|
25
|
+
registered_at TEXT NOT NULL,
|
|
26
|
+
last_seen TEXT NOT NULL
|
|
27
|
+
)
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
register(agent) {
|
|
31
|
+
if (!agent.name || agent.name.trim().length === 0) {
|
|
32
|
+
throw new Error('Agent name is required');
|
|
33
|
+
}
|
|
34
|
+
if (!agent.endpoint || agent.endpoint.trim().length === 0) {
|
|
35
|
+
throw new Error('Agent endpoint is required');
|
|
36
|
+
}
|
|
37
|
+
const now = new Date().toISOString();
|
|
38
|
+
const capabilities = JSON.stringify(agent.capabilities);
|
|
39
|
+
const protocol = agent.protocol ?? 'mcp';
|
|
40
|
+
this.db.prepare(`
|
|
41
|
+
INSERT INTO agents (name, description, capabilities, endpoint, protocol, registered_at, last_seen)
|
|
42
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
43
|
+
ON CONFLICT(name) DO UPDATE SET
|
|
44
|
+
description = excluded.description,
|
|
45
|
+
capabilities = excluded.capabilities,
|
|
46
|
+
endpoint = excluded.endpoint,
|
|
47
|
+
protocol = excluded.protocol,
|
|
48
|
+
last_seen = excluded.last_seen
|
|
49
|
+
`).run(agent.name, agent.description, capabilities, agent.endpoint, protocol, now, now);
|
|
50
|
+
return this.get(agent.name);
|
|
51
|
+
}
|
|
52
|
+
unregister(name) {
|
|
53
|
+
const result = this.db.prepare('DELETE FROM agents WHERE name = ?').run(name);
|
|
54
|
+
return result.changes > 0;
|
|
55
|
+
}
|
|
56
|
+
list() {
|
|
57
|
+
const rows = this.db.prepare('SELECT * FROM agents ORDER BY name').all();
|
|
58
|
+
return rows.map(r => this.rowToRecord(r));
|
|
59
|
+
}
|
|
60
|
+
get(name) {
|
|
61
|
+
const row = this.db.prepare('SELECT * FROM agents WHERE name = ?').get(name);
|
|
62
|
+
if (!row)
|
|
63
|
+
return null;
|
|
64
|
+
return this.rowToRecord(row);
|
|
65
|
+
}
|
|
66
|
+
heartbeat(name) {
|
|
67
|
+
const now = new Date().toISOString();
|
|
68
|
+
const result = this.db.prepare('UPDATE agents SET last_seen = ? WHERE name = ?').run(now, name);
|
|
69
|
+
return result.changes > 0;
|
|
70
|
+
}
|
|
71
|
+
close() {
|
|
72
|
+
this.db.close();
|
|
73
|
+
}
|
|
74
|
+
rowToRecord(row) {
|
|
75
|
+
return {
|
|
76
|
+
...row,
|
|
77
|
+
capabilities: JSON.parse(row.capabilities),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { DiscoveryEngine } from './discovery.js';
|
|
4
|
+
import { DelegationClient } from './delegation.js';
|
|
5
|
+
export function createServer(registry) {
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: 'agentkit-mesh',
|
|
8
|
+
version: '0.1.0',
|
|
9
|
+
});
|
|
10
|
+
const discovery = new DiscoveryEngine();
|
|
11
|
+
server.tool('mesh_register', 'Register an agent with its capabilities', {
|
|
12
|
+
name: z.string().describe('Unique agent name'),
|
|
13
|
+
description: z.string().describe('What this agent does'),
|
|
14
|
+
capabilities: z.array(z.string()).describe('List of capabilities'),
|
|
15
|
+
endpoint: z.string().describe('Agent endpoint URL'),
|
|
16
|
+
}, async ({ name, description, capabilities, endpoint }) => {
|
|
17
|
+
const agent = registry.register({ name, description, capabilities, endpoint });
|
|
18
|
+
return { content: [{ type: 'text', text: JSON.stringify(agent, null, 2) }] };
|
|
19
|
+
});
|
|
20
|
+
server.tool('mesh_discover', 'Discover agents matching a query', {
|
|
21
|
+
query: z.string().describe('Search query'),
|
|
22
|
+
limit: z.number().optional().describe('Max results'),
|
|
23
|
+
}, async ({ query, limit }) => {
|
|
24
|
+
const results = discovery.discover(query, registry, limit);
|
|
25
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
26
|
+
});
|
|
27
|
+
server.tool('mesh_unregister', 'Unregister an agent', {
|
|
28
|
+
name: z.string().describe('Agent name to remove'),
|
|
29
|
+
}, async ({ name }) => {
|
|
30
|
+
const removed = registry.unregister(name);
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: 'text', text: removed ? `Unregistered ${name}` : `Agent ${name} not found` }],
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
const delegationClient = new DelegationClient();
|
|
36
|
+
server.tool('mesh_delegate', 'Delegate a task to another agent by name', {
|
|
37
|
+
targetName: z.string().describe('Name of the target agent'),
|
|
38
|
+
task: z.string().describe('Task to delegate'),
|
|
39
|
+
context: z.string().optional().describe('Optional JSON context'),
|
|
40
|
+
}, async ({ targetName, task, context }) => {
|
|
41
|
+
const agent = registry.get(targetName);
|
|
42
|
+
if (!agent) {
|
|
43
|
+
return { content: [{ type: 'text', text: `Agent "${targetName}" not found` }] };
|
|
44
|
+
}
|
|
45
|
+
let ctx = {};
|
|
46
|
+
if (context) {
|
|
47
|
+
try {
|
|
48
|
+
ctx = JSON.parse(context);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { content: [{ type: 'text', text: `Invalid JSON in context` }] };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const result = await delegationClient.delegate(agent.endpoint, task, ctx);
|
|
55
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
56
|
+
});
|
|
57
|
+
return server;
|
|
58
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentkit-mesh",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Agent-to-agent discovery and delegation via MCP (Model Context Protocol)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/openclaw/agentkit-mesh"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"build": "tsc"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [],
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "ISC",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
31
|
+
"better-sqlite3": "^12.6.2",
|
|
32
|
+
"zod": "^4.3.6"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
36
|
+
"tsx": "^4.21.0",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"vitest": "^4.0.18"
|
|
39
|
+
},
|
|
40
|
+
"bin": {
|
|
41
|
+
"agentkit-mesh": "./dist/index.js"
|
|
42
|
+
}
|
|
43
|
+
}
|