mirror of
https://github.com/purocean/yn.git
synced 2026-10-16 15:41:46 +08:00
feat: expose setting panel MCP tool
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
@@ -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: [
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user