OpenFunction definitions can be exposed through MCP or adapted to a host's native extension API. The framework keeps all three integrations dependency free: install a host SDK only in the plugin or extension package that needs it.
Hermes consumes OpenFunction through MCP, its supported boundary for external
tool servers. createHermesMcpConfig() builds the mcp_servers fragment for
~/.hermes/config.yaml and snapshots an explicit tool allowlist from the
registry.
import {
createHermesMcpConfig,
registry,
} from "../src/framework/index.js";
const config = createHermesMcpConfig(registry, {
command: "node",
args: ["/absolute/path/to/openfunction/dist/src/index.js"],
timeout: 120,
connectTimeout: 30,
supportsParallelToolCalls: true,
filter: (tool) => tool.tags?.includes("hermes") ?? false,
resources: false,
prompts: false,
});Serialize or merge the returned object into YAML, then start a new Hermes
session or run /reload-mcp. Prefer an absolute server entrypoint because
Hermes may start the subprocess from a different working directory.
The generated tools.include is intentional. Adding a new registry tool later
does not silently grant it to Hermes; regenerate and review the config.
Pi has a native TypeScript extension API. registerPiTools() accepts the
small part of ExtensionAPI that OpenFunction needs and registers compatible
tool definitions without importing Pi in the framework itself.
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import {
registerPiTools,
type PiExtensionApiLike,
} from "openfunction/framework";
import { registry } from "./registry.js";
export default function (pi: ExtensionAPI) {
registerPiTools(pi as unknown as PiExtensionApiLike, registry, {
namePrefix: "of_",
promptSnippet: (tool) => `Use ${tool.name} from OpenFunction`,
});
}The JSON Schema subset used by defineTool() passes through as Pi's
parameters schema. Successful calls return text content plus the original
ToolResult in details. Failed validation or handlers throw because that is
how current Pi sets isError: true on a tool result.
See plugins/pi-openfunctions/ for a loadable local reference extension.
Current OpenClaw has two relevant plugin paths:
defineToolPlugin()for a static, tool-only plugin. It exposes metadata thatopenclaw plugins buildwrites toopenclaw.plugin.jsonbefore runtime code is loaded. UsetoOpenclawToolPluginTools()for this path.definePluginEntry()plusapi.registerTool()for mixed-capability plugins or plugins whose tool names are computed dynamically. ExistingtoOpenclawTools()andtoolToOpenclaw()remain for this path.
import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
import type { TSchema } from "typebox";
import { toOpenclawToolPluginTools } from "openfunction/framework";
import { registry } from "./registry.js";
const definitions = toOpenclawToolPluginTools(registry);
export default defineToolPlugin({
id: "my-openfunctions-tools",
name: "My OpenFunction tools",
description: "Expose an OpenFunction registry to OpenClaw.",
tools: (tool) => definitions.map((definition) => tool({
...definition,
parameters: definition.parameters as TSchema,
})),
});For distributable plugins, ship compiled ESM, keep typebox in runtime
dependencies, declare openclaw >=2026.5.17 as a peer, and run both:
openclaw plugins build --entry ./dist/index.js --check
openclaw plugins validate --entry ./dist/index.jsplugins/openclaw-openfunctions/ demonstrates the generated tool-only path.
plugins/openclaw-execufunction/ uses direct registration because its surface
is projected dynamically from the installed Siftable MCP SDK. Its checked-in
contracts.tools list is generated from that same SDK and verified against
runtime registrations, so OpenClaw discovery cannot drift from execution.
| Runtime | Boundary | Failure signal | Discovery safeguard |
|---|---|---|---|
| Hermes | MCP stdio | MCP error result | Explicit tools.include snapshot |
| Pi | ExtensionAPI.registerTool() |
Throw from execute() |
Extension-selected registry filter |
| OpenClaw tool plugin | defineToolPlugin() |
Throw from execute() |
Generated contracts.tools manifest |
| OpenClaw mixed plugin | api.registerTool() |
Throw from execute() |
Generated and runtime-verified manifest contract |