mirror of
https://github.com/aljazceru/dvmcp.git
synced 2025-12-17 05:14:24 +01:00
docs: clarify spec, feat: relay reconnection loop, and other improvements
This commit is contained in:
@@ -66,7 +66,7 @@ Clients MAY use either method or both depending on their needs. Each method has
|
||||
|
||||
## Discovery via NIP-89 Announcements
|
||||
|
||||
DVMs SHOULD publish their tool listings using NIP-89 announcements. This enables immediate tool discovery without requiring a request/response cycle and allows clients to discover tools through relay queries.
|
||||
You can query relays by creating a filter for events with kind `31990`, and `t` tag `mcp`. DVMs SHOULD include their available tools directly in their kind:31990 announcement events. This enables immediate tool discovery and execution without requiring an additional request/response cycle. Here's an example of a complete announcement:
|
||||
|
||||
Example announcement:
|
||||
|
||||
@@ -80,11 +80,16 @@ Example announcement:
|
||||
"tools": [
|
||||
{
|
||||
"name": "summarize",
|
||||
"description": "Summarizes text input"
|
||||
},
|
||||
{
|
||||
"name": "translate",
|
||||
"description": "Translates text between languages"
|
||||
"description": "Summarizes text input",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "Text to summarize"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -105,12 +110,19 @@ Each tool in the `tools` array MUST include:
|
||||
|
||||
- `name`: The unique identifier for the tool
|
||||
- `description`: A brief description of the tool's functionality
|
||||
- `tools`: The tools present in the MCP server
|
||||
|
||||
To maintain the announcement under control, the NIP-89 announcement SHOULD NOT include full input schemas. Since some apis might have an undefined amount of inputs and the event might end pretty verbose or lengthly.
|
||||
### Required Tags
|
||||
|
||||
- `d`: A unique identifier for this announcement that should be maintained consistently for announcement updates
|
||||
- `k`: The event kind this DVM supports (5910 for MCP bridge requests)
|
||||
- `capabilities`: Must include "mcp-1.0" to indicate MCP protocol support
|
||||
- `t`: Should include "mcp", and also tool names, to aid in discovery
|
||||
|
||||
## Discovery via Direct Request
|
||||
|
||||
Following NIP-90's model, clients MAY discover tools by publishing a request event and receiving responses from available DVMs. This method allows for discovery of DVMs that may not publish NIP-89 announcements.
|
||||
Another way to do discovery using the previous list tools request is to query relays with a filter for events with type `5910` and `c` tag `list-tools-response`.
|
||||
|
||||
### List Tools Request
|
||||
|
||||
@@ -210,7 +222,8 @@ Tools are executed through request/response pairs using kinds 5910/6910.
|
||||
},
|
||||
"tags": [
|
||||
["c", "execute-tool"],
|
||||
["p", "<provider-pubkey>"]
|
||||
["p", "<provider-pubkey>"],
|
||||
["output", "application/json"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@ import { MCPClientHandler } from './mcp-client';
|
||||
import { NostrAnnouncer } from './nostr/announcer';
|
||||
import { RelayHandler } from './nostr/relay';
|
||||
import { keyManager } from './nostr/keys';
|
||||
import { CONFIG } from './config';
|
||||
import relayHandler from './nostr/relay';
|
||||
import type { Event } from 'nostr-tools/pure';
|
||||
|
||||
export class DVMBridge {
|
||||
@@ -14,8 +14,8 @@ export class DVMBridge {
|
||||
constructor() {
|
||||
console.log('Initializing DVM Bridge...');
|
||||
this.mcpClient = new MCPClientHandler();
|
||||
this.relayHandler = relayHandler;
|
||||
this.nostrAnnouncer = new NostrAnnouncer(this.mcpClient);
|
||||
this.relayHandler = new RelayHandler(CONFIG.nostr.relayUrls);
|
||||
}
|
||||
|
||||
async start() {
|
||||
|
||||
@@ -33,7 +33,7 @@ export class MCPClientHandler {
|
||||
}
|
||||
|
||||
async listTools() {
|
||||
return await this.client.listTools();
|
||||
return (await this.client.listTools()).tools;
|
||||
}
|
||||
|
||||
async callTool(name: string, args: Record<string, any>) {
|
||||
|
||||
@@ -2,45 +2,38 @@ import { CONFIG } from '../config';
|
||||
import { RelayHandler } from './relay';
|
||||
import { keyManager } from './keys';
|
||||
import { MCPClientHandler } from '../mcp-client';
|
||||
import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
|
||||
import relayHandler from './relay';
|
||||
|
||||
export class NostrAnnouncer {
|
||||
private relayHandler: RelayHandler;
|
||||
private mcpClient: MCPClientHandler;
|
||||
|
||||
constructor(mcpClient: MCPClientHandler) {
|
||||
this.relayHandler = new RelayHandler(CONFIG.nostr.relayUrls);
|
||||
this.relayHandler = relayHandler;
|
||||
this.mcpClient = mcpClient;
|
||||
}
|
||||
|
||||
async announceService() {
|
||||
const toolsResult: ListToolsResult = await this.mcpClient.listTools();
|
||||
|
||||
const toolsListing = toolsResult.tools
|
||||
.map((tool) => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
}))
|
||||
.slice(0, 100); // Hard limit to 100 tools
|
||||
const toolsResult = await this.mcpClient.listTools();
|
||||
|
||||
const event = keyManager.signEvent({
|
||||
...keyManager.createEventTemplate(31990),
|
||||
content: JSON.stringify({
|
||||
name: CONFIG.mcp.name,
|
||||
about: CONFIG.mcp.about,
|
||||
tools: toolsListing,
|
||||
tools: toolsResult,
|
||||
}),
|
||||
tags: [
|
||||
['d', 'dvm-announcement'],
|
||||
['k', '5910'],
|
||||
['capabilities', 'mcp-1.0'],
|
||||
['t', 'mcp'],
|
||||
...toolsListing.map((tool) => ['t', tool.name]),
|
||||
...toolsResult.map((tool) => ['t', tool.name]),
|
||||
],
|
||||
});
|
||||
|
||||
await this.relayHandler.publishEvent(event);
|
||||
console.log(`Announced service with ${toolsListing.length} tools`);
|
||||
console.log(`Announced service with ${toolsResult.length} tools`);
|
||||
}
|
||||
|
||||
async updateAnnouncement() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { SubCloser } from 'nostr-tools/pool';
|
||||
import WebSocket from 'ws';
|
||||
import { useWebSocketImplementation } from 'nostr-tools/pool';
|
||||
import type { Filter } from 'nostr-tools';
|
||||
import { CONFIG } from '../config';
|
||||
|
||||
useWebSocketImplementation(WebSocket);
|
||||
|
||||
@@ -11,10 +12,32 @@ export class RelayHandler {
|
||||
private pool: SimplePool;
|
||||
private relayUrls: string[];
|
||||
private subscriptions: SubCloser[] = [];
|
||||
private reconnectInterval?: ReturnType<typeof setTimeout>;
|
||||
|
||||
constructor(relayUrls: string[]) {
|
||||
this.pool = new SimplePool();
|
||||
this.relayUrls = relayUrls;
|
||||
this.startReconnectLoop();
|
||||
}
|
||||
|
||||
private startReconnectLoop() {
|
||||
this.reconnectInterval = setInterval(() => {
|
||||
this.relayUrls.forEach((url) => {
|
||||
const normalizedUrl = new URL(url).href;
|
||||
if (!this.getConnectionStatus().get(normalizedUrl)) {
|
||||
this.ensureRelay(url);
|
||||
}
|
||||
});
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
private async ensureRelay(url: string) {
|
||||
try {
|
||||
await this.pool.ensureRelay(url, { connectionTimeout: 5000 });
|
||||
console.log(`Connected to relay: ${url}`);
|
||||
} catch (error) {
|
||||
console.log(`Failed to connect to relay ${url}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async publishEvent(event: Event): Promise<void> {
|
||||
@@ -42,6 +65,9 @@ export class RelayHandler {
|
||||
oneose() {
|
||||
console.log('Reached end of stored events');
|
||||
},
|
||||
onclose(reasons) {
|
||||
console.log('Subscription closed:', reasons);
|
||||
},
|
||||
});
|
||||
|
||||
this.subscriptions.push(sub);
|
||||
@@ -53,8 +79,17 @@ export class RelayHandler {
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if (this.reconnectInterval) {
|
||||
clearInterval(this.reconnectInterval);
|
||||
}
|
||||
this.subscriptions.forEach((sub) => sub.close());
|
||||
this.subscriptions = [];
|
||||
this.pool.close(this.relayUrls);
|
||||
}
|
||||
|
||||
getConnectionStatus(): Map<string, boolean> {
|
||||
return this.pool.listConnectionStatus();
|
||||
}
|
||||
}
|
||||
|
||||
export default new RelayHandler(CONFIG.nostr.relayUrls);
|
||||
|
||||
Reference in New Issue
Block a user