feat: expose setting panel MCP tool

This commit is contained in:
purocean
2026-05-16 22:28:32 +08:00
parent 73ba2931b0
commit cf56eb92b2
5 changed files with 19 additions and 59 deletions
-1
View File
@@ -6,7 +6,6 @@ type Ctx = {
showSettingPanel: (key?: string) => void
getSchemaForMcp: () => Promise<any>
getSettingsForMcp: () => Promise<Record<string, any>>
setSettingForMcp: (key: string, value: any) => Promise<Record<string, any>>
},
doc: {
switchDocByPath: (path: string) => Promise<void>
+8 -9
View File
@@ -15,9 +15,9 @@ const mocks = vi.hoisted(() => ({
executeAction: vi.fn(),
},
setting: {
showSettingPanel: vi.fn(),
getSchemaForMcp: vi.fn(),
getSettingsForMcp: vi.fn(),
setSettingForMcp: vi.fn(),
},
},
},
@@ -120,9 +120,9 @@ afterEach(() => {
mocks.exportDocumentForMcp.mockReset()
mocks.jsonRPCClient.call.ctx.action.getRawActions.mockReset()
mocks.jsonRPCClient.call.ctx.action.executeAction.mockReset()
mocks.jsonRPCClient.call.ctx.setting.showSettingPanel.mockReset()
mocks.jsonRPCClient.call.ctx.setting.getSchemaForMcp.mockReset()
mocks.jsonRPCClient.call.ctx.setting.getSettingsForMcp.mockReset()
mocks.jsonRPCClient.call.ctx.setting.setSettingForMcp.mockReset()
})
describe('MCP server request handling', () => {
@@ -163,7 +163,7 @@ describe('MCP server request handling', () => {
'yn_export_document',
'yn_get_config_schema',
'yn_get_all_configs',
'yn_set_config',
'yn_show_setting_panel',
])
const callTool = server.handlers.get(CallToolRequestSchema)
@@ -218,7 +218,7 @@ describe('MCP server request handling', () => {
expect(JSON.parse(exported.content[0].text)).toEqual({ success: true, result: { base64: 'ZGF0YQ==' } })
})
it('gets config schema, fetches all configs, and sets a config with refresh', async () => {
it('gets config schema, fetches all configs, and opens the setting panel', async () => {
const { server } = await initEnabledServer()
const callTool = server.handlers.get(CallToolRequestSchema)
@@ -253,13 +253,12 @@ describe('MCP server request handling', () => {
result: { theme: 'dark', readonly: true },
})
mocks.jsonRPCClient.call.ctx.setting.setSettingForMcp.mockResolvedValue({ theme: 'light' })
const setConfig = await callTool({ params: { name: 'yn_set_config', arguments: { key: 'theme', value: 'light' } } })
expect(JSON.parse(setConfig.content[0].text)).toEqual({
const openPanel = await callTool({ params: { name: 'yn_show_setting_panel', arguments: { key: 'theme' } } })
expect(JSON.parse(openPanel.content[0].text)).toEqual({
success: true,
result: { key: 'theme', value: 'light', refreshed: true },
result: { key: 'theme' },
})
expect(mocks.jsonRPCClient.call.ctx.setting.setSettingForMcp).toHaveBeenCalledWith('theme', 'light')
expect(mocks.jsonRPCClient.call.ctx.setting.showSettingPanel).toHaveBeenCalledWith('theme')
mocks.jsonRPCClient.call.ctx.setting.getSchemaForMcp.mockResolvedValue({ properties: {} })
const missing = await callTool({ params: { name: 'yn_get_config_schema', arguments: { key: 'missing' } } })
+9 -15
View File
@@ -61,13 +61,11 @@ async function getAllConfigs () {
return await jsonRPCClient.call.ctx.setting.getSettingsForMcp()
}
async function setConfig (key: string, value: any) {
const settings = await jsonRPCClient.call.ctx.setting.setSettingForMcp(key, value)
async function showSettingPanel (key?: string) {
await jsonRPCClient.call.ctx.setting.showSettingPanel(key)
return {
key,
value: settings?.[key],
refreshed: true,
key: key || null,
}
}
@@ -446,20 +444,16 @@ function createMCPServer (): Server {
},
},
{
name: 'yn_set_config',
description: 'Set one Yank Note config value by key, persist it, and refresh frontend settings/hooks immediately.',
name: 'yn_show_setting_panel',
description: 'Open the Yank Note setting panel, optionally locating a specific config key or setting group.',
inputSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Config key to update.',
},
value: {
description: 'New config value. Match the type defined in yn_get_config_schema.',
description: 'Optional config key or setting group to locate.',
},
},
required: ['key', 'value'],
additionalProperties: false,
},
},
@@ -647,11 +641,11 @@ function createMCPServer (): Server {
}
}
if (name === 'yn_set_config') {
const { key, value } = (args || {}) as any
if (name === 'yn_show_setting_panel') {
const { key } = (args || {}) as any
try {
const result = await setConfig(key, value)
const result = await showSettingPanel(key)
return {
content: [
+1 -18
View File
@@ -199,7 +199,7 @@ test('gets and sets individual settings without exposing mutable local state', a
expect(mocks.writtenSettings).toStrictEqual({ readonly: true })
})
test('provides MCP helpers for schema, refresh, and setting updates', async () => {
test('provides MCP helpers for schema and refreshed settings', async () => {
const setting = await importSetting({ readonly: false })
mocks.fetchedSettings = { readonly: true }
@@ -215,23 +215,6 @@ test('provides MCP helpers for schema, refresh, and setting updates', async () =
const settings = await setting.getSettingsForMcp()
expect(settings.readonly).toBe(true)
expect(mocks.hooks[0]).toMatchObject({ name: 'SETTING_FETCHED' })
mocks.hooks = []
await setting.setSettingForMcp('readonly', false)
expect(mocks.hooks[0]).toStrictEqual({
name: 'SETTING_PANEL_BEFORE_SHOW',
payload: {},
options: { breakable: true },
})
expect(mocks.writtenSettings).toStrictEqual({ readonly: false })
mocks.hooks = []
mocks.fetchedSettings = { readonly: true }
const themeSettings = await setting.setSettingForMcp('theme', 'light')
expect(mocks.setTheme).toHaveBeenCalledWith('light')
expect(themeSettings.readonly).toBe(true)
await expect(setting.setSettingForMcp('missing', true)).rejects.toThrow('Unknown setting key: missing')
})
test('shows, locates, and hides setting panel', async () => {
+1 -16
View File
@@ -6,7 +6,7 @@ import { basename } from '@fe/utils/path'
import { sleep } from '@fe/utils'
import type { BuildInSettings, FileItem, PathItem, SettingGroup, SettingSchema } from '@fe/types'
import { getDefaultSettingSchema } from '@fe/others/setting-schema'
import { getThemeName, setTheme } from './theme'
import { getThemeName } from './theme'
import { t } from './i18n'
type Schema = SettingSchema
@@ -188,21 +188,6 @@ export async function setSetting<T extends keyof BuildInSettings> (key: T, val:
await writeSettings({ [key]: val })
}
export async function setSettingForMcp (key: string, val: any) {
await prepareSchemaForMcp()
if (!Object.prototype.hasOwnProperty.call(schema.properties, key)) {
throw new Error(`Unknown setting key: ${key}`)
}
if (key === 'theme') {
setTheme(val)
return await fetchSettings()
}
return await writeSettings({ [key]: val })
}
/**
* Show setting panel.
* @param keyOrGroup