Skip to content

[mcp-openclaw] Plugin does not load on current OpenClaw — discovery + API contract issues #4

@clawdreyhepburn

Description

@clawdreyhepburn

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:

  1. 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().
  2. No logger usage. api.logger exists; right now the plugin runs silently. Triage is hard when something goes wrong.
  3. 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.
  4. delegate field is unused noise. README example sets delegate: "openclaw" and code defaults to "openclaw". Remove from public config until there's a reason.
  5. 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

  1. npm install -g @aauth/mcp-openclaw (or copy mcp-openclaw/ into ~/.openclaw/extensions/aauth-mcp/)
  2. Add plugins.entries.aauth-mcp to ~/.openclaw/openclaw.json per README
  3. openclaw gateway restart
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions