Skillgate: Turn REST APIs into Governed Agent Skills
Skillgate converts selected HazelJS (or OpenAPI) endpoints into agent skills — filtered, classified, and wired into Agent OS and optionally MCP. It does not invent business logic from your spec. Domain reasoning stays in your agent prompt / AgentRuntime. Skillgate owns which APIs become tools and how safely.
Quick Reference
- Purpose: Curate a small, safe skill surface from OpenAPI or Hazel controllers, then register it on
ToolRegistry(and optionally MCP). - When to use: You already have REST controllers and want an agent that can call a subset of them with HITL on writes.
- Key concepts: opt-in tags /
x-hazel-skill/@AgentSkill, read vs write vs destructive,Skillgate.fromOpenApi/fromModule,register,report,toMcpServer,defaultSkillgateOptions. - Dependencies:
@hazeljs/skillgate,@hazeljs/agent; optional@hazeljs/swagger(fromModule),@hazeljs/mcp(toMcpServer),@hazeljs/core. - Common mistakes:
include.mode: 'all'in production; dumping every CRUD route; skipping approval on writes; ignoringreport().denied/ tool-count warnings.
Mental model
OpenAPI / Hazel controllers
│
▼
Skillgate filter (opt-in)
│
▼
Classify (read / write / destructive / admin)
│
▼
GovernedSkill[] ──register──► ToolRegistry (+ HITL flags)
│
├──► AgentRuntime.execute(...)
└──► toMcpServer(...) (optional)
| Layer | Owns |
|---|---|
| Your controllers | Business logic & auth |
| Skillgate | Allowlist, classification, HTTP invokers, approval defaults |
| Agent OS | Loop, policies, budgets, durable HITL, leases, identity |
| MCP | External tool protocol export |
Install
npm install @hazeljs/skillgate @hazeljs/agent
# optional:
npm install @hazeljs/swagger @hazeljs/mcp @hazeljs/cli
Or scaffold:
hazel add skillgate
hazel skillgate init
Path A — From OpenAPI JSON
import { Skillgate } from '@hazeljs/skillgate';
import { ToolRegistry } from '@hazeljs/agent';
const gate = Skillgate.fromOpenApi(spec, {
include: { tags: ['agent'] }, // default opt-in tags: agent, skillgate
invoke: {
baseUrl: 'http://127.0.0.1:3000',
headers: { Authorization: 'Bearer ${API_TOKEN}' }, // env interpolation
},
});
const registry = new ToolRegistry();
gate.register(registry, 'api-concierge');
console.log(gate.report());
CLI preview (no runtime):
hazel skillgate from-openapi ./openapi.json
Headers and SSRF
${ENV_NAME}ininvoke.headersis expanded fromprocess.env.- Set
invoke.ssrfProtection: truewhenbaseUrlcomes from untrusted input (blocks private / loopback / cloud-metadata hosts). First-party localhost backends usually leave thisfalse.
Path B — From a Hazel module (no hand-written OpenAPI)
Tag controllers with agent / skillgate, or mark methods with @AgentSkill:
import { Controller, Get, Post, Body, Param, ApiTags, HazelModule } from '@hazeljs/core';
import { AgentSkill, Skillgate } from '@hazeljs/skillgate';
@ApiTags('agent')
@Controller('/orders')
class OrdersController {
@Get('/:id')
@AgentSkill({ description: 'Fetch an order by id', readOnly: true })
getOrder(@Param('id') id: string) {
return { id, status: 'shipped' };
}
@Post('/:id/refund')
@AgentSkill({
description: 'Refund an order',
requiresApproval: true,
class: 'write',
})
refund(@Param('id') id: string, @Body() body: { amount: number }) {
return { id, refunded: true, ...body };
}
}
@HazelModule({ controllers: [OrdersController] })
class AppModule {}
const gate = Skillgate.fromModule(AppModule, {
swagger: { title: 'Orders', servers: [{ url: 'http://127.0.0.1:3000' }] },
invoke: { baseUrl: 'http://127.0.0.1:3000' },
});
fromModule requires @hazeljs/swagger. It calls createOpenApiDocument, then (by default) merges @AgentSkill → OpenAPI x-hazel-skill via enrichAgentSkills: true.
Safety defaults
| Class | How it is inferred | Default behavior |
|---|---|---|
| read | GET / HEAD | readOnly: true, no approval |
| write | POST / PUT / PATCH | requiresApproval: true |
| destructive | DELETE (and configured methods) | Denied unless classify.allowDestructive |
| admin | Paths matching /admin, /internal, /debug, health, etc. | Denied unless classify.allowAdmin |
Tool-count guardrails (overridable):
| Knob | Default |
|---|---|
warnAbove | 12 — warning in report().warnings |
maxTools | 24 — throws unless force: true |
strictDescriptions | off — when on, rejects placeholder summaries |
Production-safe merge helper:
import { defaultSkillgateOptions, Skillgate } from '@hazeljs/skillgate';
const gate = Skillgate.fromOpenApi(spec, defaultSkillgateOptions({
invoke: { baseUrl: process.env.API_BASE_URL! },
classify: { allowDestructive: false },
}));
Include modes (curation)
Skillgate.fromOpenApi(spec, {
include: {
mode: 'opt-in', // default — prefer this in prod
tags: ['agent', 'skillgate'],
operationIds: ['getOrder'],
paths: [/^\/orders/],
methods: ['GET', 'POST'],
deny: [/admin/i, 'internalHealth'],
},
});
| Mode | Behavior |
|---|---|
opt-in | Only ops with matching tags, x-hazel-skill, operationIds, or paths |
all | Every non-denied op — warns; avoid in production |
OpenAPI extension: x-hazel-skill
paths:
/orders/{id}:
get:
operationId: getOrder
tags: [agent]
summary: Fetch an order by id
x-hazel-skill:
readOnly: true
name: get_order
post:
operationId: refundOrder
tags: [agent]
x-hazel-skill:
requiresApproval: true
class: write
Boolean form x-hazel-skill: true means “enabled with defaults.”
@AgentSkill decorator
| Option | Meaning |
|---|---|
name | Tool name (default: method name) |
description | Shown to the LLM |
readOnly | Marks read skill |
requiresApproval | HITL before invoke |
class | Force read | write | destructive | admin |
enabled: false | Explicitly not a skill |
Helpers: getAgentSkillMetadata, getAgentSkillMethods, isAgentSkill, toXHazelSkill.
Registration & Agent OS
register attaches HTTP invokers via createSkillInvoker and sets tool-driver metadata used by Agent OS / MCP:
requiresApproval,readOnly,capability(skillgate.{class}.{name})riskLevel: read → low, write → medium, destructive → highidempotentfor reads
import { AgentRuntime, ToolRegistry } from '@hazeljs/agent';
import { Skillgate } from '@hazeljs/skillgate';
const gate = Skillgate.fromModule(AppModule, {
invoke: { baseUrl: 'http://127.0.0.1:3000' },
});
const registry = new ToolRegistry();
gate.register(registry, 'api-concierge');
const runtime = new AgentRuntime({
llmProvider,
durableSuspend: true, // crash-safe HITL for write approvals
// wire registry / AgentModule as usual in your app
});
await runtime.execute('api-concierge', userGoal, {
contract: { name: 'api-slo', maxLatencyMs: 30_000 },
budget: { maxTokens: 50_000 },
});
Inspect what was curated:
const { included, denied, warnings } = gate.report();
console.table(included.map((s) => ({ name: s.name, class: s.class, hitl: s.requiresApproval })));
console.table(denied.map((s) => ({ name: s.name, reason: s.denyReason })));
MCP export
const server = gate.toMcpServer({
name: 'hazel-api-skills',
version: '1.0.0',
agentName: 'api-concierge',
});
server.listenStdio();
Requires @hazeljs/mcp. MCP tools inherit Skillgate annotations / _meta for tool-driver parity with Agent OS.
End-to-end recipe (API + agent + MCP)
- Build REST controllers; tag opt-in surface with
@ApiTags('agent')and@AgentSkill. Skillgate.fromModule(AppModule, { invoke: { baseUrl } }).gate.register(registry, 'api-concierge')inside your agent module.- Run with Agent OS options (loop, policy, durable HITL, budget).
- Optionally
gate.toMcpServer(...)for Cursor / Claude Desktop. - Keep skill count ≤ ~12 for reliable tool selection.
Starter: hazeljs-skillgate-agent-starter (OpenAPI demo + MCP entrypoint).
Troubleshooting
| Symptom | Fix |
|---|---|
| Zero skills | Add tags / @AgentSkill / x-hazel-skill; check include.mode |
SkillgateConfigError on maxTools | Curate surface or raise maxTools / force (not recommended) |
| Writes run without HITL | Ensure classify.writeRequiresApproval (default true) and Agent OS durableSuspend / approvals wired |
fromModule throws | Install @hazeljs/swagger |
toMcpServer throws | Install @hazeljs/mcp |
| Localhost blocked | Leave ssrfProtection false for first-party apps |
What not to do
- Auto-agentize every route (
mode: 'all') without curation - Claim the agent “learns” business rules from OpenAPI alone
- Expose admin / destructive tools without HITL and audit
- Register 30+ skills and expect reliable tool choice
Related
- Skillgate package reference
- Agent package
- Agent OS
- Swagger
- MCP
- Production AI agents
- Glossary
- hazeljs-skillgate-agent-starter — Meridian Commerce ops concierge starter