mirror of
https://github.com/aljazceru/dvmcp.git
synced 2025-12-17 05:14:24 +01:00
Refactor/clis (#6)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -178,4 +178,4 @@ dist
|
||||
codebase*
|
||||
.aider*
|
||||
.prettierrc
|
||||
config.yml
|
||||
config.dvmcp.yml
|
||||
@@ -42,8 +42,8 @@ This specification defines these event kinds:
|
||||
| Kind | Description |
|
||||
| ----- | ------------------------------------- |
|
||||
| 31990 | DVM Service Announcement (via NIP-89) |
|
||||
| 5910 | DVM-MCP Bridge Requests |
|
||||
| 6910 | DVM-MCP Bridge Responses |
|
||||
| 5910 | DVMCP Bridge Requests |
|
||||
| 6910 | DVMCP Bridge Responses |
|
||||
| 7000 | Job Feedback |
|
||||
|
||||
Operations are differentiated using the `c` tag, which specifies the command being executed:
|
||||
@@ -339,7 +339,7 @@ For any error, DVMs MUST:
|
||||
sequenceDiagram
|
||||
participant Client as Nostr Client
|
||||
participant Relay as Nostr Relay
|
||||
participant DVM as MCP-DVM Bridge
|
||||
participant DVM as DVMCP-Bridge
|
||||
participant Server as MCP Server
|
||||
|
||||
rect rgb(240, 240, 240)
|
||||
|
||||
@@ -4,4 +4,4 @@ node_modules
|
||||
.DS_Store
|
||||
*.test.ts
|
||||
*.test.js
|
||||
config.yml
|
||||
config.dvmcp.yml
|
||||
@@ -12,11 +12,11 @@ A bridge implementation that connects Model Context Protocol (MCP) servers to No
|
||||
|
||||
## Configuration
|
||||
|
||||
When the package is run for the first time, it will detect if the `config.yml` file exists, and if not, it will launch a configuration wizard to help you create the configuration file. You can also create your configuration file by copying `config.example.yml` and changing the values of the fields
|
||||
When the package is run for the first time, it will detect if the `'config.dvmcp.yml'` file exists, and if not, it will launch a configuration wizard to help you create the configuration file. You can also create your configuration file by copying `config.example.yml` and changing the values of the fields
|
||||
|
||||
```bash
|
||||
cp config.example.yml config.yml
|
||||
nano config.yml
|
||||
cp config.example.yml config.dvmcp.yml
|
||||
nano config.dvmcp.yml
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { argv } from 'process';
|
||||
import type { Config } from './src/types';
|
||||
|
||||
const configPath = join(process.cwd(), 'config.yml');
|
||||
const configPath = join(process.cwd(), 'config.dvmcp.yml');
|
||||
|
||||
const configFields: Record<string, FieldConfig> = {
|
||||
nostr: {
|
||||
|
||||
@@ -15,7 +15,9 @@ mcp:
|
||||
# Required client information
|
||||
clientName: "DVM MCP Bridge Client"
|
||||
clientVersion: "1.0.0"
|
||||
|
||||
# optional metadata
|
||||
picture: "https://image.nostr.build/5bf2e2eb3b858bf72c23e53ed1f41ed0f65b2c8a805eaa48dd506b7cfec4ab88.png"
|
||||
website: "https://github.com/gzuuus/dvmcp"
|
||||
# MCP Servers Configuration accepts multiple servers
|
||||
servers:
|
||||
- name: "server1"
|
||||
|
||||
18
packages/dvmcp-bridge/config.yml
Normal file
18
packages/dvmcp-bridge/config.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
nostr:
|
||||
privateKey: 0e79e5e90754cb23275477a32bd1c42bee28dc78a0b10693dfd8cac6852363fd
|
||||
relayUrls:
|
||||
- ws://localhost:10547
|
||||
mcp:
|
||||
name: DVM MCP Bridge
|
||||
about: MCP-enabled DVM providing AI and computational tools
|
||||
clientName: DVM MCP Bridge Client
|
||||
clientVersion: 1.0.0
|
||||
servers:
|
||||
- name: hello
|
||||
command: bun
|
||||
args:
|
||||
- --cwd
|
||||
- /home/user/Documents/dev/gz/js-ts/bun/mcp-echo
|
||||
- start:mcp
|
||||
whitelist:
|
||||
allowedPubkeys: []
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@dvmcp/bridge",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.11",
|
||||
"description": "Bridge connecting MCP servers to Nostr's DVM ecosystem",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
@@ -35,7 +35,7 @@
|
||||
"dotenv": "^16.4.7",
|
||||
"nostr-tools": "^2.10.4",
|
||||
"yaml": "^2.7.0",
|
||||
"@dvmcp/commons": "^0.1.1"
|
||||
"@dvmcp/commons": "^0.1.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -37,6 +37,9 @@ export class NostrAnnouncer {
|
||||
content: JSON.stringify({
|
||||
name: CONFIG.mcp.name,
|
||||
about: CONFIG.mcp.about,
|
||||
picture: CONFIG.mcp.picture,
|
||||
website: CONFIG.mcp.website,
|
||||
banner: CONFIG.mcp.banner,
|
||||
tools: tools,
|
||||
}),
|
||||
tags: [
|
||||
|
||||
@@ -4,7 +4,7 @@ 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.yml');
|
||||
const CONFIG_PATH = join(process.cwd(), 'config.dvmcp.yml');
|
||||
|
||||
const TEST_CONFIG: Config = {
|
||||
nostr: {
|
||||
@@ -87,7 +87,7 @@ function loadConfig(): Config {
|
||||
|
||||
if (!existsSync(CONFIG_PATH)) {
|
||||
throw new Error(
|
||||
'No config.yml file found. Please create one based on config.example.yml'
|
||||
'No config.dvmcp.yml file found. Please create one based on config.example.yml'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,6 +117,9 @@ function loadConfig(): Config {
|
||||
rawConfig.mcp?.clientVersion,
|
||||
'mcp.clientVersion'
|
||||
),
|
||||
picture: rawConfig.mcp?.picture,
|
||||
website: rawConfig.mcp?.website,
|
||||
banner: rawConfig.mcp?.banner,
|
||||
servers: validateMCPServers(rawConfig.mcp?.servers),
|
||||
},
|
||||
whitelist: {
|
||||
|
||||
@@ -24,7 +24,10 @@ export class DVMBridge {
|
||||
}
|
||||
|
||||
private isWhitelisted(pubkey: string): boolean {
|
||||
if (!CONFIG.whitelist.allowedPubkeys) {
|
||||
if (
|
||||
!CONFIG.whitelist.allowedPubkeys ||
|
||||
CONFIG.whitelist.allowedPubkeys.size == 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return CONFIG.whitelist.allowedPubkeys.has(pubkey);
|
||||
@@ -47,7 +50,12 @@ export class DVMBridge {
|
||||
await this.nostrAnnouncer.updateAnnouncement();
|
||||
|
||||
console.log('Setting up request handlers...');
|
||||
this.relayHandler.subscribeToRequests(this.handleRequest.bind(this));
|
||||
const publicKey = keyManager.getPublicKey();
|
||||
this.relayHandler.subscribeToRequests(this.handleRequest.bind(this), {
|
||||
kinds: [TOOL_REQUEST_KIND],
|
||||
'#p': [publicKey],
|
||||
since: Math.floor(Date.now() / 1000),
|
||||
});
|
||||
|
||||
this.isRunning = true;
|
||||
console.log('DVM Bridge is now running and ready to handle requests');
|
||||
@@ -88,14 +96,14 @@ export class DVMBridge {
|
||||
tools,
|
||||
}),
|
||||
tags: [
|
||||
['request', JSON.stringify(event)],
|
||||
['c', 'list-tools-response'],
|
||||
['e', event.id],
|
||||
['p', event.pubkey],
|
||||
],
|
||||
});
|
||||
|
||||
await this.relayHandler.publishEvent(response);
|
||||
} else {
|
||||
} else if (command === 'execute-tool') {
|
||||
const jobRequest = JSON.parse(event.content);
|
||||
const processingStatus = keyManager.signEvent({
|
||||
...keyManager.createEventTemplate(DVM_NOTICE_KIND),
|
||||
@@ -125,7 +133,7 @@ export class DVMBridge {
|
||||
...keyManager.createEventTemplate(TOOL_RESPONSE_KIND),
|
||||
content: JSON.stringify(result),
|
||||
tags: [
|
||||
['request', JSON.stringify(event)],
|
||||
['c', 'execute-tool-response'],
|
||||
['e', event.id],
|
||||
['p', event.pubkey],
|
||||
],
|
||||
@@ -159,6 +167,7 @@ export class DVMBridge {
|
||||
],
|
||||
});
|
||||
await this.relayHandler.publishEvent(errorStatus);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling request:', error);
|
||||
|
||||
@@ -22,4 +22,9 @@ describe('KeyManager', () => {
|
||||
expect(signedEvent.sig).toBeDefined();
|
||||
expect(signedEvent.pubkey).toBe(keyManager.pubkey);
|
||||
});
|
||||
|
||||
test('should get public key', () => {
|
||||
const publicKey = keyManager.getPublicKey();
|
||||
expect(publicKey).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,10 @@ describe('MCPPool', () => {
|
||||
let transports: any[] = [];
|
||||
const serverNames = ['server1', 'server2', 'server3', 'server4'];
|
||||
beforeAll(async () => {
|
||||
const mockServerPath = join(import.meta.dir, 'mock-server.ts');
|
||||
const mockServerPath = join(
|
||||
import.meta.dir,
|
||||
'../../dvmcp-commons/mock-server.ts'
|
||||
);
|
||||
|
||||
const serverConfigs = serverNames.map((name) => ({
|
||||
name,
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const createMockServer = async (name: string) => {
|
||||
const server = new McpServer({
|
||||
name: `Mock ${name}`,
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
server.tool(
|
||||
`${name}-echo`,
|
||||
`Echo tool for ${name}`,
|
||||
{
|
||||
text: z.string(),
|
||||
},
|
||||
async ({ text }) => ({
|
||||
content: [{ type: 'text' as const, text: `[${name}] ${text}` }],
|
||||
})
|
||||
);
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
return { server, transport };
|
||||
};
|
||||
|
||||
if (import.meta.path === Bun.main) {
|
||||
await createMockServer(process.argv[2] || 'default');
|
||||
}
|
||||
@@ -8,6 +8,9 @@ export interface MCPConfig {
|
||||
about: string;
|
||||
clientName: string;
|
||||
clientVersion: string;
|
||||
picture?: string;
|
||||
website?: string;
|
||||
banner?: string;
|
||||
servers: MCPServerConfig[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createInterface } from 'node:readline';
|
||||
import { stringify } from 'yaml';
|
||||
import { writeFileSync, existsSync } from 'node:fs';
|
||||
import { parse, stringify } from 'yaml';
|
||||
import { writeFileSync, existsSync, readFileSync } from 'node:fs';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { HEX_KEYS_REGEX } from './constants';
|
||||
|
||||
@@ -47,10 +47,21 @@ export interface FieldConfig {
|
||||
}
|
||||
|
||||
export class ConfigGenerator<T extends Record<string, any>> {
|
||||
private currentConfig: T | null = null;
|
||||
constructor(
|
||||
private configPath: string,
|
||||
private fields: Record<string, FieldConfig>
|
||||
) {}
|
||||
) {
|
||||
if (existsSync(configPath)) {
|
||||
try {
|
||||
this.currentConfig = parse(readFileSync(configPath, 'utf8'));
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`${CONFIG_EMOJIS.INFO} Could not parse existing configuration`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async prompt(question: string, defaultValue = ''): Promise<string> {
|
||||
const rl = createInterface({
|
||||
@@ -112,6 +123,27 @@ export class ConfigGenerator<T extends Record<string, any>> {
|
||||
): Promise<any> {
|
||||
const emoji = config.emoji || CONFIG_EMOJIS.PROMPT;
|
||||
|
||||
if (currentValue !== undefined) {
|
||||
if (config.type !== 'nested' && config.type !== 'object-array') {
|
||||
console.log(
|
||||
`${CONFIG_EMOJIS.INFO} Current value: ${
|
||||
typeof currentValue === 'object'
|
||||
? JSON.stringify(currentValue)
|
||||
: currentValue
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
const keepCurrent = await this.promptYesNo(
|
||||
`${emoji} Keep current ${fieldName}?`,
|
||||
true
|
||||
);
|
||||
|
||||
if (keepCurrent) {
|
||||
return currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.type === 'nested') {
|
||||
console.log(`\n${emoji} ${config.description || fieldName}`);
|
||||
}
|
||||
@@ -151,7 +183,20 @@ export class ConfigGenerator<T extends Record<string, any>> {
|
||||
array.forEach((item: string, index: number) => {
|
||||
console.log(`${CONFIG_EMOJIS.INFO} ${index + 1}. ${item}`);
|
||||
});
|
||||
console.log('');
|
||||
|
||||
if (await this.promptYesNo(`${emoji} Remove any items?`, false)) {
|
||||
while (true) {
|
||||
const index =
|
||||
parseInt(
|
||||
await this.prompt('Enter index to remove (0 to finish):')
|
||||
) - 1;
|
||||
if (isNaN(index) || index < 0) break;
|
||||
if (index < array.length) {
|
||||
array.splice(index, 1);
|
||||
console.log('Item removed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (await this.promptYesNo(`${emoji} Add ${fieldName}?`, true)) {
|
||||
@@ -187,7 +232,7 @@ export class ConfigGenerator<T extends Record<string, any>> {
|
||||
},
|
||||
|
||||
'object-array': async () => {
|
||||
const array: ArrayItem[] = currentValue || [];
|
||||
let array: ArrayItem[] = currentValue || [];
|
||||
if (array.length > 0) {
|
||||
console.log('\nCurrent servers:');
|
||||
array.forEach((item: ArrayItem, index: number) => {
|
||||
@@ -196,19 +241,49 @@ export class ConfigGenerator<T extends Record<string, any>> {
|
||||
);
|
||||
});
|
||||
console.log('');
|
||||
const keepCurrent = await this.promptYesNo(
|
||||
`${emoji} Keep current ${fieldName}?`,
|
||||
true
|
||||
);
|
||||
if (!keepCurrent) {
|
||||
array = [];
|
||||
console.log(`${CONFIG_EMOJIS.INFO} Cleared existing servers.`);
|
||||
} else {
|
||||
if (
|
||||
await this.promptYesNo(
|
||||
`${emoji} Remove any existing servers?`,
|
||||
false
|
||||
)
|
||||
) {
|
||||
while (true) {
|
||||
const index =
|
||||
parseInt(
|
||||
await this.prompt(
|
||||
'Enter server number to remove (0 to finish):'
|
||||
)
|
||||
) - 1;
|
||||
if (isNaN(index) || index < 0) break;
|
||||
if (index < array.length) {
|
||||
console.log(
|
||||
`${CONFIG_EMOJIS.INFO} Removed server: ${array[index].name}`
|
||||
);
|
||||
array.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(await this.promptYesNo(`${emoji} Add new ${fieldName}?`, true)) &&
|
||||
config.fields
|
||||
) {
|
||||
if (await this.promptYesNo(`${emoji} Add new ${fieldName}?`, true)) {
|
||||
while (true) {
|
||||
const item = await this.handleObjectArrayItem(config.fields);
|
||||
const item = await this.handleObjectArrayItem(config.fields!);
|
||||
if (!item) break;
|
||||
array.push(item as ArrayItem);
|
||||
console.log(`${CONFIG_EMOJIS.INFO} Added new server: ${item.name}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
},
|
||||
|
||||
@@ -269,19 +344,15 @@ export class ConfigGenerator<T extends Record<string, any>> {
|
||||
}
|
||||
|
||||
async generate(): Promise<T> {
|
||||
console.log(`\n${CONFIG_EMOJIS.SETUP} Configuration Setup\n`);
|
||||
|
||||
if (existsSync(this.configPath)) {
|
||||
const reconfigure = await this.promptYesNo(
|
||||
`${CONFIG_EMOJIS.PROMPT} Configuration exists. Reconfigure?`,
|
||||
true
|
||||
);
|
||||
if (!reconfigure) process.exit(0);
|
||||
}
|
||||
|
||||
const config: Record<string, any> = {};
|
||||
for (const [fieldName, fieldConfig] of Object.entries(this.fields)) {
|
||||
config[fieldName] = await this.handleField(fieldName, fieldConfig);
|
||||
const currentValue = this.currentConfig?.[fieldName];
|
||||
config[fieldName] = await this.handleField(
|
||||
fieldName,
|
||||
fieldConfig,
|
||||
true,
|
||||
currentValue
|
||||
);
|
||||
}
|
||||
|
||||
writeFileSync(this.configPath, stringify(config));
|
||||
|
||||
@@ -22,6 +22,9 @@ export const createKeyManager = (privateKeyHex: string) => {
|
||||
content: '',
|
||||
};
|
||||
}
|
||||
getPublicKey(): string {
|
||||
return this.pubkey;
|
||||
}
|
||||
}
|
||||
|
||||
return new Manager();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@dvmcp/commons",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"description": "Shared utilities for DVMCP packages",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -4,4 +4,4 @@ node_modules
|
||||
.DS_Store
|
||||
*.test.ts
|
||||
*.test.js
|
||||
config.yml
|
||||
config.dvmcp.yml
|
||||
@@ -11,11 +11,11 @@ A MCP server implementation that aggregates tools from DVMs across the Nostr net
|
||||
|
||||
## Configuration
|
||||
|
||||
When the package is run for the first time, it will detect if the `config.yml` file exists, and if not, it will launch a configuration wizard to help you create the configuration file. You can also create your configuration file by copying `config.example.yml` and changing the values of the fields
|
||||
When the package is run for the first time, it will detect if the `config.dvmcp.yml` file exists, and if not, it will launch a configuration wizard to help you create the configuration file. You can also create your configuration file by copying `config.example.yml` and changing the values of the fields
|
||||
|
||||
```bash
|
||||
cp config.example.yml config.yml
|
||||
nano config.yml
|
||||
cp config.example.yml config.dvmcp.yml
|
||||
nano config.dvmcp.yml
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { Config } from './src/config.js';
|
||||
import { argv } from 'process';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
const configPath = join(process.cwd(), 'config.yml');
|
||||
const configPath = join(process.cwd(), 'config.dvmcp.yml');
|
||||
|
||||
const configFields: Record<string, FieldConfig> = {
|
||||
nostr: {
|
||||
|
||||
10
packages/dvmcp-discovery/config.yml
Normal file
10
packages/dvmcp-discovery/config.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
nostr:
|
||||
privateKey: c5af6a2588250d3dfe82a9cc1c054697d7483b53850d7e6552620ff8e565630c
|
||||
relayUrls:
|
||||
- wss://relay.dvmcp.fun
|
||||
mcp:
|
||||
name: DVMCP Discovery
|
||||
version: 1.0.0
|
||||
about: DVMCP Discovery Server for aggregating MCP tools from DVMs
|
||||
whitelist:
|
||||
allowedDVMs: []
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@dvmcp/discovery",
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.11",
|
||||
"description": "Discovery service for MCP tools in the Nostr DVM ecosystem",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
@@ -33,7 +33,7 @@
|
||||
"@modelcontextprotocol/sdk": "^1.5.0",
|
||||
"nostr-tools": "^2.10.4",
|
||||
"yaml": "^2.7.0",
|
||||
"@dvmcp/commons": "^0.1.1"
|
||||
"@dvmcp/commons": "^0.1.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface Config {
|
||||
};
|
||||
}
|
||||
|
||||
const CONFIG_PATH = join(process.cwd(), 'config.yml');
|
||||
const CONFIG_PATH = join(process.cwd(), 'config.dvmcp.yml');
|
||||
|
||||
const TEST_CONFIG: Config = {
|
||||
nostr: {
|
||||
@@ -79,7 +79,7 @@ function loadConfig(): Config {
|
||||
|
||||
if (!existsSync(CONFIG_PATH)) {
|
||||
throw new Error(
|
||||
'No config.yml file found. Please create one based on config.example.yml'
|
||||
'No config.dvmcp.yml file found. Please create one based on config.example.yml'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export class DiscoveryServer {
|
||||
if (!announcement?.tools) return;
|
||||
|
||||
for (const tool of announcement.tools) {
|
||||
const toolId = `${event.pubkey.slice(0, 12)}:${tool.name}`;
|
||||
const toolId = `${tool.name}:${event.pubkey.slice(0, 4)}`;
|
||||
this.toolRegistry.registerTool(toolId, tool);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -72,7 +72,7 @@ export class DiscoveryServer {
|
||||
private isAllowedDVM(pubkey: string): boolean {
|
||||
if (
|
||||
!CONFIG.whitelist?.allowedDVMs ||
|
||||
CONFIG.whitelist.allowedDVMs.size === 0
|
||||
CONFIG.whitelist.allowedDVMs.size == 0
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('DiscoveryServer E2E', () => {
|
||||
const toolIds = Array.from(toolRegistry['discoveredTools'].keys());
|
||||
console.log('Available tool IDs:', toolIds);
|
||||
|
||||
const toolId = toolIds.find((id) => id.endsWith(`:${mockTool.name}`));
|
||||
const toolId = toolIds.find((id) => id.startsWith(`${mockTool.name}`));
|
||||
expect(toolId).toBeDefined();
|
||||
console.log('Selected tool ID:', toolId);
|
||||
|
||||
|
||||
@@ -104,10 +104,18 @@ export class ToolExecutor {
|
||||
|
||||
private createToolRequest(tool: Tool, params: unknown): Event {
|
||||
const request = this.keyManager.createEventTemplate(TOOL_REQUEST_KIND);
|
||||
|
||||
const parameters =
|
||||
!tool.inputSchema.properties ||
|
||||
Object.keys(tool.inputSchema.properties).length === 0
|
||||
? {}
|
||||
: params;
|
||||
|
||||
request.content = JSON.stringify({
|
||||
name: tool.name,
|
||||
parameters: params,
|
||||
parameters,
|
||||
});
|
||||
|
||||
request.tags.push(['c', 'execute-tool']);
|
||||
return this.keyManager.signEvent(request);
|
||||
}
|
||||
|
||||
@@ -81,27 +81,34 @@ export class ToolRegistry {
|
||||
}
|
||||
|
||||
private mapJsonSchemaToZod(schema: Tool['inputSchema']): z.ZodRawShape {
|
||||
if (!schema.properties || Object.keys(schema.properties).length === 0) {
|
||||
return { _: z.object({}).optional() };
|
||||
}
|
||||
|
||||
const properties: z.ZodRawShape = {};
|
||||
if (schema.properties) {
|
||||
for (const [key, prop] of Object.entries(schema.properties)) {
|
||||
if (typeof prop === 'object' && prop && 'type' in prop) {
|
||||
switch (prop.type) {
|
||||
case 'string':
|
||||
properties[key] = z.string();
|
||||
break;
|
||||
case 'number':
|
||||
properties[key] = z.number();
|
||||
break;
|
||||
case 'integer':
|
||||
properties[key] = z.number().int();
|
||||
break;
|
||||
case 'boolean':
|
||||
properties[key] = z.boolean();
|
||||
break;
|
||||
default:
|
||||
properties[key] = z.any();
|
||||
}
|
||||
for (const [key, prop] of Object.entries(schema.properties)) {
|
||||
if (typeof prop === 'object' && prop && 'type' in prop) {
|
||||
let zodType: z.ZodType;
|
||||
switch (prop.type) {
|
||||
case 'string':
|
||||
zodType = z.string();
|
||||
break;
|
||||
case 'number':
|
||||
zodType = z.number();
|
||||
break;
|
||||
case 'integer':
|
||||
zodType = z.number().int();
|
||||
break;
|
||||
case 'boolean':
|
||||
zodType = z.boolean();
|
||||
break;
|
||||
default:
|
||||
zodType = z.any();
|
||||
}
|
||||
properties[key] =
|
||||
Array.isArray(schema.required) && schema.required.includes(key)
|
||||
? zodType
|
||||
: zodType.optional();
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
|
||||
Reference in New Issue
Block a user