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:
@@ -11,3 +11,6 @@ MCP_CLIENT_NAME="DVM MCP Bridge Client"
|
|||||||
MCP_CLIENT_VERSION="1.0.0"
|
MCP_CLIENT_VERSION="1.0.0"
|
||||||
MCP_SERVER_COMMAND=bun # The command to start the MCP server
|
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 { join } from 'path';
|
||||||
import { existsSync } from 'fs';
|
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');
|
const envPath = join(process.cwd(), '.env');
|
||||||
if (!existsSync(envPath)) {
|
if (!existsSync(envPath)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -31,7 +55,7 @@ function getEnvVar(name: string, defaultValue: string): string {
|
|||||||
return process.env[name] || defaultValue;
|
return process.env[name] || defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CONFIG = {
|
export const CONFIG: AppConfig = {
|
||||||
nostr: {
|
nostr: {
|
||||||
privateKey: requireEnvVar('PRIVATE_KEY'),
|
privateKey: requireEnvVar('PRIVATE_KEY'),
|
||||||
relayUrls: requireEnvVar('RELAY_URLS')
|
relayUrls: requireEnvVar('RELAY_URLS')
|
||||||
@@ -49,6 +73,11 @@ export const CONFIG = {
|
|||||||
serverCommand: requireEnvVar('MCP_SERVER_COMMAND'),
|
serverCommand: requireEnvVar('MCP_SERVER_COMMAND'),
|
||||||
serverArgs: requireEnvVar('MCP_SERVER_ARGS').split(','),
|
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)) {
|
if (!HEX_KEYS_REGEX.test(CONFIG.nostr.privateKey)) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { RelayHandler } from './nostr/relay';
|
|||||||
import { keyManager } from './nostr/keys';
|
import { keyManager } from './nostr/keys';
|
||||||
import relayHandler from './nostr/relay';
|
import relayHandler from './nostr/relay';
|
||||||
import type { Event } from 'nostr-tools/pure';
|
import type { Event } from 'nostr-tools/pure';
|
||||||
|
import { CONFIG } from './config';
|
||||||
|
|
||||||
export class DVMBridge {
|
export class DVMBridge {
|
||||||
private mcpClient: MCPClientHandler;
|
private mcpClient: MCPClientHandler;
|
||||||
@@ -18,13 +19,18 @@ export class DVMBridge {
|
|||||||
this.nostrAnnouncer = new NostrAnnouncer(this.mcpClient);
|
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() {
|
async start() {
|
||||||
if (this.isRunning) {
|
if (this.isRunning) {
|
||||||
console.log('Bridge is already running');
|
console.log('Bridge is already running');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Starting DVM Bridge...');
|
|
||||||
try {
|
try {
|
||||||
console.log('Connecting to MCP server...');
|
console.log('Connecting to MCP server...');
|
||||||
await this.mcpClient.connect();
|
await this.mcpClient.connect();
|
||||||
@@ -33,7 +39,7 @@ export class DVMBridge {
|
|||||||
console.log('Available MCP tools:', tools);
|
console.log('Available MCP tools:', tools);
|
||||||
|
|
||||||
console.log('Announcing service to Nostr network...');
|
console.log('Announcing service to Nostr network...');
|
||||||
await this.nostrAnnouncer.announceService();
|
await this.nostrAnnouncer.updateAnnouncement();
|
||||||
|
|
||||||
console.log('Setting up request handlers...');
|
console.log('Setting up request handlers...');
|
||||||
this.relayHandler.subscribeToRequests(this.handleRequest.bind(this));
|
this.relayHandler.subscribeToRequests(this.handleRequest.bind(this));
|
||||||
@@ -65,6 +71,7 @@ export class DVMBridge {
|
|||||||
|
|
||||||
private async handleRequest(event: Event) {
|
private async handleRequest(event: Event) {
|
||||||
try {
|
try {
|
||||||
|
if (this.isWhitelisted(event.pubkey)) {
|
||||||
if (event.kind === 5910) {
|
if (event.kind === 5910) {
|
||||||
const command = event.tags.find((tag) => tag[0] === 'c')?.[1];
|
const command = event.tags.find((tag) => tag[0] === 'c')?.[1];
|
||||||
|
|
||||||
@@ -136,6 +143,18 @@ export class DVMBridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} 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) {
|
} catch (error) {
|
||||||
console.error('Error handling request:', error);
|
console.error('Error handling request:', error);
|
||||||
}
|
}
|
||||||
@@ -143,7 +162,6 @@ export class DVMBridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (import.meta.main) {
|
if (import.meta.main) {
|
||||||
console.log('Starting DVM-MCP Bridge service...');
|
|
||||||
const bridge = new DVMBridge();
|
const bridge = new DVMBridge();
|
||||||
|
|
||||||
const shutdown = async () => {
|
const shutdown = async () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||||
import { CONFIG } from './config';
|
import { CONFIG } from './config';
|
||||||
|
// TODO: Add connection to multiple mcp servers and router
|
||||||
export class MCPClientHandler {
|
export class MCPClientHandler {
|
||||||
private client: Client;
|
private client: Client;
|
||||||
private transport: StdioClientTransport;
|
private transport: StdioClientTransport;
|
||||||
|
|||||||
@@ -13,9 +13,19 @@ export class NostrAnnouncer {
|
|||||||
this.mcpClient = mcpClient;
|
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() {
|
async announceService() {
|
||||||
const toolsResult = await this.mcpClient.listTools();
|
const toolsResult = await this.mcpClient.listTools();
|
||||||
|
|
||||||
const event = keyManager.signEvent({
|
const event = keyManager.signEvent({
|
||||||
...keyManager.createEventTemplate(31990),
|
...keyManager.createEventTemplate(31990),
|
||||||
content: JSON.stringify({
|
content: JSON.stringify({
|
||||||
@@ -31,12 +41,11 @@ export class NostrAnnouncer {
|
|||||||
...toolsResult.map((tool) => ['t', tool.name]),
|
...toolsResult.map((tool) => ['t', tool.name]),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.relayHandler.publishEvent(event);
|
await this.relayHandler.publishEvent(event);
|
||||||
console.log(`Announced service with ${toolsResult.length} tools`);
|
console.log(`Announced service with ${toolsResult.length} tools`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAnnouncement() {
|
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> {
|
async publishEvent(event: Event): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await Promise.any(this.pool.publish(this.relayUrls, event));
|
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) {
|
} catch (error) {
|
||||||
console.error('Failed to publish event:', error);
|
console.error('Failed to publish event:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -60,6 +62,9 @@ export class RelayHandler {
|
|||||||
|
|
||||||
const sub = this.pool.subscribeMany(this.relayUrls, filters, {
|
const sub = this.pool.subscribeMany(this.relayUrls, filters, {
|
||||||
onevent(event) {
|
onevent(event) {
|
||||||
|
console.log(
|
||||||
|
`Event received(${event.kind}), id: ${event.id.slice(0, 12)}`
|
||||||
|
);
|
||||||
onRequest(event);
|
onRequest(event);
|
||||||
},
|
},
|
||||||
oneose() {
|
oneose() {
|
||||||
|
|||||||
Reference in New Issue
Block a user