Mods: a mod's own $.mcp.call passes auto mode in a terminal but is refused in the Desktop app, on the same build
Versions: Claude Code 2.1.295 (the build bundled with the Desktop app) and 2.1.296 (CLI), macOS, direct Anthropic API connection.
What the mod does
A plugin ships an HTTP MCP server in .mcp.json and a mod. After each turn the mod calls one tool on that server with $.mcp.call, from a $.clock.after callback started in its turn.complete hook. It is a background network call. Nobody asks Claude for it.
What happens, all in auto mode
- Interactive terminal session, 2.1.296 and 2.1.295: the call runs. The debug log says
Skipping auto mode classifier for mcp__plugin_myplugin_api__refresh: called by plugin myplugin. claude -p --input-format stream-json --output-format stream-json --permission-mode auto --permission-prompt-tool stdioon 2.1.295: the call runs, with the same debug line.- Desktop app, Code tab, 2.1.295: the call is refused:
> $.mcp.call(plugin:myplugin:api, refresh) refused: The server-side auto mode classifier gave no verdict for mcp__plugin_myplugin_api__refresh: the request that produced this action did not ask for one.
Expected
The same decision on every surface. The mods API declaration of $.mcp.call says "No permission prompt: the plugin's call, seen by the hooks above it, is the grant", which is how the terminal behaves.
What the mod cannot do about it
The docs say a mod that handles tool.check can approve an action before the classifier. That is not available for a mod's own call: its tool.check hook is skipped (tool.check skipped: re-entry (the plugin's own code raised it; origin myplugin)) and the hook's .catch handler is not asked either.
Shape of the mod
I saw the refusal with a larger plugin of this shape, not with this exact file.
import type { Register } from 'claude-code'
export const register: Register = on => {
on('turn.complete', async ($, e, next) => {
const result = await next(e)
if (e.agentId === undefined) {
$.clock.after(0, async () => {
try {
const connection = await $.mcp.connect('api')
if (connection.isConnected) await $.mcp.call(connection.server, 'refresh', {})
} catch (error) {
$.ui.log(String(error))
}
})
}
return result
})
}
Related: #100575, #99214, #100633 report the same refusal. None of them notes that the terminal exempts the call.