refactor: adapt code to new approach, readme, spec

This commit is contained in:
gzuuus
2025-02-08 19:31:45 +01:00
parent c5adb8c33d
commit 244848c762
5 changed files with 124 additions and 92 deletions

View File

@@ -14,8 +14,7 @@ The Model Context Protocol provides a standardized way for applications to expos
## Features
- **Service Discovery**: Automatically announces MCP services using NIP-89
- **Tool Discovery**: Exposes MCP tools through DVM kind:5600/6600 events
- **Tool Execution**: Handles tool execution requests via kind:5601/6601 events
- **Tool Discovery and Execution**: Exposes and executes MCP tools through DVM kind:5910/6910 events
- **Status Updates**: Provides job status and payment handling via kind:7000 events
- **Error Handling**: Comprehensive error handling and status reporting
- **Payment Flow**: Built-in support for Lightning payment processing
@@ -79,25 +78,38 @@ The bridge operates in several stages:
- Announces service availability on Nostr network
- Begins listening for DVM requests
2. **Tool Discovery**:
2. **Tool Operations**:
- Receives kind:5600 requests from clients
- Queries available tools from MCP server
- Responds with kind:6600 tool catalog
3. **Tool Execution**:
- Receives kind:5601 execution requests
- Validates parameters against tool schema
- Executes tool via MCP server
- Returns results via kind:6601 events
- Receives kind:5910 requests from clients for tool listing or execution
- Processes requests based on the 'c' tag command
- Responds with kind:6910 events containing tool catalog or execution results
- Provides status updates via kind:7000 events
4. **Payment Processing**:
3. **Payment Processing**:
- Handles payment requirements through kind:7000 events
- Supports Lightning Network payments
- Provides execution status updates
## Example Commands
List available tools:
```bash
nak event -k 5910 -c '' --tag 'c=list-tools' --tag 'output=application/json' wss://relay.com
```
Execute a tool:
```bash
nak event -k 5910 -c '{"name":"extract","parameters":{"url":"https://nostr.how"}}' --tag 'c=execute-tool' wss://relay.com
```
Monitor results:
```bash
nak req --stream -k 6910 -k 7000 -s "$(date +%s)" wss://relay.com | jq --stream "fromstream(0|truncate_stream(inputs))"
```
## Contributing
Contributions are welcome! Please feel free to submit pull requests, or issues. For major changes, please open an issue first to discuss what you would like to change.

View File

@@ -42,18 +42,18 @@ Following NIP-90 conventions, this specification defines these event kinds:
| Kind | Description |
| ---- | ------------------------ |
| 5600 | DVM-MCP Bridge Requests |
| 6600 | DVM-MCP Bridge Responses |
| 5910 | DVM-MCP Bridge Requests |
| 6910 | DVM-MCP Bridge Responses |
| 7000 | Job Feedback |
Operations are differentiated using the `c` tag, which specifies the command being executed:
| Command Value | Type | Kind | Description |
| --------------------- | -------- | ---- | ----------------------------------------- |
| list-tools | Request | 5600 | Request available tools catalog |
| list-tools-response | Response | 6600 | Returns available tools and their schemas |
| execute-tool | Request | 5600 | Request execution of a specific tool |
| execute-tool-response | Response | 6600 | Returns the results of tool execution |
| list-tools | Request | 5910 | Request available tools catalog |
| list-tools-response | Response | 6910 | Returns available tools and their schemas |
| execute-tool | Request | 5910 | Request execution of a specific tool |
| execute-tool-response | Response | 6910 | Returns the results of tool execution |
## Service Discovery
@@ -69,7 +69,7 @@ Service providers SHOULD announce their DVM capabilities using NIP-89 handler in
},
"tags": [
["d", "<dvm-announcement/random-id>"],
["k", "5600"],
["k", "5910"],
["capabilities", "mcp-1.0"],
["t", "mcp"]
]
@@ -105,7 +105,7 @@ DVMs SHOULD include their available tools directly in their kind:31990 announcem
},
"tags": [
["d", "<dvm-announcement/random-id>"],
["k", "5600"],
["k", "5910"],
["capabilities", "mcp-1.0"],
["t", "mcp"],
["t", "summarize"]
@@ -130,7 +130,7 @@ DVMs MAY fall back to the `list-tools` command (described in the Tool Discovery
### Required Tags
- `d`: A unique identifier for this announcement that should be maintained consistently for announcement updates
- `k`: The event kind this DVM supports (5600 for MCP bridge requests)
- `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
@@ -140,7 +140,7 @@ Clients can also discover available tools by sending a request:
```json
{
"kind": 5600,
"kind": 5910,
"content": "",
"tags": [
["c", "list-tools"],
@@ -156,11 +156,11 @@ The `p` tag MAY be included to target a specific provider:
["p", "<provider-pubkey>"]
```
The DVM MUST respond with a kind 6600 event:
The DVM MUST respond with a kind 6910 event:
```json
{
"kind": 6600,
"kind": 6910,
"content": {
"tools": [
{
@@ -204,13 +204,13 @@ The DVM MUST respond with a kind 6600 event:
## Job Execution
Tools are executed through request/response pairs using kinds 5600/6600.
Tools are executed through request/response pairs using kinds 5910/6910.
### Job Request
```json
{
"kind": 5600,
"kind": 5910,
"content": {
"name": "<tool-name>",
"parameters": {
@@ -240,7 +240,7 @@ The content object MAY include:
```json
{
"kind": 6600,
"kind": 6910,
"content": {
"content": [
{
@@ -301,11 +301,11 @@ The `status` tag MUST use one of these values:
A typical payment flow proceeds as follows:
1. Client submits job request (kind:5600)
1. Client submits job request (kind:5910)
2. DVM responds with payment requirement (kind:7000)
3. Client pays the invoice
4. DVM indicates processing (kind:7000)
5. DVM returns results (kind:6600)
5. DVM returns results (kind:6910)
## Error Handling
@@ -328,7 +328,7 @@ DVMs MUST handle both protocol and execution errors appropriately:
For any error, DVMs MUST:
1. Send a kind:7000 event with status "error"
2. Set isError=true in the kind:6600 response
2. Set isError=true in the kind:6910 response
3. Include relevant error details
## Implementation Requirements
@@ -357,21 +357,21 @@ sequenceDiagram
Relay-->>Client: DVM handler info with tools
alt Tools not in announcement
Client->>DVM: kind:5600, c:list-tools
Client->>DVM: kind:5910, c:list-tools
DVM->>Server: Initialize + Get Tools
Server-->>DVM: Tool Definitions
DVM-->>Client: kind:6600, c:list-tools-response
DVM-->>Client: kind:6910, c:list-tools-response
end
Note over Client,DVM: Tool execution (same for both paths)
Client->>DVM: kind:5600, c:execute-tool
Client->>DVM: kind:5910, c:execute-tool
DVM-->>Client: kind:7000 (payment-required)
Client->>DVM: Payment
DVM-->>Client: kind:7000 (processing)
DVM->>Server: Execute Tool
Server-->>DVM: Results
DVM-->>Client: kind:7000 (success)
DVM-->>Client: kind:6600, c:execute-tool-response
DVM-->>Client: kind:6910, c:execute-tool-response
```
## Future Extensions

View File

@@ -14,7 +14,7 @@ export class DVMBridge {
constructor() {
console.log('Initializing DVM Bridge...');
this.mcpClient = new MCPClientHandler();
this.nostrAnnouncer = new NostrAnnouncer();
this.nostrAnnouncer = new NostrAnnouncer(this.mcpClient);
this.relayHandler = new RelayHandler(CONFIG.nostr.relayUrls);
}
@@ -65,71 +65,75 @@ export class DVMBridge {
private async handleRequest(event: Event) {
try {
if (event.kind === 5600) {
const tools = await this.mcpClient.listTools();
const response = keyManager.signEvent({
...keyManager.createEventTemplate(6600),
content: JSON.stringify({
schema_version: '1.0',
tools,
}),
tags: [
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(response);
} else if (event.kind === 5601) {
const { name, parameters } = JSON.parse(event.content);
const processingStatus = keyManager.signEvent({
...keyManager.createEventTemplate(7000),
tags: [
['status', 'processing'],
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(processingStatus);
try {
const result = await this.mcpClient.callTool(name, parameters);
const successStatus = keyManager.signEvent({
...keyManager.createEventTemplate(7000),
tags: [
['status', 'success'],
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(successStatus);
if (event.kind === 5910) {
const command = event.tags.find((tag) => tag[0] === 'c')?.[1];
if (command === 'list-tools') {
const tools = await this.mcpClient.listTools();
const response = keyManager.signEvent({
...keyManager.createEventTemplate(6601),
content: JSON.stringify(result),
...keyManager.createEventTemplate(6910),
content: JSON.stringify({
tools,
}),
tags: [
['request', JSON.stringify(event)],
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(response);
} catch (error) {
const errorStatus = keyManager.signEvent({
} else {
const jobRequest = JSON.parse(event.content);
const processingStatus = keyManager.signEvent({
...keyManager.createEventTemplate(7000),
tags: [
[
'status',
'error',
error instanceof Error ? error.message : 'Unknown error',
],
['status', 'processing'],
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(errorStatus);
await this.relayHandler.publishEvent(processingStatus);
try {
const result = await this.mcpClient.callTool(
jobRequest.name,
jobRequest.parameters
);
const successStatus = keyManager.signEvent({
...keyManager.createEventTemplate(7000),
tags: [
['status', 'success'],
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(successStatus);
const response = keyManager.signEvent({
...keyManager.createEventTemplate(6910),
content: JSON.stringify(result),
tags: [
['request', JSON.stringify(event)],
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(response);
} catch (error) {
const errorStatus = keyManager.signEvent({
...keyManager.createEventTemplate(7000),
tags: [
[
'status',
'error',
error instanceof Error ? error.message : 'Unknown error',
],
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(errorStatus);
}
}
}
} catch (error) {

View File

@@ -1,30 +1,46 @@
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';
export class NostrAnnouncer {
private relayHandler: RelayHandler;
private mcpClient: MCPClientHandler;
constructor() {
constructor(mcpClient: MCPClientHandler) {
this.relayHandler = new RelayHandler(CONFIG.nostr.relayUrls);
this.mcpClient = mcpClient;
}
async announceService() {
const toolsResult: ListToolsResult = await this.mcpClient.listTools();
const toolsWithMetadata = toolsResult.tools
.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
}))
.slice(0, 100); // Hard limit to 100 tools
const event = keyManager.signEvent({
...keyManager.createEventTemplate(31990),
content: JSON.stringify({
name: CONFIG.mcp.name,
about: CONFIG.mcp.about,
tools: toolsWithMetadata,
}),
tags: [
['d', 'dvm-announcement'],
['k', '5600'],
['k', '5601'],
['k', '5910'],
['capabilities', 'mcp-1.0'],
['t', 'mcp'],
...toolsWithMetadata.map((tool) => ['t', tool.name]),
],
});
await this.relayHandler.publishEvent(event);
console.log(`Announced service with ${toolsWithMetadata.length} tools`);
}
}

View File

@@ -30,7 +30,7 @@ export class RelayHandler {
subscribeToRequests(onRequest: (event: Event) => void): SubCloser {
const filters: Filter[] = [
{
kinds: [5600, 5601],
kinds: [5910],
since: Math.floor(Date.now() / 1000),
},
];