Files
dvmcp/packages/dvmcp-bridge/src/config.ts
2025-03-19 17:31:33 +01:00

145 lines
3.9 KiB
TypeScript

import { parse } from 'yaml';
import { join } from 'path';
import { existsSync, readFileSync } from 'fs';
import { HEX_KEYS_REGEX } from '@dvmcp/commons/constants';
import type { Config, MCPServerConfig } from './types';
const CONFIG_PATH = join(process.cwd(), 'config.dvmcp.yml');
const TEST_CONFIG: Config = {
nostr: {
privateKey:
'd4d4d7aae7857054596c4c0976b22a73acac3a10d30bf56db35ee038bbf0dd44',
relayUrls: ['ws://localhost:3334'],
},
mcp: {
name: 'Test DVM MCP Bridge',
about: 'Test MCP-enabled DVM',
clientName: 'Test Client',
clientVersion: '1.0.0',
servers: [],
},
whitelist: {
allowedPubkeys: new Set(),
},
};
function validateRequiredField(value: any, fieldName: string): string {
if (!value) {
throw new Error(`Missing required config field: ${fieldName}`);
}
return value;
}
function getConfigValue(
value: string | undefined,
defaultValue: string
): string {
return value || defaultValue;
}
function validateRelayUrls(urls: any): string[] {
if (!Array.isArray(urls) || urls.length === 0) {
throw new Error(
'At least one relay URL must be provided in nostr.relayUrls'
);
}
return urls.map((url: string) => {
try {
const trimmedUrl = url.trim();
new URL(trimmedUrl);
if (!trimmedUrl.startsWith('ws://') && !trimmedUrl.startsWith('wss://')) {
throw new Error(
`Relay URL must start with ws:// or wss://: ${trimmedUrl}`
);
}
return trimmedUrl;
} catch (error) {
throw new Error(`Invalid relay URL: ${url}`);
}
});
}
function validateMCPServers(servers: any): MCPServerConfig[] {
if (!Array.isArray(servers) || servers.length === 0) {
throw new Error(
'At least one MCP server must be configured in mcp.servers'
);
}
return servers.map((server: any, index: number) => {
if (!server.name || !server.command || !Array.isArray(server.args)) {
throw new Error(
`Invalid MCP server configuration at index ${index}. Required fields: name, command, args[]`
);
}
return {
name: server.name,
command: server.command,
args: server.args,
};
});
}
function loadConfig(): Config {
if (process.env.NODE_ENV === 'test') {
return TEST_CONFIG;
}
if (!existsSync(CONFIG_PATH)) {
throw new Error(
'No config.dvmcp.yml file found. Please create one based on config.example.yml'
);
}
try {
const configFile = readFileSync(CONFIG_PATH, 'utf8');
const rawConfig = parse(configFile);
const config: Config = {
nostr: {
privateKey: validateRequiredField(
rawConfig.nostr?.privateKey,
'nostr.privateKey'
),
relayUrls: validateRelayUrls(rawConfig.nostr?.relayUrls),
},
mcp: {
name: getConfigValue(rawConfig.mcp?.name, 'DVM MCP Bridge'),
about: getConfigValue(
rawConfig.mcp?.about,
'MCP-enabled DVM providing AI and computational tools'
),
clientName: validateRequiredField(
rawConfig.mcp?.clientName,
'mcp.clientName'
),
clientVersion: validateRequiredField(
rawConfig.mcp?.clientVersion,
'mcp.clientVersion'
),
picture: rawConfig.mcp?.picture,
website: rawConfig.mcp?.website,
banner: rawConfig.mcp?.banner,
servers: validateMCPServers(rawConfig.mcp?.servers),
},
whitelist: {
allowedPubkeys: rawConfig.whitelist?.allowedPubkeys
? new Set(
rawConfig.whitelist.allowedPubkeys.map((pk: string) => pk.trim())
)
: undefined,
},
};
if (!HEX_KEYS_REGEX.test(config.nostr.privateKey)) {
throw new Error('privateKey must be a 32-byte hex string');
}
return config;
} catch (error) {
throw new Error(`Failed to load config: ${error}`);
}
}
export const CONFIG = loadConfig();