diff --git a/package.json b/package.json index c36b0e36ff..6ddbaccc64 100644 --- a/package.json +++ b/package.json @@ -257,7 +257,8 @@ "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", "word-extractor": "^1.0.4", - "zipread": "^1.3.3" + "zipread": "^1.3.3", + "zod": "^3.25.74" }, "optionalDependencies": { "@cherrystudio/mac-system-ocr": "^0.2.2" diff --git a/src/main/mcpServers/dify-knowledge.ts b/src/main/mcpServers/dify-knowledge.ts index db62f6cddd..e3f8c84b26 100644 --- a/src/main/mcpServers/dify-knowledge.ts +++ b/src/main/mcpServers/dify-knowledge.ts @@ -1,9 +1,8 @@ // inspired by https://dify.ai/blog/turn-your-dify-app-into-an-mcp-server import { loggerService } from '@logger' import { Server } from '@modelcontextprotocol/sdk/server/index.js' -import { CallToolRequestSchema, ListToolsRequestSchema, ToolSchema } from '@modelcontextprotocol/sdk/types.js' -import { z } from 'zod' -import { zodToJsonSchema } from 'zod-to-json-schema' +import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js' +import * as z from 'zod/v4' const logger = loggerService.withContext('DifyKnowledgeServer') @@ -39,10 +38,6 @@ interface DifySearchKnowledgeResponse { }> } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const ToolInputSchema = ToolSchema.shape.inputSchema -type ToolInput = z.infer - const SearchKnowledgeArgsSchema = z.object({ id: z.string().describe('Knowledge ID'), query: z.string().describe('Query string'), @@ -96,7 +91,7 @@ class DifyKnowledgeServer { { name: 'search_knowledge', description: 'Search knowledge by id and query', - inputSchema: zodToJsonSchema(SearchKnowledgeArgsSchema) as ToolInput + inputSchema: SearchKnowledgeArgsSchema } ] } diff --git a/src/main/mcpServers/filesystem.ts b/src/main/mcpServers/filesystem.ts index ff72973c29..6858b8d820 100644 --- a/src/main/mcpServers/filesystem.ts +++ b/src/main/mcpServers/filesystem.ts @@ -2,14 +2,13 @@ import { loggerService } from '@logger' import { Server } from '@modelcontextprotocol/sdk/server/index.js' -import { CallToolRequestSchema, ListToolsRequestSchema, ToolSchema } from '@modelcontextprotocol/sdk/types.js' +import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js' import { createTwoFilesPatch } from 'diff' import fs from 'fs/promises' import { minimatch } from 'minimatch' import os from 'os' import path from 'path' -import { z } from 'zod' -import { zodToJsonSchema } from 'zod-to-json-schema' +import * as z from 'zod/v4' const logger = loggerService.withContext('MCP:FileSystemServer') @@ -120,10 +119,6 @@ const GetFileInfoArgsSchema = z.object({ path: z.string() }) -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const ToolInputSchema = ToolSchema.shape.inputSchema -type ToolInput = z.infer - interface FileInfo { size: number created: Date @@ -345,7 +340,7 @@ class FileSystemServer { 'Handles various text encodings and provides detailed error messages ' + 'if the file cannot be read. Use this tool when you need to examine ' + 'the contents of a single file. Only works within allowed directories.', - inputSchema: zodToJsonSchema(ReadFileArgsSchema) as ToolInput + inputSchema: ReadFileArgsSchema }, { name: 'read_multiple_files', @@ -355,7 +350,7 @@ class FileSystemServer { "or compare multiple files. Each file's content is returned with its " + "path as a reference. Failed reads for individual files won't stop " + 'the entire operation. Only works within allowed directories.', - inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema) as ToolInput + inputSchema: ReadMultipleFilesArgsSchema }, { name: 'write_file', @@ -363,7 +358,7 @@ class FileSystemServer { 'Create a new file or completely overwrite an existing file with new content. ' + 'Use with caution as it will overwrite existing files without warning. ' + 'Handles text content with proper encoding. Only works within allowed directories.', - inputSchema: zodToJsonSchema(WriteFileArgsSchema) as ToolInput + inputSchema: WriteFileArgsSchema }, { name: 'edit_file', @@ -371,7 +366,7 @@ class FileSystemServer { 'Make line-based edits to a text file. Each edit replaces exact line sequences ' + 'with new content. Returns a git-style diff showing the changes made. ' + 'Only works within allowed directories.', - inputSchema: zodToJsonSchema(EditFileArgsSchema) as ToolInput + inputSchema: EditFileArgsSchema }, { name: 'create_directory', @@ -380,7 +375,7 @@ class FileSystemServer { 'nested directories in one operation. If the directory already exists, ' + 'this operation will succeed silently. Perfect for setting up directory ' + 'structures for projects or ensuring required paths exist. Only works within allowed directories.', - inputSchema: zodToJsonSchema(CreateDirectoryArgsSchema) as ToolInput + inputSchema: CreateDirectoryArgsSchema }, { name: 'list_directory', @@ -389,7 +384,7 @@ class FileSystemServer { 'Results clearly distinguish between files and directories with [FILE] and [DIR] ' + 'prefixes. This tool is essential for understanding directory structure and ' + 'finding specific files within a directory. Only works within allowed directories.', - inputSchema: zodToJsonSchema(ListDirectoryArgsSchema) as ToolInput + inputSchema: ListDirectoryArgsSchema }, { name: 'directory_tree', @@ -398,7 +393,7 @@ class FileSystemServer { "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. " + 'Files have no children array, while directories always have a children array (which may be empty). ' + 'The output is formatted with 2-space indentation for readability. Only works within allowed directories.', - inputSchema: zodToJsonSchema(DirectoryTreeArgsSchema) as ToolInput + inputSchema: DirectoryTreeArgsSchema }, { name: 'move_file', @@ -407,7 +402,7 @@ class FileSystemServer { 'and rename them in a single operation. If the destination exists, the ' + 'operation will fail. Works across different directories and can be used ' + 'for simple renaming within the same directory. Both source and destination must be within allowed directories.', - inputSchema: zodToJsonSchema(MoveFileArgsSchema) as ToolInput + inputSchema: MoveFileArgsSchema }, { name: 'search_files', @@ -417,7 +412,7 @@ class FileSystemServer { 'is case-insensitive and matches partial names. Returns full paths to all ' + "matching items. Great for finding files when you don't know their exact location. " + 'Only searches within allowed directories.', - inputSchema: zodToJsonSchema(SearchFilesArgsSchema) as ToolInput + inputSchema: SearchFilesArgsSchema }, { name: 'get_file_info', @@ -426,7 +421,7 @@ class FileSystemServer { 'information including size, creation time, last modified time, permissions, ' + 'and type. This tool is perfect for understanding file characteristics ' + 'without reading the actual content. Only works within allowed directories.', - inputSchema: zodToJsonSchema(GetFileInfoArgsSchema) as ToolInput + inputSchema: GetFileInfoArgsSchema }, { name: 'list_allowed_directories', diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 94869ef5f7..38d62cd6d3 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -289,12 +289,6 @@ "topics.unpinned": "Αποστέλλω", "translate": "Μετάφραση" }, - "html_artifacts": { - "code": "Κώδικας", - "generating": "Δημιουργία", - "preview": "Προεπισκόπηση", - "split": "Διαχωρισμός" - }, "code_block": { "collapse": "συμπεριληφθείς", "disable_wrap": "ακύρωση αλλαγής γραμμής", @@ -428,6 +422,12 @@ "search.topics.empty": "Δεν βρέθηκαν σχετικά θέματα, πατήστε Enter για να αναζητήσετε όλα τα μηνύματα", "title": "Αναζήτηση θεμάτων" }, + "html_artifacts": { + "code": "Κώδικας", + "generating": "Δημιουργία", + "preview": "Προεπισκόπηση", + "split": "Διαχωρισμός" + }, "knowledge": { "add": { "title": "Προσθήκη βιβλιοθήκης γνώσεων" @@ -645,9 +645,9 @@ "close": "Κλείσιμο της εφαρμογής", "devtools": "Εργαλεία προγραμματιστή", "minimize": "Ελαχιστοποίηση της εφαρμογής", + "openExternal": "Άνοιγμα στον περιηγητή", "open_link_external_off": "Τρέχον: Άνοιγμα συνδέσμου χρησιμοποιώντας το προεπιλεγμένο παράθυρο", "open_link_external_on": "Τρέχον: Άνοιγμα συνδέσμου στον περιηγητή", - "openExternal": "Άνοιγμα στον περιηγητή", "refresh": "Ανανέωση", "rightclick_copyurl": "Αντιγραφή URL με δεξί κλικ" }, @@ -905,9 +905,9 @@ }, "settings": { "about": "Περί μας", - "about.checkingUpdate": "Ελέγχω ενημερώσεις...", "about.checkUpdate": "Έλεγχος ενημερώσεων", "about.checkUpdate.available": "Άμεση ενημέρωση", + "about.checkingUpdate": "Ελέγχω ενημερώσεις...", "about.contact.button": "Ταχυδρομείο", "about.contact.title": "Επικοινωνία μέσω ταχυδρομείου", "about.description": "Ένα AI ασιστάντα που έχει σχεδιαστεί για δημιουργούς", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index a14d35a891..1fd8428c78 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -290,12 +290,6 @@ "topics.unpinned": "Quitar fijación", "translate": "Traducir" }, - "html_artifacts": { - "code": "Código", - "generating": "Generando", - "preview": "Vista previa", - "split": "Dividir" - }, "code_block": { "collapse": "Replegar", "disable_wrap": "Deshabilitar salto de línea", @@ -429,6 +423,12 @@ "search.topics.empty": "No se encontraron temas relacionados, presione Enter para buscar todos los mensajes", "title": "Búsqueda de temas" }, + "html_artifacts": { + "code": "Código", + "generating": "Generando", + "preview": "Vista previa", + "split": "Dividir" + }, "knowledge": { "add": { "title": "Agregar base de conocimientos" @@ -646,9 +646,9 @@ "close": "Cerrar la aplicación", "devtools": "Herramientas de desarrollo", "minimize": "Minimizar la aplicación", + "openExternal": "Abrir en el navegador", "open_link_external_off": "Actual: Abrir enlaces en ventana predeterminada", "open_link_external_on": "Actual: Abrir enlaces en el navegador", - "openExternal": "Abrir en el navegador", "refresh": "Actualizar", "rightclick_copyurl": "Copiar URL con clic derecho" }, @@ -906,9 +906,9 @@ }, "settings": { "about": "Acerca de nosotros", - "about.checkingUpdate": "Verificando actualizaciones...", "about.checkUpdate": "Comprobar actualizaciones", "about.checkUpdate.available": "Actualizar ahora", + "about.checkingUpdate": "Verificando actualizaciones...", "about.contact.button": "Correo electrónico", "about.contact.title": "Contacto por correo electrónico", "about.description": "Una asistente de IA creada para los creadores", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 1dcd5731f5..4e2c2a6095 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -289,12 +289,6 @@ "topics.unpinned": "Annuler le fixage", "translate": "Traduire" }, - "html_artifacts": { - "code": "Code", - "generating": "Génération", - "preview": "Aperçu", - "split": "Diviser" - }, "code_block": { "collapse": "Réduire", "disable_wrap": "Désactiver le retour à la ligne", @@ -428,6 +422,12 @@ "search.topics.empty": "Aucun sujet correspondant trouvé, appuyez sur Entrée pour rechercher tous les messages", "title": "Recherche de sujets" }, + "html_artifacts": { + "code": "Code", + "generating": "Génération", + "preview": "Aperçu", + "split": "Diviser" + }, "knowledge": { "add": { "title": "Ajouter une base de connaissances" @@ -645,9 +645,9 @@ "close": "Закрыть мини-программу", "devtools": "Инструменты разработчика", "minimize": "Свернуть мини-программу", + "openExternal": "Открыть в браузере", "open_link_external_off": "Текущий: открывать ссылки в окне по умолчанию", "open_link_external_on": "Текущий: открывать ссылки в браузере", - "openExternal": "Открыть в браузере", "refresh": "Обновить", "rightclick_copyurl": "Скопировать URL через правую кнопку мыши" }, @@ -905,9 +905,9 @@ }, "settings": { "about": "À propos de nous", - "about.checkingUpdate": "Vérification des mises à jour en cours...", "about.checkUpdate": "Vérifier les mises à jour", "about.checkUpdate.available": "Mettre à jour maintenant", + "about.checkingUpdate": "Vérification des mises à jour en cours...", "about.contact.button": "Courriel", "about.contact.title": "Contactez-nous par courriel", "about.description": "Un assistant IA conçu pour les créateurs", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index f062782dd8..9a89762c2d 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -291,12 +291,6 @@ "topics.unpinned": "Desfixar", "translate": "Traduzir" }, - "html_artifacts": { - "code": "Código", - "generating": "Gerando", - "preview": "Visualizar", - "split": "Dividir" - }, "code_block": { "collapse": "Recolher", "disable_wrap": "Desativar quebra de linha", @@ -430,6 +424,12 @@ "search.topics.empty": "Nenhum tópico relacionado encontrado, clique em Enter para procurar todas as mensagens", "title": "Procurar Tópicos" }, + "html_artifacts": { + "code": "Código", + "generating": "Gerando", + "preview": "Visualizar", + "split": "Dividir" + }, "knowledge": { "add": { "title": "Adicionar Base de Conhecimento" @@ -647,9 +647,9 @@ "close": "Fechar aplicativo", "devtools": "Ferramentas de Desenvolvedor", "minimize": "Minimizar aplicativo", + "openExternal": "Abrir no navegador", "open_link_external_off": "Atual: Abrir links em janela padrão", "open_link_external_on": "Atual: Abrir links no navegador", - "openExternal": "Abrir no navegador", "refresh": "Atualizar", "rightclick_copyurl": "Copiar URL com botão direito" }, @@ -906,9 +906,9 @@ }, "settings": { "about": "Sobre Nós", - "about.checkingUpdate": "Verificando atualizações...", "about.checkUpdate": "Verificar atualizações", "about.checkUpdate.available": "Atualizar agora", + "about.checkingUpdate": "Verificando atualizações...", "about.contact.button": "E-mail", "about.contact.title": "Contato por e-mail", "about.description": "Um assistente de IA criado para criadores", diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 77305c74cc..8b45d67f78 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -2,6 +2,7 @@ import type { WebSearchResultBlock } from '@anthropic-ai/sdk/resources' import type { GenerateImagesConfig, GroundingMetadata, PersonGeneration } from '@google/genai' import type OpenAI from 'openai' import type { CSSProperties } from 'react' +import * as z from 'zod/v4' export * from './file' import type { FileMetadata } from './file' @@ -657,6 +658,12 @@ export interface MCPToolInputSchema { properties: Record } +export const MCPToolOutputSchema = z.object({ + type: z.literal('object'), + properties: z.record(z.string(), z.unknown()), + required: z.array(z.string()) +}) + export interface MCPTool { id: string serverId: string @@ -664,6 +671,7 @@ export interface MCPTool { name: string description?: string inputSchema: MCPToolInputSchema + outputSchema?: z.infer } export interface MCPPromptArguments { diff --git a/yarn.lock b/yarn.lock index 37b7bd3e15..e8f72b5515 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7307,6 +7307,7 @@ __metadata: winston-daily-rotate-file: "npm:^5.0.0" word-extractor: "npm:^1.0.4" zipread: "npm:^1.3.3" + zod: "npm:^3.25.74" dependenciesMeta: "@cherrystudio/mac-system-ocr": optional: true @@ -21068,6 +21069,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.25.74": + version: 3.25.74 + resolution: "zod@npm:3.25.74" + checksum: 10c0/59e38b046ac333b5bd1ba325a83b6798721227cbfb1e69dfc7159bd7824b904241ab923026edb714fafefec3624265ae374a70aee9a5a45b365bd31781ffa105 + languageName: node + linkType: hard + "zustand@npm:^4.4.0": version: 4.5.6 resolution: "zustand@npm:4.5.6"