mirror of
https://github.com/aljazceru/dvmcp.git
synced 2025-12-17 05:14:24 +01:00
feat: white list, announce relay list
This commit is contained in:
@@ -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=
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user