( serverName: string, capabilities: ServerCapabilities | undefined, pluginSource: string | undefined, )
| 189 | * this gate only decides whether the notification handler registers. |
| 190 | */ |
| 191 | export function gateChannelServer( |
| 192 | serverName: string, |
| 193 | capabilities: ServerCapabilities | undefined, |
| 194 | pluginSource: string | undefined, |
| 195 | ): ChannelGateResult { |
| 196 | // Channel servers declare `experimental['claude/channel']: {}` (MCP's |
| 197 | // presence-signal idiom — same as `tools: {}`). Truthy covers `{}` and |
| 198 | // `true`; absent/undefined/explicit-`false` all fail. Key matches the |
| 199 | // notification method namespace (notifications/claude/channel). |
| 200 | if (!capabilities?.experimental?.['claude/channel']) { |
| 201 | return { |
| 202 | action: 'skip', |
| 203 | kind: 'capability', |
| 204 | reason: 'server did not declare claude/channel capability', |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | // Overall runtime gate. After capability so normal MCP servers never hit |
| 209 | // this path. Before auth/policy so the killswitch works regardless of |
| 210 | // session state. |
| 211 | if (!isChannelsEnabled()) { |
| 212 | return { |
| 213 | action: 'skip', |
| 214 | kind: 'disabled', |
| 215 | reason: 'channels feature is not currently available', |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | // OAuth-only. API key users (console) are blocked — there's no |
| 220 | // channelsEnabled admin surface in console yet, so the policy opt-in |
| 221 | // flow doesn't exist for them. Drop this when console parity lands. |
| 222 | if (!getClaudeAIOAuthTokens()?.accessToken) { |
| 223 | return { |
| 224 | action: 'skip', |
| 225 | kind: 'auth', |
| 226 | reason: 'channels requires claude.ai authentication (run /login)', |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | // Teams/Enterprise opt-in. Managed orgs must explicitly enable channels. |
| 231 | // Default OFF — absent or false blocks. Keyed off subscription tier, not |
| 232 | // "policy settings exist" — a team org with zero configured policy keys |
| 233 | // (remote endpoint returns 404) is still a managed org and must not fall |
| 234 | // through to the unmanaged path. |
| 235 | const sub = getSubscriptionType() |
| 236 | const managed = sub === 'team' || sub === 'enterprise' |
| 237 | const policy = managed ? getSettingsForSource('policySettings') : undefined |
| 238 | if (managed && policy?.channelsEnabled !== true) { |
| 239 | return { |
| 240 | action: 'skip', |
| 241 | kind: 'policy', |
| 242 | reason: |
| 243 | 'channels not enabled by org policy (set channelsEnabled: true in managed settings)', |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | // User-level session opt-in. A server must be explicitly listed in |
| 248 | // --channels to push inbound this session — protects against a trusted |
no test coverage detected