How to Create and Register Custom Skills in OmniRoute's Framework

To create and register custom skills in OmniRoute, implement the Skill interface defined in src/lib/skills/types.ts, add your skill to the registry in src/lib/skills/registry.ts, and optionally define a Zod input schema for validation.

OmniRoute's skill system is an extensible plug-in framework that lets developers add new capabilities invokable from the router's API or MCP tools. This guide walks through the complete workflow based on the diegosouzapw/OmniRoute source code, from defining a skill's contract to making it discoverable across the application.


Understanding the Skill Architecture

Before writing code, it helps to understand how OmniRoute organizes its skill layer. The framework separates concerns across five core files:

Component Purpose Source Location
Skill interface TypeScript contract every skill must satisfy src/lib/skills/types.ts
Skill registry Singleton map holding all registered skills src/lib/skills/registry.ts
Schema definitions Zod schemas for input validation src/lib/skills/schemas.ts
Execution wrapper Sandboxing, error handling, and remote execution src/lib/skills/executor.ts
Built-in skills Reference implementations (browser, githubCollector, a2a) src/lib/skills/builtins.ts

The registry aggregates skills from builtins.ts plus any custom additions, then exposes getSkill(name) and listSkills() for the router and MCP server to consume.


Step 1: Create a Skill File

Create a new TypeScript file under src/lib/skills/. The convention uses camelCase filenames matching your skill name.

// src/lib/skills/helloWorld.ts
import { Skill, SkillContext } from '@/lib/skills/types';
import { z } from 'zod';

export const helloWorldSchema = z.object({
  name: z.string().default('World'),
});

export const helloWorld: Skill = {
  name: 'helloWorld',
  description: 'Returns a friendly greeting.',
  inputSchema: helloWorldSchema,
  async execute(input, _ctx: SkillContext) {
    const { name } = input;
    return { greeting: `Hello, ${name}!` };
  },
};

Every skill requires four properties:

  • name — unique identifier used for lookups
  • description — human-readable summary for tool listings
  • inputSchema — optional but strongly recommended Zod schema
  • execute — async function receiving validated input and context

The SkillContext parameter provides runtime dependencies like logger, configuration, and sandbox controls.


Step 2: Define Input Validation Schemas

For complex inputs, define a dedicated schema in src/lib/skills/schemas.ts or co-locate it with your skill:

// src/lib/skills/schemas.ts (or myCustomSkill.ts)
import { z } from 'zod';

export const mySkillSchema = z.object({
  url: z.string().url(),
  timeoutMs: z.number().int().positive().default(5000),
  retries: z.number().int().min(0).max(3).default(0),
});

Using zod enables automatic validation, type inference, and OpenAPI-compatible JSON Schema generation for MCP tool definitions.


Step 3: Register Your Skill in the Registry

Open src/lib/skills/registry.ts and import your skill into the builtinSkills array:

// src/lib/skills/registry.ts
import { helloWorld } from './helloWorld';
import { myCustomSkill } from './myCustomSkill';
import { builtinSkills as existingBuiltins } from './builtins';

export const builtinSkills = [
  ...existingBuiltins,
  helloWorld,
  myCustomSkill,
];

The registry builds its internal map from this exported array. Skills are registered at import time, making them immediately available to:

  • Router strategies resolving skills by name
  • MCP server tool listings
  • API endpoints using getSkill()

Step 4: Execute Skills Programmatically

Once registered, invoke skills through the SkillExecutor:

import { getSkill } from '@/lib/skills/registry';
import { SkillExecutor } from '@/lib/skills/executor';

async function handleRequest(req: Request) {
  const skill = getSkill('helloWorld');
  if (!skill) {
    throw new Error('Skill not found');
  }
  
  const result = await SkillExecutor.execute(skill, req.body);
  return result;
}

The executor handles:

  • Input validation against inputSchema
  • Error boundary catches and formatting
  • Sandbox context injection
  • Optional remote execution flags

Exposing Skills via MCP Tools

To make your skill callable from MCP clients, no additional code is typically required. The framework in open-sse/mcp-server/tools/ automatically forwards incoming tool calls to SkillExecutor.execute using the skill name as the tool identifier.

Ensure your skill's name matches the MCP tool name and that inputSchema produces a valid JSON Schema for the MCP protocol.


Summary

  • Implement Skill from src/lib/skills/types.ts with name, description, inputSchema, and execute
  • Add Zod schemas for robust input validation and type safety
  • Register in src/lib/skills/registry.ts by importing into the builtinSkills array
  • Execute via SkillExecutor for sandboxed, error-handled invocation
  • Expose automatically through MCP when following naming conventions

Frequently Asked Questions

What interface must a custom skill implement?

The Skill interface in src/lib/skills/types.ts requires name: string, description: string, inputSchema?: ZodTypeAny, and execute(input: any, ctx: SkillContext): Promise<any>. All properties except inputSchema are mandatory.

Where do I register a new skill so the router can find it?

Register custom skills in src/lib/skills/registry.ts by importing them and adding them to the exported builtinSkills array. The registry singleton provides getSkill(name) lookups used throughout the application.

Can I skip defining an input schema?

Yes, but it's discouraged. Without inputSchema, your skill receives raw unvalidated input. The SkillExecutor will still run the skill, but type safety and automatic MCP schema generation are lost.

How do built-in skills differ from custom skills?

Functionally, nothing. Both implement the same Skill interface. Built-in skills live in src/lib/skills/builtins.ts and ship with OmniRoute; custom skills follow identical patterns in separate files you create.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →