Skip to content

Commit 9c502ce

Browse files
committed
chore: update
1 parent db6cff0 commit 9c502ce

9 files changed

Lines changed: 270 additions & 12 deletions

File tree

docs/issues/plugin-settings-surface-isolation/plan.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,16 @@
1313
official manifest, even if the version string is unchanged.
1414
- Preserve plugin-local `config.json` when reinstalling a stale official plugin so credentials do
1515
not disappear during self-healing.
16+
- When discovery rejects an installed official plugin as unsupported or untrusted, remove the
17+
persisted installation record and disable plugin-owned MCP servers, settings resources, and tool
18+
policies before initialization tries to reactivate them.
1619
- Keep Feishu's MCP bootstrap self-contained with only Node builtins in the installed entrypoint,
1720
and use a built-in stdio warning responder when credentials are missing.
21+
- Clear the Feishu settings page message banner on non-error MCP states so prior failures do not
22+
linger after recovery.
23+
- Pin the Feishu `npx` package invocation and only pass through an explicit registry override from
24+
the environment.
25+
- Replace Feishu's blanket MCP auto-approval with an empty default allowlist.
1826
- Resolve settings contributions from the current official manifest instead of trusting an older
1927
installed manifest copy, while still reusing installed file paths when those assets exist.
2028
- Resolve plugin settings contributions from the installed plugin manifest when stored plugin
@@ -30,6 +38,9 @@
3038
## Affected Areas
3139

3240
- `src/main/presenter/pluginPresenter/index.ts`
41+
- `plugins/feishu/settings/assets/index.js`
42+
- `plugins/feishu/mcp/serve.mjs`
43+
- `plugins/feishu/plugin.json`
3344
- `src/renderer/src/components/mcp-config/components/McpServers.vue`
3445
- Focused presenter and renderer regression tests
3546

@@ -41,8 +52,12 @@
4152
- Add a main-process regression test covering opening settings for a disabled packaged plugin.
4253
- Add a main-process regression test covering startup self-heal for stale same-version installs.
4354
- Add a main-process regression test covering dev-directory sync when only plugin files changed.
55+
- Add a main-process regression test covering cleanup when discovery rejects a persisted official
56+
plugin installation.
4457
- Add a regression assertion that the Feishu installed MCP bootstrap does not statically import host
4558
SDK packages.
59+
- Add focused Feishu regression assertions for the pinned bootstrap package version, registry
60+
override behavior, safer auto-approve defaults, and stale settings error clearing.
4661
- Add a renderer regression test covering plugin-owned MCP servers being hidden from the global MCP
4762
settings list.
4863

docs/issues/plugin-settings-surface-isolation/spec.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ instead of being mixed into the existing global settings or MCP settings surface
1818
cannot survive on version equality alone.
1919
- In development, official plugin directory installs stay synchronized with workspace files even when
2020
only non-manifest files changed.
21+
- Discovery that rejects an official plugin as unsupported or untrusted clears its persisted
22+
installation record and plugin-owned runtime resources before startup activation can reuse them.
2123
- Enabling an official plugin with a declared settings contribution exposes the plugin settings
2224
action on the Plugins settings page without depending on previously persisted resource records.
2325
- Opening plugin settings still works when persisted plugin resource records are missing or stale,
2426
as long as the installed plugin manifest still declares a valid settings contribution.
2527
- Reinstalling a stale official plugin preserves plugin-local configuration such as `config.json`.
2628
- Plugin-owned MCP entrypoints remain runnable after installation into userData and must not rely on
2729
static imports from the workspace or app-level `node_modules`.
30+
- The Feishu plugin settings page clears stale MCP error text whenever the Feishu MCP is not in an
31+
error state.
32+
- The Feishu MCP bootstrap launches a pinned upstream package version and only honors explicit
33+
registry overrides instead of injecting a hardcoded registry fallback.
34+
- The Feishu plugin manifest does not auto-approve every MCP tool call by default.
2835
- Global MCP settings do not render plugin-owned MCP servers identified by `source: plugin`.
2936
- Plugin-owned MCP runtime status remains available from plugin-specific settings/status surfaces.
3037

docs/issues/plugin-settings-surface-isolation/tasks.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
- [x] Prefer workspace official plugin directories over stale installed copies during dev discovery.
77
- [x] Reinstall stale same-version official plugins and preserve `config.json` during refresh.
88
- [x] Keep dev directory plugin installs synced even when only file contents changed.
9+
- [x] Clear persisted plugin installation state when discovery rejects unsupported or untrusted
10+
official plugins.
911
- [x] Keep Feishu installed MCP bootstrap self-contained and free of static host SDK imports.
12+
- [x] Clear stale MCP error text from the Feishu settings page after healthy status refreshes.
13+
- [x] Pin the Feishu MCP bootstrap package and remove the hardcoded registry fallback.
14+
- [x] Replace Feishu's blanket MCP auto-approve default with an empty allowlist.
1015
- [x] Hide plugin-owned MCP servers from the global MCP settings list.
1116
- [x] Add focused regression coverage for presenter and renderer behavior.
1217
- [x] Run focused validation and repo-required format/i18n/lint checks as feasible.

plugins/feishu/mcp/serve.mjs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
88
const pluginRoot = join(__dirname, '..')
99
const WARNING_TEXT =
1010
'Feishu/Lark credentials are not configured. Please open the plugin settings and set your App ID and App Secret, then restart the MCP server.'
11+
const LARK_MCP_PACKAGE = '@larksuiteoapi/lark-mcp@0.5.1'
1112

1213
function loadConfig() {
1314
const configPath = join(pluginRoot, 'config.json')
@@ -151,8 +152,20 @@ function startWarningServer() {
151152
process.stdin.resume()
152153
}
153154

155+
function resolveSpawnEnv() {
156+
const registryOverride = process.env.REGISTRY_OVERRIDE?.trim()
157+
if (!registryOverride) {
158+
return process.env
159+
}
160+
161+
return {
162+
...process.env,
163+
npm_config_registry: registryOverride
164+
}
165+
}
166+
154167
function startConfiguredServer() {
155-
const args = ['-y', '@larksuiteoapi/lark-mcp', 'mcp', '-a', appId, '-s', appSecret]
168+
const args = ['-y', LARK_MCP_PACKAGE, 'mcp', '-a', appId, '-s', appSecret]
156169
if (brand === 'lark') {
157170
args.push('--domain', 'https://open.larksuite.com')
158171
}
@@ -162,10 +175,7 @@ function startConfiguredServer() {
162175

163176
const child = spawn('npx', args, {
164177
stdio: 'inherit',
165-
env: {
166-
...process.env,
167-
npm_config_registry: process.env.npm_config_registry || 'https://registry.npmmirror.com'
168-
}
178+
env: resolveSpawnEnv()
169179
})
170180

171181
child.on('error', (error) => {

plugins/feishu/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"command": "node",
2323
"args": ["${plugin.root}/mcp/serve.mjs"],
2424
"env": {},
25-
"autoApprove": ["all"]
25+
"autoApprove": []
2626
}
2727
],
2828
"skills": [

plugins/feishu/settings/assets/index.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,22 @@ async function refreshStatus() {
4343
const mcp = status.mcpServers?.find((s) => s.serverId === 'feishu-tools')
4444
if (!mcp) {
4545
setText(mcpStateNode, 'Unavailable')
46-
} else if (mcp.lastError) {
47-
setText(mcpStateNode, 'Error')
48-
setMessage(mcp.lastError)
4946
} else if (mcp.running) {
5047
setText(mcpStateNode, 'Running')
48+
setMessage('')
5149
} else if (mcp.enabled) {
5250
setText(mcpStateNode, 'Stopped')
51+
setMessage('')
52+
} else if (mcp.lastError) {
53+
setText(mcpStateNode, 'Error')
54+
setMessage(mcp.lastError)
5355
} else {
5456
setText(mcpStateNode, 'Disabled')
57+
setMessage('')
58+
}
59+
60+
if (!mcp) {
61+
setMessage('')
5562
}
5663
}
5764

src/main/presenter/pluginPresenter/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ export class PluginPresenter {
307307
this.removeResourceRecordsByOwner(pluginId)
308308
}
309309

310+
private async removePersistedInstallation(pluginId: string): Promise<void> {
311+
await this.disableByOwner(pluginId)
312+
this.removeInstallationRecord(pluginId)
313+
this.removeRuntimeRecordsByOwner(pluginId)
314+
}
315+
310316
private async registerMcpServers(
311317
plugin: ResolvedOfficialPlugin,
312318
runtime?: PluginRuntimeStatus
@@ -753,12 +759,14 @@ export class PluginPresenter {
753759
}
754760
if (!this.isPluginPlatformSupported(plugin.manifest)) {
755761
console.info(`[PluginHost] Skipping plugin ${plugin.manifest.id}: platform not supported`)
762+
await this.removePersistedInstallation(plugin.manifest.id)
756763
continue
757764
}
758765
try {
759766
this.assertTrustedOfficialPlugin(plugin.manifest)
760767
} catch (error) {
761768
console.warn(`[PluginHost] Skipping untrusted plugin ${plugin.manifest.id}:`, error)
769+
await this.removePersistedInstallation(plugin.manifest.id)
762770
continue
763771
}
764772
console.info(`[PluginHost] Discovered plugin: ${plugin.manifest.id} at ${plugin.root}`)
@@ -1191,6 +1199,13 @@ export class PluginPresenter {
11911199
return this.getInstallations().find((installation) => installation.pluginId === pluginId)
11921200
}
11931201

1202+
private removeInstallationRecord(pluginId: string): void {
1203+
this.store.set(
1204+
'installations',
1205+
this.getInstallations().filter((installation) => installation.pluginId !== pluginId)
1206+
)
1207+
}
1208+
11941209
private upsertInstallation(record: PluginInstallationRecord): void {
11951210
this.store.set('installations', [
11961211
...this.getInstallations().filter((item) => item.pluginId !== record.pluginId),
@@ -1247,6 +1262,13 @@ export class PluginPresenter {
12471262
)
12481263
}
12491264

1265+
private removeRuntimeRecordsByOwner(pluginId: string): void {
1266+
this.store.set(
1267+
'runtimes',
1268+
(this.store.get('runtimes') ?? []).filter((runtime) => runtime.pluginId !== pluginId)
1269+
)
1270+
}
1271+
12501272
private upsertRuntimeRecord(record: RuntimeDependencyRecord): void {
12511273
this.store.set('runtimes', [
12521274
...(this.store.get('runtimes') ?? []).filter(

test/main/presenter/pluginPresenter.test.ts

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,51 @@ describe('PluginPresenter', () => {
618618
expect(presenter.__mocks.mcpPresenter.startServer).toHaveBeenCalledWith('fixture-tools')
619619
})
620620

621+
it('removes persisted plugin state when discovery rejects an installed official plugin', async () => {
622+
const fixture = await createDirectoryFixture()
623+
const workspaceManifestPath = path.join(fixture.pluginRoot, 'plugin.json')
624+
const manifest = JSON.parse(await readFile(workspaceManifestPath, 'utf8'))
625+
manifest.toolPolicies = [
626+
{
627+
serverId: 'fixture-tools',
628+
tools: {
629+
fixture_tool: 'ask'
630+
}
631+
}
632+
]
633+
await writeFile(workspaceManifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
634+
635+
const presenter = await createPluginPresenter('darwin', fixture.appPath)
636+
const { getPluginToolPolicy } = await import('@/presenter/pluginPresenter/toolPolicyStore')
637+
638+
const enabled = await presenter.enablePlugin(fixture.pluginId)
639+
expect(enabled.ok).toBe(true)
640+
expect(getPluginToolPolicy('fixture-tools', 'fixture_tool')).toBe('ask')
641+
642+
const rejectedManifest = {
643+
...manifest,
644+
engines: {
645+
...manifest.engines,
646+
platforms: ['linux']
647+
}
648+
}
649+
await writeFile(workspaceManifestPath, `${JSON.stringify(rejectedManifest, null, 2)}\n`)
650+
await writeFile(
651+
path.join(fixture.installedRoot, 'plugin.json'),
652+
`${JSON.stringify(rejectedManifest, null, 2)}\n`
653+
)
654+
655+
await presenter.initialize()
656+
657+
const servers = await presenter.__mocks.configPresenter.getMcpServers()
658+
659+
expect((presenter as any).store.get('installations')).toEqual([])
660+
expect((presenter as any).store.get('resources')).toEqual([])
661+
expect((presenter as any).store.get('runtimes')).toEqual([])
662+
expect(servers['fixture-tools']).toBeUndefined()
663+
expect(getPluginToolPolicy('fixture-tools', 'fixture_tool')).toBeNull()
664+
})
665+
621666
it('loads official packages only from resources roots in packaged mode', async () => {
622667
const cwdRoot = await mkdtemp(path.join(os.tmpdir(), 'deepchat-plugin-cwd-'))
623668
tempRoots.push(cwdRoot)
@@ -885,13 +930,27 @@ describe('PluginPresenter', () => {
885930
}
886931
})
887932

888-
it('keeps the Feishu MCP bootstrap self-contained for installed plugin copies', async () => {
933+
it('pins the Feishu MCP bootstrap package and keeps registry selection explicit', async () => {
889934
const source = await readFile('plugins/feishu/mcp/serve.mjs', 'utf8')
890935

891936
expect(source).not.toContain('@modelcontextprotocol/sdk')
892937
expect(source).toContain('Content-Length:')
893-
expect(source).toContain('npm_config_registry')
894-
expect(source).toContain('registry.npmmirror.com')
938+
expect(source).toContain('@larksuiteoapi/lark-mcp@0.5.1')
939+
expect(source).toContain('REGISTRY_OVERRIDE')
940+
expect(source).not.toContain('registry.npmmirror.com')
941+
})
942+
943+
it('uses conservative Feishu MCP defaults in the plugin manifest', async () => {
944+
const manifest = JSON.parse(await readFile('plugins/feishu/plugin.json', 'utf8'))
945+
946+
expect(manifest.mcpServers).toEqual(
947+
expect.arrayContaining([
948+
expect.objectContaining({
949+
id: 'feishu-tools',
950+
autoApprove: []
951+
})
952+
])
953+
)
895954
})
896955

897956
it('declares a Feishu plugin skill for MCP tool routing', async () => {

0 commit comments

Comments
 (0)