mirror of
https://github.com/aljazceru/dvmcp.git
synced 2025-12-17 05:14:24 +01:00
refactor: adapt code to new approach, readme, spec
This commit is contained in:
40
README.md
40
README.md
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user