# How to Set Up A2A Agent Protocol Skills in OmniRoute

> Learn how to set up A2A agent protocol skills in OmniRoute by defining schema handlers registering them with registerA2ASkill and exposing the endpoint at api mcp a2a

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-08-10

---

**OmniRoute implements A2A skills as JSON-RPC 2.0 services inside the MCP server, requiring you to define a schema-compliant handler, register it via `registerA2ASkill()`, and expose the endpoint at `/api/mcp/a2a`.**

The **Agent-to-Agent (A2A)** protocol in OmniRoute enables autonomous agents to invoke remote capabilities through a standardized JSON-RPC interface. Setting up A2A agent protocol skills involves creating reusable logic modules that conform to the Zod schemas defined in the MCP server and registering them for dispatch. This guide walks through the exact implementation based on the OmniRoute source code, referencing specific files from the `release/v3.8.50` branch.

## Understanding A2A Skills in OmniRoute

An A2A skill is a discrete unit of functionality that accepts structured input, executes business logic, and returns structured output. In OmniRoute, skills run inside the **MCP server** (`open-sse/mcp-server`) and communicate via JSON-RPC 2.0 payloads. The canonical skill implementation resides in [`src/lib/skills/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/a2a.ts), which exports the `a2aMemorySkill` demonstrating the memory-aware routing pattern.

## Step-by-Step Setup Process

### Define the Skill Schema and Handler

Create a JavaScript or TypeScript object that declares the skill's metadata and implements its execution logic. The object must include `name`, `version`, `description`, `schema`, and `handler` properties.

According to [`src/lib/skills/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/a2a.ts), the schema follows the Zod contracts defined in [`open-sse/mcp-server/schemas/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/mcp-server/schemas/a2a.ts) (specifically `TaskInputSchema` and `TaskOutputSchema`):

```typescript
// src/lib/skills/a2a.ts
export const a2aMemorySkill = {
  name: "memory_aware_routing",
  version: "1.0.0",
  description: "A2A skill for memory-aware request routing",
  schema: {
    input: {
      type: "object",
      properties: {
        contextRequired: { type: "boolean" }
      }
    },
    output: {
      type: "object",
      properties: {
        recommendedProvider: { type: "string" },
        reason: { type: "string" },
        contextUsed: { type: "boolean" }
      }
    }
  },
  handler: async (input: any, context: any) => ({
    recommendedProvider: "auto",
    reason: "Memory-aware routing requires memories to be loaded",
    contextUsed: input.contextRequired ?? false,
  }),
};

```

### Register the Skill in the A2A Registry

After definition, you must register the handler with the A2A skill registry so the MCP server can route incoming calls. The registration helper in [`src/lib/skills/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/a2a.ts) uses the `registerHandler` method exposed by the registry core in [`src/lib/a2a/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/registry.ts):

```typescript
// src/lib/skills/a2a.ts
export function registerA2ASkill(registry: any): void {
  registry.registerHandler("memory_aware_routing", a2aMemorySkill.handler);
}

```

During server startup, the core initialization sequence calls `registerA2ASkill(registry)` to bind the skill name to its handler function, making it available for remote invocation.

### Enable the A2A Endpoint

The MCP server must start with the A2A transport enabled (the default configuration in the release branch). Once running, the endpoint accepts POST requests at `/api/mcp/a2a` and validates incoming JSON-RPC payloads against the schemas in [`open-sse/mcp-server/schemas/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/mcp-server/schemas/a2a.ts). Remote agents invoke skills using the `message/send` method with a `skillId` parameter matching your registered name.

## Practical Example: Creating a Custom Lookup Skill

To implement a new skill beyond the built-in memory routing, create a standalone file and follow the same registration pattern. Here is a complete custom skill implementation:

```typescript
// src/lib/skills/myLookup.ts
import { z } from "zod";

export const myLookupSkill = {
  name: "quick_lookup",
  version: "1.0.0",
  description: "Returns a static answer for a given key",
  schema: {
    input: {
      type: "object",
      properties: { key: { type: "string" } },
      required: ["key"],
    },
    output: {
      type: "object",
      properties: {
        answer: { type: "string" },
      },
    },
  },
  handler: async ({ key }: { key: string }) => ({
    answer: `You asked for ${key}`,
  }),
};

export function registerMyLookupSkill(registry: any): void {
  registry.registerHandler("quick_lookup", myLookupSkill.handler);
}

```

Integrate this into the system by importing it into the registry initialization file:

```typescript
// src/lib/a2a/registry.ts
import { registerMyLookupSkill } from "@/lib/skills/myLookup";

export function initA2ASkills(registry: any) {
  registerA2ASkill(registry);          // memory-aware routing
  registerMyLookupSkill(registry);     // custom lookup skill
}

```

## Invoking A2A Skills via JSON-RPC

Once registered, clients can call your skill by sending JSON-RPC requests to the A2A endpoint. The request must specify `method: "message/send"` and include the `skillId` in the task parameters.

Based on the test patterns in [`tests/unit/t09-a2a-lifecycle.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/tests/unit/t09-a2a-lifecycle.test.ts), here is a valid invocation using `curl`:

```bash
curl -X POST http://localhost:20128/api/mcp/a2a \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "message/send",
    "params": {
      "task": { "skillId": "quick_lookup" },
      "message": { "role": "user", "content": "{\"key\":\"foo\"}" }
    },
    "id": "req-123"
  }'

```

The server validates the payload against `TaskInputSchema`, dispatches to the registered handler, and returns a JSON-RPC response containing the `answer` field defined in your skill's output schema.

## Summary

- **A2A skills** in OmniRoute are JSON-RPC 2.0 services running inside the MCP server that expose reusable logic to remote agents.
- **Skill definition** requires creating an object with `name`, `version`, `schema`, and `handler` properties in files like [`src/lib/skills/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/skills/a2a.ts), conforming to Zod schemas from [`open-sse/mcp-server/schemas/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/mcp-server/schemas/a2a.ts).
- **Registration** happens via `registerA2ASkill(registry)` or custom registration functions that call `registry.registerHandler()` from [`src/lib/a2a/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/registry.ts).
- **Invocation** occurs at `POST /api/mcp/a2a` using the `message/send` method with a `skillId` parameter, as demonstrated in [`tests/unit/t09-a2a-lifecycle.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/tests/unit/t09-a2a-lifecycle.test.ts).
- The **CLI interface** for A2A operations is available through `bin/cli/commands/a2a.mjs`.

## Frequently Asked Questions

### What is the difference between A2A skills and regular API endpoints in OmniRoute?

A2A skills follow a specific JSON-RPC 2.0 protocol defined by the Agent-to-Agent specification, including standardized task schemas and agent cards, whereas regular API endpoints typically use REST conventions. A2A skills are registered through the centralized registry in [`src/lib/a2a/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/registry.ts) and support asynchronous agent-to-agent messaging patterns.

### How do I validate input data for an A2A skill?

Input validation occurs through the `schema` property of your skill definition, which should match the Zod contracts defined in [`open-sse/mcp-server/schemas/a2a.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/mcp-server/schemas/a2a.ts) (such as `TaskInputSchema`). The MCP server automatically validates incoming JSON-RPC payloads against these schemas before invoking your handler function.

### Can I register multiple skills in a single OmniRoute instance?

Yes. You can define multiple skill objects and register each via separate registration functions or a combined initialization function. The `initA2ASkills(registry)` pattern shown in [`src/lib/a2a/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/a2a/registry.ts) allows you to register any number of skills—such as `memory_aware_routing` and `quick_lookup`—within the same server lifecycle.

### Where can I find working examples of A2A skill testing?

The repository includes comprehensive unit tests in [`tests/unit/t09-a2a-lifecycle.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/tests/unit/t09-a2a-lifecycle.test.ts) that demonstrate the complete request/response flow, including skill registration, task creation, and completion handling. These tests serve as executable documentation for the A2A protocol implementation in OmniRoute.