feat: white list, announce relay list

This commit is contained in:
gzuuus
2025-02-11 19:09:57 +01:00
parent 48c45eab8d
commit 1e3f22647b
6 changed files with 127 additions and 63 deletions

View File

@@ -10,4 +10,7 @@ MCP_SERVICE_ABOUT="MCP-enabled DVM providing AI and computational tools"
MCP_CLIENT_NAME="DVM MCP Bridge Client"
MCP_CLIENT_VERSION="1.0.0"
MCP_SERVER_COMMAND=bun # The command to start the MCP server
MCP_SERVER_ARGS=run,src/external-mcp-server.ts # Comma-separated args to pass to the server command
MCP_SERVER_ARGS=run,src/external-mcp-server.ts # Comma-separated args to pass to the server command
# Optional: Comma-separated list of allowed pubkeys. If empty, allows all.
ALLOWED_PUBKEYS=

View File

@@ -2,6 +2,30 @@ import { config } from 'dotenv';
import { join } from 'path';
import { existsSync } from 'fs';
interface NostrConfig {
privateKey: string;
relayUrls: string[];
}
interface MCPConfig {
name: string;
about: string;
clientName: string;
clientVersion: string;
serverCommand: string;
serverArgs: string[];
}
interface WhitelistConfig {
allowedPubkeys: Set<string> | undefined;
}
interface AppConfig {
nostr: NostrConfig;
mcp: MCPConfig;
whitelist: WhitelistConfig;
}
const envPath = join(process.cwd(), '.env');
if (!existsSync(envPath)) {
throw new Error(
@@ -31,7 +55,7 @@ function getEnvVar(name: string, defaultValue: string): string {
return process.env[name] || defaultValue;
}
export const CONFIG = {
export const CONFIG: AppConfig = {
nostr: {
privateKey: requireEnvVar('PRIVATE_KEY'),
relayUrls: requireEnvVar('RELAY_URLS')
@@ -49,6 +73,11 @@ export const CONFIG = {
serverCommand: requireEnvVar('MCP_SERVER_COMMAND'),
serverArgs: requireEnvVar('MCP_SERVER_ARGS').split(','),
},
whitelist: {
allowedPubkeys: process.env.ALLOWED_PUBKEYS
? new Set(process.env.ALLOWED_PUBKEYS.split(',').map((pk) => pk.trim()))
: undefined,
},
};
if (!HEX_KEYS_REGEX.test(CONFIG.nostr.privateKey)) {

View File

@@ -4,6 +4,7 @@ import { RelayHandler } from './nostr/relay';
import { keyManager } from './nostr/keys';
import relayHandler from './nostr/relay';
import type { Event } from 'nostr-tools/pure';
import { CONFIG } from './config';
export class DVMBridge {
private mcpClient: MCPClientHandler;
@@ -18,13 +19,18 @@ export class DVMBridge {
this.nostrAnnouncer = new NostrAnnouncer(this.mcpClient);
}
private isWhitelisted(pubkey: string): boolean {
if (!CONFIG.whitelist.allowedPubkeys) {
return true;
}
return CONFIG.whitelist.allowedPubkeys.has(pubkey);
}
async start() {
if (this.isRunning) {
console.log('Bridge is already running');
return;
}
console.log('Starting DVM Bridge...');
try {
console.log('Connecting to MCP server...');
await this.mcpClient.connect();
@@ -33,7 +39,7 @@ export class DVMBridge {
console.log('Available MCP tools:', tools);
console.log('Announcing service to Nostr network...');
await this.nostrAnnouncer.announceService();
await this.nostrAnnouncer.updateAnnouncement();
console.log('Setting up request handlers...');
this.relayHandler.subscribeToRequests(this.handleRequest.bind(this));
@@ -65,76 +71,89 @@ export class DVMBridge {
private async handleRequest(event: Event) {
try {
if (event.kind === 5910) {
const command = event.tags.find((tag) => tag[0] === 'c')?.[1];
if (this.isWhitelisted(event.pubkey)) {
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(6910),
content: JSON.stringify({
tools,
}),
tags: [
['request', JSON.stringify(event)],
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(response);
} else {
const jobRequest = 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(
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);
if (command === 'list-tools') {
const tools = await this.mcpClient.listTools();
const response = keyManager.signEvent({
...keyManager.createEventTemplate(6910),
content: JSON.stringify(result),
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);
}
}
}
} else {
const errorStatus = keyManager.signEvent({
...keyManager.createEventTemplate(7000),
content: 'Unauthorized: Pubkey not in whitelist',
tags: [
['status', 'error'],
['e', event.id],
['p', event.pubkey],
],
});
await this.relayHandler.publishEvent(errorStatus);
}
} catch (error) {
console.error('Error handling request:', error);
@@ -143,7 +162,6 @@ export class DVMBridge {
}
if (import.meta.main) {
console.log('Starting DVM-MCP Bridge service...');
const bridge = new DVMBridge();
const shutdown = async () => {

View File

@@ -1,7 +1,7 @@
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { CONFIG } from './config';
// TODO: Add connection to multiple mcp servers and router
export class MCPClientHandler {
private client: Client;
private transport: StdioClientTransport;

View File

@@ -13,9 +13,19 @@ export class NostrAnnouncer {
this.mcpClient = mcpClient;
}
async announceRelayList() {
const event = keyManager.signEvent({
...keyManager.createEventTemplate(10002),
content: '',
tags: CONFIG.nostr.relayUrls.map((url) => ['r', url]),
});
await this.relayHandler.publishEvent(event);
console.log('Announced relay list metadata');
}
async announceService() {
const toolsResult = await this.mcpClient.listTools();
const event = keyManager.signEvent({
...keyManager.createEventTemplate(31990),
content: JSON.stringify({
@@ -31,12 +41,11 @@ export class NostrAnnouncer {
...toolsResult.map((tool) => ['t', tool.name]),
],
});
await this.relayHandler.publishEvent(event);
console.log(`Announced service with ${toolsResult.length} tools`);
}
async updateAnnouncement() {
await this.announceService();
await Promise.all([this.announceService(), this.announceRelayList()]);
}
}

View File

@@ -43,7 +43,9 @@ export class RelayHandler {
async publishEvent(event: Event): Promise<void> {
try {
await Promise.any(this.pool.publish(this.relayUrls, event));
console.log(`Event published(${event.kind}):, ${event.id.slice(0, 12)}`);
console.log(
`Event published(${event.kind}), id: ${event.id.slice(0, 12)}`
);
} catch (error) {
console.error('Failed to publish event:', error);
throw error;
@@ -60,6 +62,9 @@ export class RelayHandler {
const sub = this.pool.subscribeMany(this.relayUrls, filters, {
onevent(event) {
console.log(
`Event received(${event.kind}), id: ${event.id.slice(0, 12)}`
);
onRequest(event);
},
oneose() {