Skip to main content
Glama

hono-apcore

Hono-Adapter für das apcore Ökosystem der AI-Perceivable-Module. Verwandelt eine Hono-App in MCP-Tools und OpenAI-kompatible Funktionsdefinitionen – entweder durch explizite Tool-Deklaration oder durch Scannen der bereits vorhandenen Routen.

Funktionen

  • Zwei Wege hinein – Tools mit defineTool() / defineToolset() deklarieren oder vorhandene Routen ohne eine einzige Codeänderung scannen

  • Route-Replay – eine gescannte Route wird zu einem Modul, das über app.request() zurückruft, sodass Middleware, Validatoren und Fehlerbehandler weiterhin ausgeführt werden

  • Ein Port – mountMcp() bedient den MCP-Endpunkt, den Tool Explorer und /health aus derselben Hono-App

  • Annotations-Inferenz – GET → readonly + cacheable, PUT → idempotent, DELETE → destruktiv (RFC-9110-Semantik sicherer Methoden)

  • Multi-Schema – TypeBox, Zod 3, Zod 4 und reines JSON Schema, automatisch erkannt über eine Prioritätskette

  • Context, ACL und Identität – die apcore()-Middleware baut einen Pro-Request-apcore-Context mit W3C-Trace-Propagation auf, sodass ACL-Regeln auch Ihre Routen regieren

  • Laufzeit-agnostischer Kern – apcore-mcp, apcore-cli und apcore-a2a sind optionale Peers, die lazy geladen werden, sodass der Import von hono-apcore niemals node:http in einen Edge-Build zieht

  • CLI – hono-apcore scan | serve | export funktioniert mit einer einfachen Hono-App

  • YAML-Bindings – Module deklarativ registrieren, ohne den Quellcode anzufassen

Related MCP server: hono-mcp-server

Installation

npm install hono-apcore hono

Optionale Peers, nur für die Oberflächen installieren, die Sie nutzen:

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 schemas

Voraussetzungen: Node.js >= 18, Hono >= 4 (getestet mit Hono 4.13).

Schnellstart

1. Einige Tools deklarieren

// 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. In die App einbinden

// 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. Starten

// 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 });

Ihre App antwortet nun auf:

  • REST unter http://localhost:3000/todos

  • MCP unter http://localhost:3000/mcp

  • Tool Explorer unter http://localhost:3000/explorer/

Zwei Wege, eine Fähigkeit bereitzustellen

defineTool() – explizite Tools

Das Hono-Pendant zum @ApTool-Decorator von NestJS. Hono hat keine Klassen oder DI-Container, die dekoriert werden könnten, also ist ein Tool ein schlichtes Objekt, das seine eigenen Metadaten und seinen Handler mit sich führt.

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),
});

Feld

Hinweise

id

Wird unverändert verwendet. Andernfalls "<namespace>.<name>", wobei name in Snake Case umgewandelt wird

inputSchema / outputSchema

TypeBox, Zod oder reines JSON Schema

annotations

readonly, destructive, idempotent, requiresApproval, openWorld, streaming, cacheable, …

params

Pro-Parameter-Prosa, die in das Eingabeschema eingearbeitet wird. JavaScript kann die führenden Kommentare einer Funktion zur Laufzeit nicht so lesen, wie Python einen Docstring liest, daher ist dies explizit

handler

(inputs, context) => result. Ein Nicht-Objekt-Ergebnis wird als { result } verpackt

Routen-Scanning – Tools ohne Eingriff

Zeigen Sie den Scanner auf eine App, und jede Route wird zu einem Modul, das sie in-process über app.request() repliziert:

const ap = createApcore({
  routes: {
    excludePaths: ['/health', '/mcp*', '/explorer*'],
    modulePrefix: 'api',
  },
});

await ap.init(app);   // -> api.todos.list, api.todos.get, api.todos.create, …

Modul-IDs ergeben sich aus Pfad und HTTP-Verb:

Route

Modul-ID

Abgeleitete Annotationen

GET /todos

todos.list

readonly, cacheable

GET /todos/:id

todos.get

readonly, cacheable

POST /todos

todos.create

—

PUT /todos/:id

todos.update

idempotent

DELETE /todos/:id

todos.delete

destructive

Das generierte Eingabeschema trägt eine erforderliche String-Eigenschaft pro Pfadparameter plus ein frei formbares query-Objekt (GET/DELETE) oder body-Objekt (POST/PUT/PATCH). Überschreiben Sie alles davon pro 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 },
  },
}

Da die Ausführung über app.request() zurückläuft, durchläuft ein KI-Aufruf denselben Codepfad wie ein HTTP-Aufruf – Auth-Middleware, Validatoren, Fehlerbehandler und alles andere. Identität und W3C-Trace-Header aus dem apcore-Context werden auf den replizierten Request übertragen.

API-Referenz

createApcore(options)

Gibt eine HonoApcore zurück – Registry, Executor und jede Oberfläche hängen daran.

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
})

Methode

Beschreibung

init(app?, routeOptions?)

Tools und Bindings entdecken, registrieren, Routen scannen, eigenständige Oberflächen starten. Idempotent

ready()

Auf ein laufendes init() warten

registerTool(tool) / registerTools(tools)

Tool-Definitionen zur Laufzeit registrieren

registerMethod(opts) / registerObject(opts)

Die Methoden eines einfachen Service-Objekts registrieren

scanRoutes(app, opts?)

Die Routen einer App scannen und registrieren

routeOptions

Die zusammengeführten Routen-Scan-Optionen, die diese Instanz verwenden würde

loadBindings(path?, resolver?)

Eine YAML-Bindings-Datei laden

mountMcp(app, opts?)

/mcp, den Explorer und /health in die App einhängen

toOpenaiTools(opts?)

OpenAI-kompatible Funktionsdefinitionen

close()

Die MCP- und A2A-Oberflächen herunterfahren

apcore(instance | options, middlewareOptions?)

Hono-Middleware, die die Instanz und einen Pro-Request-apcore-Context auf den Hono-Context legt.

app.use('*', apcore(ap));

app.get('/orders', async (c) =>
  c.json(await getApcore(c).executor.call('orders.list', {}, getApcoreContext(c))),
);

Die Variablenzuordnung wird erweitert, sodass c.get('apcore') und c.get('apcoreContext') ebenfalls typisiert sind. Übergeben Sie { skipContext: true } auf Routen, die nie Module aufrufen, oder { contextFactory }, um echte Authentifizierung anzustöpseln.

HonoContextFactory

Baut den apcore-Context aus einem Hono-Context, einem Request oder nackten Headers auf.

Identitätsauflösung, in dieser Reihenfolge: x-user-id → Authorization: Bearer … (Identitäts-ID "bearer") → ein nackter x-roles-Header (eine Demo-Abkürzung) → anonym. Ein traceparent-Header liefert die Trace-ID; x-correlation-id (oder x-request-id) landet in context.data.

new HonoContextFactory({
  resolveIdentity: (headers) => identityFromSession(headers),  // wins over the above
  data: (headers) => ({ tenant: headers.get('x-tenant') }),
});

MCP

ApcoreMcpService betreibt den MCP-Server auf zwei Arten.

Eingebettet – ein Prozess, ein Port:

await ap.mountMcp(app, { endpoint: '/mcp', explorer: true, allowExecute: true });

Dies benötigt die rohen Node-Request- und Response-Objekte, die @hono/node-server auf c.env bereitstellt, ist also nur für Node gedacht; ein eingehängter Handler auf einer anderen Laufzeit antwortet mit 501 und dieser Erklärung. endpoint muss der Pfad sein, wie ihn der HTTP-Server sieht – den Präfix einschließen, wenn die App unter einem basePath liegt.

Eigenständig – ein separater Port oder stdio für einen per CLI gestarteten Server:

createApcore({ mcp: { transport: 'streamable-http', host: '0.0.0.0', port: 8000 } });
// init() starts it, because `transport` was set explicitly

Wichtige MCP-Optionen:

Feld

Typ

Beschreibung

transport

'stdio' | 'streamable-http' | 'sse'

Eigenständiger Transport. Das Setzen startet den Server über init()

host / port

string / number

Bindeadresse für HTTP-Transports

name / version

string

Server-Identität

explorer / explorerPrefix / allowExecute

Tool-Explorer-Web-UI

authenticator / requireAuth / exemptPaths

JWT- oder benutzerdefinierte Authentifizierung

tags / prefix

Nur passende Module bereitstellen

validateInputs

boolean

Eingabeschemas bei jedem Aufruf erzwingen

observability

Metrik- + Usage-Middleware und deren Endpunkte

outputFormat / outputFormatter / redactOutput / trace

Ergebnis-Serialisierung

approvalHandler / approvalStore / approvalNotify

Genehmigungs-Gate für destruktive Tools

mcpMiddleware / mcpAcl

Zusätzliche apcore-Middleware / ACL für den MCP-Executor

Schema-Adapter

Schemas werden automatisch erkannt und über eine Prioritätskette konvertiert:

Adapter

Priorität

Eingabe

TypeBoxAdapter

100

@sinclair/typebox-Schemas

ZodAdapter

50

Zod 3 (_def.typeName) und Zod 4 (_zod.def.type)

JsonSchemaAdapter

30

Reine JSON-Schema-Objekte

Die Erkennung ist strukturell – weder TypeBox noch Zod werden zur Laufzeit importiert –, sodass egal ist, was die Host-App installiert (oder auch nichts). Registrieren Sie eigene Adapter mit SchemaExtractor.registerAdapter().

YAML-Bindings

Module registrieren, ohne den Quellcode anzufassen:

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: false
import { resolverFromObjects } from 'hono-apcore';

await ap.loadBindings('./bindings.yaml', resolverFromObjects({ EmailService: mailer }));

In die andere Richtung serialisiert writeBindingsFile() gescannte Module wieder heraus – genau das tut hono-apcore scan --format yaml.

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.json

Der Einstieg ist pfad[:export]; der Export standardmäßig default, dann app. Wenn das Modul eine HonoApcore unter irgendeinem Namen exportiert, werden deren Konfiguration – Routenfilter, Modul-Präfix, MCP-Optionen – berücksichtigt, sodass scan genau die Module meldet, die die App selbst registriert; CLI-Flags überschreiben sie. Ein Einstieg ohne Instanz funktioniert ebenfalls, sodass serve gegen eine App läuft, die nie von apcore gehört hat. TypeScript-Einstiege benötigen einen Loader:

npx tsx node_modules/.bin/hono-apcore scan ./src/app.ts

Konfiguration (APCORE_*)

Die kanonischen Einstellungen, die jede apcore-Integration implementiert, aus der Umgebung gelesen und über settings überschreibbar:

Variable

Type

Default

Purpose

APCORE_ENABLED

bool

true

Hauptschalter — false macht init() zu einem No-op

APCORE_DEBUG

bool

false

Ausführliche Protokollierung / Introspection

APCORE_SCANNERS

list

["auto"]

Aktivierte Scanner-Identifikatoren

APCORE_INCLUDE_PATHS

list

[]

Einzubeziehende Routenmuster (leer = alle)

APCORE_EXCLUDE_PATHS

list

[]

Auszuschließende Routenmuster

APCORE_MODULE_PREFIX

str

""

Präfix, das generierten Modul-IDs vorangestellt wird

APCORE_AUTH_ENABLED

bool

false

Auth für MCP/A2A-Endpunkte erforderlich

APCORE_AUTH_STRATEGY

str

"bearer"

bearer / session / custom

APCORE_TRANSPORT

str

"stdio"

MCP-Transport: stdio / http / sse

APCORE_HOST

str

"0.0.0.0"

Bind-Adresse, wenn der Transport nicht stdio ist

APCORE_PORT

int

8808

Bind-Port, wenn der Transport nicht stdio ist

Optionale Peers werden nicht re-exportiert

Im Gegensatz zum NestJS-Adapter exportiert hono-apcore die apcore-mcp / apcore-cli / apcore-a2a-Oberflächen nicht erneut. Andernfalls würden sie eager geladen, und apcore-mcp zieht node:http mit ein — was einen Workers-, Deno- oder Bun-Build einer App bricht, die die MCP-Oberfläche nie nutzt. Importieren Sie diese Symbole aus ihren eigenen Paketen:

import { JWTAuthenticator, getCurrentIdentity } from 'apcore-mcp';
import { createCli } from 'apcore-cli';
import { A2AClient } from 'apcore-a2a';

apcore-js und apcore-toolkit sind harte Abhängigkeiten, daher werden ihre gemeinsamen Symbole (ACL, Config, registerSysModules, TraceContext, BaseScanner, formatModules, …) direkt aus hono-apcore re-exportiert.

Beispiele

Beispiel

Zeigt

examples/demo

Vollständige App: handgeschriebene Tools und Routen-Scanning, JWT, ACL, Systemmodule, Docker

examples/acl_demo

Routen, die von der apcore-ACL verwaltet werden — orders.delete nur für Admins

pnpm install && pnpm build
cd examples/demo && pnpm install && pnpm dev

Ausführliche Dokumentation

Skripte

Befehl

Beschreibung

pnpm build

TypeScript kompilieren

pnpm dev

Kompilierung im Watch-Modus

pnpm test

Test-Suite ausführen (vitest)

pnpm test:coverage

Tests mit Coverage (90%-Schwellen)

pnpm typecheck

Typprüfung ohne Ausgabe

pnpm lint

Lint für Quellcode und Tests

Lizenz

Apache-2.0

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Exposes 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.
    34 npm
    86
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Easily expose your Hono API endpoints as MCP tools with minimal configuration, supporting type-safe input handling and tool registration.
    34 npm
    2
    MIT