diff --git a/src/renderer/others/setting-schema.ts b/src/renderer/others/setting-schema.ts index 10d433c7..07e6c781 100644 --- a/src/renderer/others/setting-schema.ts +++ b/src/renderer/others/setting-schema.ts @@ -511,15 +511,6 @@ const schema: SettingSchema = ({ format: 'checkbox', required: true, }, - 'mcp.enabled': { - defaultValue: false, - title: 'T_setting-panel.schema.mcp-enabled', - type: 'boolean', - group: 'other', - description: 'T_setting-panel.schema.mcp-enabled-desc', - format: 'checkbox', - required: true, - }, envs: { defaultValue: '', title: 'T_setting-panel.schema.envs', diff --git a/src/renderer/plugins.ts b/src/renderer/plugins.ts index 8980a3eb..ccae28ad 100644 --- a/src/renderer/plugins.ts +++ b/src/renderer/plugins.ts @@ -78,6 +78,7 @@ import previewFont from '@fe/plugins/preview-font' import dropToOpenFile from '@fe/plugins/drop-to-open-file' import recordRecentDocument from '@fe/plugins/record-recent-document' import aiCopilot from '@fe/plugins/ai-copilot' +import mcpServer from '@fe/plugins/mcp-server' import viewLinks from '@fe/plugins/view-links' import insertTable from '@fe/plugins/insert-table' import textComparator from '@fe/plugins/text-comparator' @@ -164,6 +165,7 @@ export default [ dropToOpenFile, recordRecentDocument, aiCopilot, + mcpServer, viewLinks, insertTable, textComparator, diff --git a/src/renderer/plugins/mcp-server.ts b/src/renderer/plugins/mcp-server.ts new file mode 100644 index 00000000..c25f8f73 --- /dev/null +++ b/src/renderer/plugins/mcp-server.ts @@ -0,0 +1,76 @@ +import type { Ctx, Plugin } from '@fe/context' + +const settingKey = 'mcp.enabled' +const copyEndpointAction = 'plugin.mcp-server.copy-endpoint' +const endpointPath = '/api/mcp/message' + +function getMcpEndpointUrl (ctx: Ctx) { + let port = '' + + try { + const url = new URL(window.location.href) + port = url.searchParams.get('port') || '' + + if (!port && /^https?:$/.test(url.protocol) && url.port && url.port !== '8066') { + port = url.port + } + } catch (error) { + // Ignore invalid URLs from non-standard runtime environments. + } + + if (!port) { + port = String(ctx.setting.getSetting('server.port', 3044)) + } + + return `http://127.0.0.1:${port}${endpointPath}` +} + +function appendEndpointLink (ctx: Ctx, editor: any) { + const field = editor.getEditor(`root.${settingKey}`) + const label = field?.label as HTMLLabelElement | undefined + if (!label || label.querySelector('.setting-mcp-endpoint')) { + return + } + + const endpointUrl = getMcpEndpointUrl(ctx) + const wrapper = document.createElement('span') + const link = document.createElement('a') + link.href = `javascript:ctx.action.getActionHandler('${copyEndpointAction}')()` + link.className = 'setting-mcp-endpoint' + wrapper.style.cssText = 'display:inline-block;font-size:13px;line-height:1.35;word-break:break-all;' + wrapper.appendChild(document.createTextNode(endpointUrl + ' ')) + link.textContent = ctx.i18n.t('copy') + link.title = ctx.i18n.t('click-to-copy') + wrapper.appendChild(link) + label.appendChild(wrapper) +} + +export default { + name: 'mcp-server', + register: (ctx) => { + ctx.action.registerAction({ + name: copyEndpointAction, + description: ctx.i18n.t('copy'), + handler: () => { + ctx.utils.copyText(getMcpEndpointUrl(ctx)) + }, + }) + + ctx.registerHook('SETTING_PANEL_BEFORE_SHOW', () => { + ctx.setting.changeSchema((schema) => { + ;(schema.properties as any)[settingKey] = { + defaultValue: false, + title: ctx.i18n.t('plugin-mcp-server.enable' as any) as any, + type: 'boolean', + group: 'other', + format: 'checkbox', + required: true, + } + }) + }) + + ctx.registerHook('SETTING_PANEL_AFTER_SHOW', ({ editor }) => { + appendEndpointLink(ctx, editor) + }) + } +} as Plugin diff --git a/src/share/i18n/languages/en.ts b/src/share/i18n/languages/en.ts index d52cc3ee..27f091ca 100644 --- a/src/share/i18n/languages/en.ts +++ b/src/share/i18n/languages/en.ts @@ -10,6 +10,7 @@ const data = { 'ok': 'OK', 'demo-tips': 'Some features are not available in DEMO mode.', 'more': 'More', + 'copy': 'Copy', 'copied': 'Copied', 'others': 'Others', 'insert-different-repo-doc': 'Documents from different repository cannot be inserted', @@ -495,10 +496,11 @@ const data = { }, 'hide-main-window-on-startup': 'Hide Main Window on Startup', 'keep-running-after-closing-window': 'Keep Running after Closing Window', - 'mcp-enabled': 'Enable MCP Server', - 'mcp-enabled-desc': 'Expose Yank Note actions through the MCP endpoint: /api/mcp/message. Clients must connect to the local server URL and use the same access permissions as the app.', } }, + 'plugin-mcp-server': { + 'enable': 'Enable MCP Server (Streamable HTTP)', + }, 'quick-open': { 'input-placeholder': 'Type characters...', 'empty': 'Empty', diff --git a/src/share/i18n/languages/ru.ts b/src/share/i18n/languages/ru.ts index 6a0b41c5..57ddb0f0 100644 --- a/src/share/i18n/languages/ru.ts +++ b/src/share/i18n/languages/ru.ts @@ -11,6 +11,7 @@ const data: BaseLanguage = { 'ok': 'OK', 'demo-tips': 'Некоторые функции недоступны в ДЕМО режиме.', 'more': 'Ещё', + 'copy': 'Копировать', 'copied': 'Скопировано', 'others': 'Другие', 'insert-different-repo-doc': 'Документы не могут быть вставлены из другого репозитория', @@ -496,10 +497,11 @@ const data: BaseLanguage = { }, 'hide-main-window-on-startup': 'Скрыть главное окно при запуске', 'keep-running-after-closing-window': 'Продолжать работу после закрытия окна', - 'mcp-enabled': 'Включить MCP-сервер', - 'mcp-enabled-desc': 'Предоставляет действия Yank Note через MCP endpoint: /api/mcp/message. Клиент должен подключаться к локальному адресу сервера и использовать те же права доступа, что и приложение.', } }, + 'plugin-mcp-server': { + 'enable': 'Включить MCP-сервер (Streamable HTTP)', + }, 'quick-open': { 'input-placeholder': 'Введите...', 'empty': 'Пусто', diff --git a/src/share/i18n/languages/zh-CN.ts b/src/share/i18n/languages/zh-CN.ts index c3c09a53..6f5fa39a 100644 --- a/src/share/i18n/languages/zh-CN.ts +++ b/src/share/i18n/languages/zh-CN.ts @@ -11,6 +11,7 @@ const data: BaseLanguage = { 'ok': '确定', 'demo-tips': 'DEMO 模式下一些功能不可用', 'more': '更多', + 'copy': '复制', 'copied': '已复制', 'others': '其他', 'insert-different-repo-doc': '不能插入不同仓库的文档', @@ -486,8 +487,6 @@ const data: BaseLanguage = { }, 'keep-running-after-closing-window': '关闭窗口后保持运行', 'hide-main-window-on-startup': '启动时隐藏主窗口', - 'mcp-enabled': '启用 MCP 服务器', - 'mcp-enabled-desc': '通过 MCP 端点暴露 Yank Note 操作:/api/mcp/message。客户端需要连接本地服务地址,并使用和应用一致的访问权限。', }, 'tabs': { 'repos': '仓库', @@ -500,6 +499,9 @@ const data: BaseLanguage = { 'other': '其他', }, }, + 'plugin-mcp-server': { + 'enable': '启用 MCP 服务器(Streamable HTTP)', + }, 'quick-open': { 'input-placeholder': '键入字符……', 'empty': '无结果', diff --git a/src/share/i18n/languages/zh-TW.ts b/src/share/i18n/languages/zh-TW.ts index 6b5f41f5..a2cdc1f6 100644 --- a/src/share/i18n/languages/zh-TW.ts +++ b/src/share/i18n/languages/zh-TW.ts @@ -11,6 +11,7 @@ const data: BaseLanguage = { 'ok': '確定', 'demo-tips': 'DEMO 模式下某些功能不可用', 'more': '更多', + 'copy': '複製', 'copied': '已複製', 'others': '其他', 'insert-different-repo-doc': '不能插入不同倉庫的文檔', @@ -486,8 +487,6 @@ const data: BaseLanguage = { }, 'keep-running-after-closing-window': '關閉視窗後保持執行', 'hide-main-window-on-startup': '啟動時隱藏主視窗', - 'mcp-enabled': '啟用 MCP 伺服器', - 'mcp-enabled-desc': '透過 MCP 端點暴露 Yank Note 操作:/api/mcp/message。客戶端需要連線本機服務位址,並使用和應用一致的存取權限。', }, 'tabs': { 'repos': '倉庫', @@ -500,6 +499,9 @@ const data: BaseLanguage = { 'other': '其他', }, }, + 'plugin-mcp-server': { + 'enable': '啟用 MCP 伺服器(Streamable HTTP)', + }, 'quick-open': { 'input-placeholder': '輸入字元……', 'empty': '無結果',