hono-apcore
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hono-apcorescan my Hono app routes and expose them as MCP tools"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
hono-apcore
Hono adapter for the apcore AI-Perceivable module ecosystem. Turn a Hono app into MCP tools and OpenAI-compatible function definitions — either by declaring tools explicitly, or by scanning the routes you already have.
Features
Two ways in — declare tools with
defineTool()/defineToolset(), or scan your existing routes with zero code changesRoute replay — a scanned route becomes a module that calls back through
app.request(), so middleware, validators, and error handlers all still runOne port —
mountMcp()serves the MCP endpoint, the Tool Explorer, and/healthfrom the same Hono appAnnotation inference —
GET→ readonly + cacheable,PUT→ idempotent,DELETE→ destructive (RFC 9110 safe-method semantics)Multi-schema — TypeBox, Zod 3, Zod 4, and plain JSON Schema, auto-detected through a priority chain
Context, ACL, and identity — the
apcore()middleware builds a per-request apcoreContextwith W3C trace propagation, so ACL rules govern your routes tooRuntime-agnostic core —
apcore-mcp,apcore-cli, andapcore-a2aare optional peers loaded lazily, so importinghono-apcorenever dragsnode:httpinto an edge buildCLI —
hono-apcore scan | serve | exportworks against a plain Hono appYAML bindings — register modules declaratively, without touching source
Related MCP server: Graft
Installation
npm install hono-apcore honoOptional peers, installed only for the surfaces you use:
npm install apcore-mcp @modelcontextprotocol/sdk # MCP server + Tool Explorer
npm install @hono/node-server # mountMcp() on the Node runtime
npm install apcore-cli # CLI surface
npm install apcore-a2a # A2A agent surface
npm install @sinclair/typebox # TypeBox schemas (recommended)
npm install zod # Zod schemasRequirements: Node.js >= 18, Hono >= 4 (tested with Hono 4.13).
Quick start
1. Declare some tools
// todo.tools.ts
import { Type } from '@sinclair/typebox';
import { defineToolset } from 'hono-apcore';
export const todoTools = defineToolset({
namespace: 'todo',
description: 'Todo list management',
tags: ['todo'],
tools: {
list: {
description: 'List all todos, optionally filtered by status',
inputSchema: Type.Object({ done: Type.Optional(Type.Boolean()) }),
annotations: { readonly: true, idempotent: true },
handler: (inputs) => ({ todos: store.list(inputs.done as boolean | undefined) }),
},
add: {
description: 'Add a new todo item',
inputSchema: Type.Object({ title: Type.String() }),
annotations: { readonly: false },
handler: (inputs) => ({ todo: store.add(String(inputs.title)) }),
},
},
});2. Wire it into the app
// app.ts
import { Hono } from 'hono';
import { apcore, createApcore } from 'hono-apcore';
import { todoTools } from './todo.tools.js';
export const ap = createApcore({
tools: todoTools,
mcp: { name: 'my-app', explorer: true, allowExecute: true },
});
export const app = new Hono();
app.use('*', apcore(ap));
app.get('/todos', (c) => c.json(store.list()));3. Boot
// main.ts
import { serve } from '@hono/node-server';
import { app, ap } from './app.js';
await ap.init(app); // register tools + scan routes
await ap.mountMcp(app); // mount /mcp, /explorer, /health
serve({ fetch: app.fetch, port: 3000 });Your app now answers:
REST at
http://localhost:3000/todosMCP at
http://localhost:3000/mcpTool Explorer at
http://localhost:3000/explorer/
Two ways to expose a capability
defineTool() — explicit tools
The Hono counterpart to NestJS's @ApTool decorator. Hono has no classes or DI
container to decorate, so a tool is a plain object that carries its own
metadata and handler.
import { defineTool } from 'hono-apcore';
const sendEmail = defineTool({
namespace: 'email',
name: 'send', // -> module id "email.send"
description: 'Send an email',
inputSchema: Type.Object({ to: Type.String(), body: Type.String() }),
outputSchema: Type.Object({ messageId: Type.String() }),
annotations: { readonly: false, destructive: false, requiresApproval: true },
tags: ['email'],
params: { to: 'Recipient address' }, // merged into the schema descriptions
handler: async (inputs, context) => mailer.send(inputs, context),
});Field | Notes |
| Used verbatim. Otherwise |
| TypeBox, Zod, or plain JSON Schema |
|
|
| Per-parameter prose merged into the input schema. JavaScript cannot read a function's leading comments at run time the way Python reads a docstring, so this is explicit |
|
|
Route scanning — zero-intrusion tools
Point the scanner at an app and every route becomes a module that replays it
in-process through app.request():
const ap = createApcore({
routes: {
excludePaths: ['/health', '/mcp*', '/explorer*'],
modulePrefix: 'api',
},
});
await ap.init(app); // -> api.todos.list, api.todos.get, api.todos.create, …Module IDs come from the path and the HTTP verb:
Route | Module ID | Inferred annotations |
|
|
|
|
|
|
|
| — |
|
|
|
|
|
|
The generated input schema carries one required string property per path
parameter, plus a free-form query object (GET/DELETE) or body object
(POST/PUT/PATCH). Override any of it per route:
routes: {
overrides: {
'GET /todos': {
id: 'todo.all',
description: 'Every todo, newest first',
inputSchema: Type.Object({ done: Type.Optional(Type.Boolean()) }),
annotations: { readonly: true, idempotent: true },
},
'DELETE /admin/wipe': { skip: true },
},
}Because execution goes back through app.request(), an AI call runs the same
code path as an HTTP call — auth middleware, validators, error handlers and all.
Identity and W3C trace headers from the apcore Context are forwarded onto the
replayed request.
API reference
createApcore(options)
Returns a HonoApcore — the Registry, the Executor, and every surface hang off
it.
createApcore({
extensionsDir?: string | null, // scanned by Registry.discover()
acl?: ACL, // enforced by the Executor on every call
middleware?: Middleware[], // apcore middleware installed on the Executor
bindings?: string, // YAML bindings file loaded during init()
tools?: ApToolDefinition[], // registered during init()
routes?: RouteScanOptions, // route-scanner configuration
settings?: Partial<ApcoreSettings>, // overrides for the APCORE_* settings
mcp?: ApcoreMcpOptions, // presence enables the MCP surface
cli?: ApcoreCliOptions, // presence enables the CLI surface
a2a?: ApcoreA2aOptions, // presence enables the A2A surface
})Method | Description |
| Discover, register tools and bindings, scan routes, start standalone surfaces. Idempotent |
| Await an in-flight |
| Register tool definitions at run time |
| Register the methods of a plain service object |
| Scan and register an app's routes |
| The merged route-scan options this instance would use |
| Load a YAML bindings file |
| Mount |
| OpenAI-compatible function definitions |
| Shut down the MCP and A2A surfaces |
apcore(instance | options, middlewareOptions?)
Hono middleware that puts the instance and a per-request apcore Context on the
Hono context.
app.use('*', apcore(ap));
app.get('/orders', async (c) =>
c.json(await getApcore(c).executor.call('orders.list', {}, getApcoreContext(c))),
);The variable map is augmented, so c.get('apcore') and c.get('apcoreContext')
are typed too. Pass { skipContext: true } on routes that never call modules,
or { contextFactory } to plug in real authentication.
HonoContextFactory
Builds the apcore Context from a Hono context, a Request, or bare Headers.
Identity resolution, in order: x-user-id → Authorization: Bearer … (identity
id "bearer") → a bare x-roles header (a demo shortcut) → anonymous. A
traceparent header supplies the trace id; x-correlation-id (or
x-request-id) lands in context.data.
new HonoContextFactory({
resolveIdentity: (headers) => identityFromSession(headers), // wins over the above
data: (headers) => ({ tenant: headers.get('x-tenant') }),
});MCP
ApcoreMcpService runs the MCP server two ways.
Embedded — one process, one port:
await ap.mountMcp(app, { endpoint: '/mcp', explorer: true, allowExecute: true });This needs the raw Node request and response objects that @hono/node-server
exposes on c.env, so it is Node-only; a mounted handler on another runtime
answers 501 with that explanation. endpoint must be the path as the HTTP
server sees it — include the prefix if the app sits under a basePath.
Standalone — a separate port, or stdio for a CLI-launched server:
createApcore({ mcp: { transport: 'streamable-http', host: '0.0.0.0', port: 8000 } });
// init() starts it, because `transport` was set explicitlyKey MCP options:
Field | Type | Description |
|
| Standalone transport. Setting it makes |
|
| Bind address for HTTP transports |
|
| Server identity |
| Tool Explorer web UI | |
| JWT or custom auth | |
| Expose only matching modules | |
|
| Enforce input schemas on every call |
| Metrics + usage middleware and their endpoints | |
| Result serialisation | |
| Approval gate for destructive tools | |
| Extra apcore middleware / ACL for the MCP executor |
Schema adapters
Schemas are auto-detected and converted through a priority chain:
Adapter | Priority | Input |
| 100 |
|
| 50 | Zod 3 ( |
| 30 | Plain JSON Schema objects |
Detection is structural — neither TypeBox nor Zod is imported at run time — so
whichever the host app installs (or neither) is fine. Register your own with
SchemaExtractor.registerAdapter().
YAML bindings
Register modules without touching source:
bindings:
- module_id: email.send
target: EmailService.send
description: Send an email
input_schema:
type: object
properties:
to: { type: string }
tags: [email, mutate]
annotations:
readonly: falseimport { resolverFromObjects } from 'hono-apcore';
await ap.loadBindings('./bindings.yaml', resolverFromObjects({ EmailService: mailer }));Going the other way, writeBindingsFile() serialises scanned modules back out —
which is what hono-apcore scan --format yaml does.
CLI
hono-apcore scan ./src/app.ts # print the modules a scan would produce
hono-apcore scan ./src/app.ts --format yaml --out bindings.yaml
hono-apcore serve ./src/app.ts --transport http --port 8000 --explorer
hono-apcore export ./src/app.ts --out tools.jsonThe entry is path[:export]; the export defaults to default, then app. If
the module exports a HonoApcore under any name, its configuration — route
filters, module prefix, MCP options — is honoured, so scan reports exactly the
modules the app itself registers; CLI flags override it. An entry with no
instance still works, so serve runs against an app that has never heard of
apcore. TypeScript entries need a loader:
npx tsx node_modules/.bin/hono-apcore scan ./src/app.tsConfiguration (APCORE_*)
The canonical settings every apcore integration implements, read from the
environment and overridable via settings:
Variable | Type | Default | Purpose |
| bool |
| Master switch — |
| bool |
| Verbose logging / introspection |
| list |
| Enabled scanner identifiers |
| list |
| Route patterns to include (empty = all) |
| list |
| Route patterns to exclude |
| str |
| Prefix prepended to generated module IDs |
| bool |
| Require auth for MCP/A2A endpoints |
| str |
|
|
| str |
| MCP transport: |
| str |
| Bind address when the transport is not stdio |
| int |
| Bind port when the transport is not stdio |
Optional peers are not re-exported
Unlike the NestJS adapter, hono-apcore does not re-export the apcore-mcp
/ apcore-cli / apcore-a2a surfaces. Doing so would make them load eagerly,
and apcore-mcp pulls in node:http — which breaks a Workers, Deno, or Bun
build of an app that never uses the MCP surface. Import those symbols from their
own packages:
import { JWTAuthenticator, getCurrentIdentity } from 'apcore-mcp';
import { createCli } from 'apcore-cli';
import { A2AClient } from 'apcore-a2a';apcore-js and apcore-toolkit are hard dependencies, so their common
symbols (ACL, Config, registerSysModules, TraceContext, BaseScanner,
formatModules, …) re-export from hono-apcore directly.
Examples
Example | Shows |
Full app: hand-written tools and route scanning, JWT, ACL, system modules, Docker | |
Routes governed by apcore ACL — |
pnpm install && pnpm build
cd examples/demo && pnpm install && pnpm devDetailed documentation
Feature overview — architecture and dependency graph
Tool definition —
defineTool,defineToolset, module IDsRoute scanner — how routes become modules, and what replay costs
MCP integration — embedded vs standalone, the Node bridge
Schema extraction — the adapter chain and custom adapters
Context and ACL — identity, tracing, and governing routes
Scripts
Command | Description |
| Compile TypeScript |
| Watch-mode compilation |
| Run the test suite (vitest) |
| Tests with coverage (90% thresholds) |
| Type-check without emitting |
| Lint source and tests |
License
Apache-2.0
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceExposes Hono API endpoints as Model Context Protocol tools, allowing LLMs to interact with your API routes through a dedicated MCP endpoint. It provides helpers to describe routes and includes a codemode for dynamic API interaction via search and execute tools.3286MIT
- AlicenseNot gradedqualityCmaintenanceEnables building agent-ready APIs that expose tools as both HTTP and MCP endpoints from a single server definition, with automatic OpenAPI, discovery docs, and interactive API reference.5Apache 2.0
- AlicenseBqualityCmaintenanceTransforms OpenAPI definitions into MCP tools for seamless LLM-API integration.8391MIT
- AlicenseNot gradedqualityCmaintenanceEasily expose your Hono API endpoints as MCP tools with minimal configuration, supporting type-safe input handling and tool registration.322MIT
Related MCP Connectors
Point Gecko at an OpenAPI spec; get first-call-correct, auth-hidden agent tools.
Free public MCP for AI agents — 193 tools, 44 workflows. No API key.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/aiperceivable/hono-apcore'
If you have feedback or need assistance with the MCP directory API, please join our Discord server