diff --git a/src/plugins/bundled/index.ts b/src/plugins/bundled/index.ts index 85dda6519..ab8e2f77d 100644 --- a/src/plugins/bundled/index.ts +++ b/src/plugins/bundled/index.ts @@ -14,10 +14,11 @@ * 2. Call registerBuiltinPlugin() with the plugin definition here */ +import { registerWeixinBuiltinPlugin } from './weixin.js' + /** * Initialize built-in plugins. Called during CLI startup. */ export function initBuiltinPlugins(): void { - // No built-in plugins registered yet — this is the scaffolding for - // migrating bundled skills that should be user-toggleable. + registerWeixinBuiltinPlugin() } diff --git a/src/plugins/bundled/weixin.ts b/src/plugins/bundled/weixin.ts new file mode 100644 index 000000000..2a37b86ab --- /dev/null +++ b/src/plugins/bundled/weixin.ts @@ -0,0 +1,21 @@ +import { registerBuiltinPlugin } from '../builtinPlugins.js' +import { buildCliLaunch } from '../../utils/cliLaunch.js' + +export function registerWeixinBuiltinPlugin(): void { + const launch = buildCliLaunch(['weixin', 'serve']) + + registerBuiltinPlugin({ + name: 'weixin', + description: + 'WeChat channel integration. Enables inbound WeChat messages via channels and provides reply/send_typing MCP tools. Configure with `ccb weixin login` and enable for a session with `--channels plugin:weixin@builtin`.', + version: MACRO.VERSION, + defaultEnabled: true, + mcpServers: { + weixin: { + type: 'stdio', + command: launch.execPath, + args: launch.args, + }, + }, + }) +} diff --git a/src/services/mcp/__tests__/channelAllowlist.test.ts b/src/services/mcp/__tests__/channelAllowlist.test.ts new file mode 100644 index 000000000..ed2907204 --- /dev/null +++ b/src/services/mcp/__tests__/channelAllowlist.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, mock, test } from 'bun:test' + +mock.module('../../analytics/growthbook.js', () => ({ + getFeatureValue_CACHED_MAY_BE_STALE: () => [], +})) + +import { isChannelAllowlisted } from '../channelAllowlist.js' + +describe('isChannelAllowlisted', () => { + test('allows builtin weixin plugin', () => { + expect(isChannelAllowlisted('weixin@builtin')).toBe(true) + }) + + test('rejects undefined plugin source', () => { + expect(isChannelAllowlisted(undefined)).toBe(false) + }) +}) diff --git a/src/services/mcp/channelAllowlist.ts b/src/services/mcp/channelAllowlist.ts index 11a48a519..9a5e25f92 100644 --- a/src/services/mcp/channelAllowlist.ts +++ b/src/services/mcp/channelAllowlist.ts @@ -16,6 +16,7 @@ */ import { z } from 'zod/v4' +import { BUILTIN_MARKETPLACE_NAME } from '../../plugins/builtinPlugins.js' import { lazySchema } from '../../utils/lazySchema.js' import { parsePluginIdentifier } from '../../utils/plugins/pluginIdentifier.js' import { getFeatureValue_CACHED_MAY_BE_STALE } from '../analytics/growthbook.js' @@ -68,6 +69,9 @@ export function isChannelAllowlisted( if (!pluginSource) return false const { name, marketplace } = parsePluginIdentifier(pluginSource) if (!marketplace) return false + if (marketplace === BUILTIN_MARKETPLACE_NAME && name === 'weixin') { + return true + } return getChannelAllowlist().some( e => e.plugin === name && e.marketplace === marketplace, )