Summary
@aauth/mcp-openclaw does not load against any current OpenClaw runtime. Three blocking issues at discovery + activation time, plus a handful of contract mismatches once those are fixed. The protocol-layer packages (@aauth/local-keys, @aauth/mcp-agent) all work — verified end-to-end against whoami.aauth.dev with full three-party flow returning identity claims via person.hello-beta.net. This issue is purely about the OpenClaw integration.
Environment
- OpenClaw: latest published (
/opt/homebrew/lib/node_modules/openclaw), Node 22.22.0, macOS arm64
- Plugin:
@aauth/mcp-openclaw v0.8.1 installed at ~/.openclaw/extensions/aauth-mcp/
- Config:
"plugins": {
"entries": {
"aauth-mcp": {
"enabled": true,
"config": {
"agent_url": "https://clawdrey.com",
"delegate": "openclaw",
"mcp_servers": {}
}
}
}
}
- Gateway log:
Config warnings:
- plugins.entries.aauth-mcp: plugin not found: aauth-mcp (stale config entry ignored; remove it from plugins config)
- plugins.allow: plugin not found: aauth-mcp
Blocking issue 1 — package.json missing the openclaw discovery key
OpenClaw discovers installed (non-bundled) plugins by reading package.json.openclaw.extensions (and optionally runtimeExtensions, setupEntry, runtimeSetupEntry). Without that key the plugin is invisible to OpenClaw and plugins.entries.<id> references produce the "plugin not found" warning shown above.
Reference: Plugin entry points.
Fix. Add to mcp-openclaw/package.json:
{
"openclaw": {
"extensions": ["./src/index.ts"],
"runtimeExtensions": ["./dist/index.js"]
}
}
runtimeExtensions is preferred for installed packages so they don't have to ship TypeScript or run tsc at load.
Blocking issue 2 — openclaw.plugin.json declares an unsupported entry field
OpenClaw's plugin manifest is metadata-only by design. It is read before any plugin code is loaded so config validation can run without booting plugin runtime. entry is not part of the manifest schema and is silently ignored.
Reference: Plugin manifest.
Fix. Drop entry from mcp-openclaw/openclaw.plugin.json. Code entry belongs in package.json.openclaw.*.
Blocking issue 3 — Plugin uses a non-existent OpenClaw plugin API
src/index.ts calls:
api.getConfig() // not on OpenClawPluginApi
api.registerTool(name, handler) // wrong signature (takes one object)
api.onShutdown(fn) // does not exist
Real surface (selected fields from openclaw/dist/plugin-sdk/src/plugins/types.d.ts):
export type OpenClawPluginApi = {
id: string
pluginConfig?: Record<string, unknown> // not getConfig()
logger: PluginLogger
registerTool: (tool: AnyAgentTool | OpenClawPluginToolFactory, opts?) => void
registerService: (service: { id: string; start(): Promise<void>|void; stop(): Promise<void>|void }) => void
registerHook: (events, handler, opts?) => void
registerHttpRoute: (...) => void
registerCommand: (...) => void
registerCli: (...) => void
// ~40 more
}
A registered tool is an object:
api.registerTool({
name: 'my_files_read_file',
description: 'Read a file via the my-files MCP server',
parameters: {
type: 'object',
required: ['path'],
properties: { path: { type: 'string' } },
},
async execute(_toolCallId, params) {
return manager.callTool('my-files_read_file', params)
},
})
There is no onShutdown hook on the API. The canonical lifecycle pattern is registerService({ id, start, stop }) and the service's stop() is what should call manager.shutdown().
Fix. Use definePluginEntry from openclaw/plugin-sdk/plugin-entry. Register an aauth-mcp service that does connectAll() + per-tool registerTool({...}) in start(), and manager.shutdown() in stop().
Contract issues once it loads
These don't block discovery but are worth fixing in the same pass:
register() is async-leaky. manager.connectAll().then(() => api.registerTool(...)) registers tools after register() returns. Tools registered post-return may not appear in OpenClaw's static catalog snapshot used for setup-only loading. Move dynamic registration into registerService.start(), or await from an async register().
- No logger usage.
api.logger exists; right now the plugin runs silently. Triage is hard when something goes wrong.
agent_url is required but could self-discover. When @aauth/local-keys has exactly one configured agent, default to it; require the field only when there's ambiguity.
delegate field is unused noise. README example sets delegate: "openclaw" and code defaults to "openclaw". Remove from public config until there's a reason.
- Tool prefixing collides.
<server>_<tool> collides with any MCP tool that already has an underscore (most of them). Prefer either <server>__<tool> (rare collision) or register tools under their original MCP names with an internal (toolName → server) index in ServerManager.
Adoption suggestions (out of scope for this PR, filing separately if useful)
setup-entry.ts so openclaw onboard can run an interactive AAuth bootstrap.
registerAutoEnableProbe so installing the plugin + running aauth-bootstrap lights up automatically.
- Working-directory plumbing into
callTool so AAuth-MCP servers behave like local ones from the agent's POV.
Reproduction
npm install -g @aauth/mcp-openclaw (or copy mcp-openclaw/ into ~/.openclaw/extensions/aauth-mcp/)
- Add
plugins.entries.aauth-mcp to ~/.openclaw/openclaw.json per README
openclaw gateway restart
tail /tmp/openclaw/openclaw-*.log → see plugin not found: aauth-mcp
What worked
For completeness, these all worked end-to-end on the first real attempt, separate from the OpenClaw plugin:
- Secure Enclave keypair generation via
@aauth/local-keys on Apple Silicon
aauth-bootstrap against https://person.hello-beta.net (one user-consent round-trip)
GET https://whoami.aauth.dev/ (mode 1, agent-token only) → 200 with {sub, ps}
GET https://whoami.aauth.dev/?scope=name+email (mode 3, three-party) → 401 challenge → resource_token → PS /aauth/token exchange → consent → auth_token → 200 with full identity claims
onInteraction callback firing with the right URL+code
The protocol layer is in really good shape. This issue is just about the OpenClaw plugin entry not matching the OpenClaw plugin API.
Will follow up with a PR that fixes the three blocking issues + the async-leaky register(). Happy to take direction on the contract-issue items if you want them in the same PR or split out.
Summary
@aauth/mcp-openclawdoes not load against any current OpenClaw runtime. Three blocking issues at discovery + activation time, plus a handful of contract mismatches once those are fixed. The protocol-layer packages (@aauth/local-keys,@aauth/mcp-agent) all work — verified end-to-end againstwhoami.aauth.devwith full three-party flow returning identity claims viaperson.hello-beta.net. This issue is purely about the OpenClaw integration.Environment
/opt/homebrew/lib/node_modules/openclaw), Node 22.22.0, macOS arm64@aauth/mcp-openclawv0.8.1 installed at~/.openclaw/extensions/aauth-mcp/Blocking issue 1 —
package.jsonmissing theopenclawdiscovery keyOpenClaw discovers installed (non-bundled) plugins by reading
package.json.openclaw.extensions(and optionallyruntimeExtensions,setupEntry,runtimeSetupEntry). Without that key the plugin is invisible to OpenClaw andplugins.entries.<id>references produce the "plugin not found" warning shown above.Reference: Plugin entry points.
Fix. Add to
mcp-openclaw/package.json:{ "openclaw": { "extensions": ["./src/index.ts"], "runtimeExtensions": ["./dist/index.js"] } }runtimeExtensionsis preferred for installed packages so they don't have to ship TypeScript or runtscat load.Blocking issue 2 —
openclaw.plugin.jsondeclares an unsupportedentryfieldOpenClaw's plugin manifest is metadata-only by design. It is read before any plugin code is loaded so config validation can run without booting plugin runtime.
entryis not part of the manifest schema and is silently ignored.Reference: Plugin manifest.
Fix. Drop
entryfrommcp-openclaw/openclaw.plugin.json. Code entry belongs inpackage.json.openclaw.*.Blocking issue 3 — Plugin uses a non-existent OpenClaw plugin API
src/index.tscalls:Real surface (selected fields from
openclaw/dist/plugin-sdk/src/plugins/types.d.ts):A registered tool is an object:
There is no
onShutdownhook on the API. The canonical lifecycle pattern isregisterService({ id, start, stop })and the service'sstop()is what should callmanager.shutdown().Fix. Use
definePluginEntryfromopenclaw/plugin-sdk/plugin-entry. Register anaauth-mcpservice that doesconnectAll()+ per-toolregisterTool({...})instart(), andmanager.shutdown()instop().Contract issues once it loads
These don't block discovery but are worth fixing in the same pass:
register()is async-leaky.manager.connectAll().then(() => api.registerTool(...))registers tools afterregister()returns. Tools registered post-return may not appear in OpenClaw's static catalog snapshot used for setup-only loading. Move dynamic registration intoregisterService.start(), orawaitfrom an asyncregister().api.loggerexists; right now the plugin runs silently. Triage is hard when something goes wrong.agent_urlis required but could self-discover. When@aauth/local-keyshas exactly one configured agent, default to it; require the field only when there's ambiguity.delegatefield is unused noise. README example setsdelegate: "openclaw"and code defaults to"openclaw". Remove from public config until there's a reason.<server>_<tool>collides with any MCP tool that already has an underscore (most of them). Prefer either<server>__<tool>(rare collision) or register tools under their original MCP names with an internal(toolName → server)index inServerManager.Adoption suggestions (out of scope for this PR, filing separately if useful)
setup-entry.tssoopenclaw onboardcan run an interactive AAuth bootstrap.registerAutoEnableProbeso installing the plugin + runningaauth-bootstraplights up automatically.callToolso AAuth-MCP servers behave like local ones from the agent's POV.Reproduction
npm install -g @aauth/mcp-openclaw(or copymcp-openclaw/into~/.openclaw/extensions/aauth-mcp/)plugins.entries.aauth-mcpto~/.openclaw/openclaw.jsonper READMEopenclaw gateway restarttail /tmp/openclaw/openclaw-*.log→ seeplugin not found: aauth-mcpWhat worked
For completeness, these all worked end-to-end on the first real attempt, separate from the OpenClaw plugin:
@aauth/local-keyson Apple Siliconaauth-bootstrapagainsthttps://person.hello-beta.net(one user-consent round-trip)GET https://whoami.aauth.dev/(mode 1, agent-token only) → 200 with{sub, ps}GET https://whoami.aauth.dev/?scope=name+email(mode 3, three-party) → 401 challenge → resource_token → PS/aauth/tokenexchange → consent → auth_token → 200 with full identity claimsonInteractioncallback firing with the right URL+codeThe protocol layer is in really good shape. This issue is just about the OpenClaw plugin entry not matching the OpenClaw plugin API.
Will follow up with a PR that fixes the three blocking issues + the async-leaky
register(). Happy to take direction on the contract-issue items if you want them in the same PR or split out.