| 2112 | // silently not-matching in the gate would look like channels are |
| 2113 | // "on" but nothing ever fires. |
| 2114 | const parseChannelEntries = (raw: string[], flag: string): ChannelEntry[] => { |
| 2115 | const entries: ChannelEntry[] = []; |
| 2116 | const bad: string[] = []; |
| 2117 | for (const c of raw) { |
| 2118 | if (c.startsWith('plugin:')) { |
| 2119 | const rest = c.slice(7); |
| 2120 | const at = rest.indexOf('@'); |
| 2121 | if (at <= 0 || at === rest.length - 1) { |
| 2122 | bad.push(c); |
| 2123 | } else { |
| 2124 | entries.push({ |
| 2125 | kind: 'plugin', |
| 2126 | name: rest.slice(0, at), |
| 2127 | marketplace: rest.slice(at + 1), |
| 2128 | }); |
| 2129 | } |
| 2130 | } else if (c.startsWith('server:') && c.length > 7) { |
| 2131 | entries.push({ kind: 'server', name: c.slice(7) }); |
| 2132 | } else { |
| 2133 | bad.push(c); |
| 2134 | } |
| 2135 | } |
| 2136 | if (bad.length > 0) { |
| 2137 | process.stderr.write( |
| 2138 | chalk.red( |
| 2139 | `${flag} entries must be tagged: ${bad.join(', ')}\n` + |
| 2140 | ` plugin:<name>@<marketplace> — plugin-provided channel (allowlist enforced)\n` + |
| 2141 | ` server:<name> — manually configured MCP server\n`, |
| 2142 | ), |
| 2143 | ); |
| 2144 | process.exit(1); |
| 2145 | } |
| 2146 | return entries; |
| 2147 | }; |
| 2148 | |
| 2149 | const channelOpts = options as { |
| 2150 | channels?: string[]; |