Skip to content
omp
Docs ↗ ↗
docs · Authoring MCP servers▾
Documentation
Start
  • Overview
  • Quickstart
  • Using omp
  • Slash commands
  • Keybindings
  • Settings
  • Run modes
  • Sessions
  • Session tree
  • Memory
  • Compaction
  • Plan mode
  • Goal mode
  • Handoff
Capabilities
  • Working with files
  • Code intelligence
  • Debugging
  • Structural edits
  • Code review
  • Creating commits
  • Security scans
  • Subagents
  • Advisor models
  • Vibe mode
  • Collab
  • Web & browser
  • Computer control
  • GitHub
Models
  • Providers
  • Model roles
  • Agents & model roles
  • Custom models & providers
  • Prewalk
Customization
  • Context files
  • Skills
  • Prompt templates
  • Magic keywords
  • Hooks
  • Custom tools
  • Authoring subagents
  • MCP
  • Authoring MCP servers
  • Themes
  • TTSR rules
  • Plugins
  • Authoring extensions
  • Marketplaces
Programmatic
  • SDK
  • RPC mode
  • ACP
Reference
  • CLI reference
  • Environment variables
  • Secrets and auth
  • Tool approvals
  • Session format
  • Tools index
Documentation
Start
  • Overview
  • Quickstart
  • Using omp
  • Slash commands
  • Keybindings
  • Settings
  • Run modes
  • Sessions
  • Session tree
  • Memory
  • Compaction
  • Plan mode
  • Goal mode
  • Handoff
Capabilities
  • Working with files
  • Code intelligence
  • Debugging
  • Structural edits
  • Code review
  • Creating commits
  • Security scans
  • Subagents
  • Advisor models
  • Vibe mode
  • Collab
  • Web & browser
  • Computer control
  • GitHub
Models
  • Providers
  • Model roles
  • Agents & model roles
  • Custom models & providers
  • Prewalk
Customization
  • Context files
  • Skills
  • Prompt templates
  • Magic keywords
  • Hooks
  • Custom tools
  • Authoring subagents
  • MCP
  • Authoring MCP servers
  • Themes
  • TTSR rules
  • Plugins
  • Authoring extensions
  • Marketplaces
Programmatic
  • SDK
  • RPC mode
  • ACP
Reference
  • CLI reference
  • Environment variables
  • Secrets and auth
  • Tool approvals
  • Session format
  • Tools index
loading…
§ 04Customization← MCPThemes →

Authoring MCP servers

Build a standards-based tool server, connect it to omp, and verify the handshake and tool calls end to end.

Build once, use it from many clients

An MCP server turns an API, local service, or workflow into tools that omp can discover and use. Choose MCP when the integration should also work with other MCP clients. For an omp-only integration, a custom tool is usually smaller.

The quickest path is a local stdio server. The official SDK handles JSON-RPC framing, version negotiation, and the initialization sequence, so your code can focus on tool schemas and behavior.

Build a minimal stdio server

Install the current TypeScript server SDK and Zod in your project:

npm install @modelcontextprotocol/server zod

Save this as mcp/hello-server.mjs:

import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";

const server = new McpServer({
  name: "hello",
  version: "1.0.0",
});

server.registerTool(
  "greet",
  {
    description: "Greet one person by name.",
    inputSchema: z.object({
      name: z.string().min(1).describe("The person's name"),
    }),
  },
  async ({ name }) => ({
    content: [{ type: "text", text: `Hello, ${name}!` }],
  }),
);

await server.connect(new StdioServerTransport());

Do not print logs to stdout in a stdio server. stdout carries newline-delimited MCP messages; one debug line can corrupt the connection. Write diagnostics to stderr instead.

Connect it to omp

Add a project config at .omp/mcp.json:

{
  "$schema": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json",
  "mcpServers": {
    "hello": {
      "type": "stdio",
      "command": "node",
      "args": ["./mcp/hello-server.mjs"]
    }
  }
}

Start omp in that project, then run:

/mcp reload
/mcp test hello

/mcp test hello creates a temporary connection, completes initialization, calls tools/list, and reports the server identity and discovered tool names. It does not call a tool. Exercise the handler with a normal request:

Use the hello server to greet Ada.

You should see Hello, Ada!. The child process starts in the omp project directory unless the server config sets cwd, so relative args above resolve from the project root.

Choose a transport

TransportUse it forServer-side contractomp config
stdioA local server launched for one omp sessionRead JSON-RPC from stdin, write newline-delimited JSON-RPC to stdout, and keep diagnostics off stdoutOmit type or use "type": "stdio"; set command, with optional args, env, and cwd
Streamable HTTPA daemon or hosted, shared serviceAccept MCP JSON-RPC over HTTP POST; support JSON or SSE responses as required by the MCP transport specUse "type": "http" and url; headers and OAuth are optional
Legacy HTTP+SSEAn existing server built for the 2024-11-05 transportOpen an SSE stream that announces the JSON-RPC POST endpointUse "type": "sse" and url; do not choose this for a new server

stdio is the default when type is absent. A remote entry without "type": "http" is therefore treated as stdio and fails because it has no command.

Publish the same tool over Streamable HTTP

The current SDK can serve both current and 2025-era clients. For a small Node-hosted endpoint, install its Node adapter:

npm install @modelcontextprotocol/node

A minimal loopback server looks like this:

import { createServer } from "node:http";
import {
  localhostHostValidation,
  localhostOriginValidation,
  toNodeHandler,
} from "@modelcontextprotocol/node";
import { createMcpHandler, McpServer } from "@modelcontextprotocol/server";
import * as z from "zod/v4";

const handler = createMcpHandler(() => {
  const server = new McpServer({ name: "hello-http", version: "1.0.0" });
  server.registerTool(
    "greet",
    {
      description: "Greet one person by name.",
      inputSchema: z.object({ name: z.string().min(1) }),
    },
    async ({ name }) => ({
      content: [{ type: "text", text: `Hello, ${name}!` }],
    }),
  );
  return server;
});

const nodeHandler = toNodeHandler(handler);
const validateHost = localhostHostValidation();
const validateOrigin = localhostOriginValidation();

createServer((request, response) => {
  if (!validateHost(request, response) || !validateOrigin(request, response)) return;
  void nodeHandler(request, response);
}).listen(3000, "127.0.0.1");

Configure the running endpoint:

{
  "mcpServers": {
    "hello-http": {
      "type": "http",
      "url": "http://127.0.0.1:3000/mcp"
    }
  }
}

For a public deployment, validate Host and Origin, authenticate before dispatching to the MCP handler, use TLS, and pass only verified identity to tool code. The handler does not authenticate requests for you. See the SDK's HTTP serving guide and MCP for omp header and OAuth configuration.

Public protocol contract

Using an MCP SDK is strongly recommended. If you implement the wire protocol yourself, these are the interoperability points omp relies on:

PhaseWhat the server must do
initializeAccept a JSON-RPC 2.0 request. omp currently offers MCP protocol revision 2025-11-25. Reply with the negotiated protocolVersion, serverInfo, and capabilities.
notifications/initializedAccept the notification before expecting later session traffic. Notifications have no request ID and receive no response.
tools/listWhen capabilities.tools is declared, return { "tools": [...] }. If the result has nextCursor, omp follows it until all pages are loaded.
tools/callAccept { "name": string, "arguments"?: object } and return a tool result. Echo each JSON-RPC request ID exactly; valid IDs may be numbers or strings.
Server requestsomp answers ping and roots/list. roots/list returns the active project as a file: URI. Do not assume a client supports other server-to-client methods unless it advertised them.
Tool changesIf tools can change, advertise tools.listChanged and send notifications/tools/list_changed after initialization. omp then reloads that server's tool list without a session restart.

For Streamable HTTP, preserve Mcp-Session-Id when you issue one and accept MCP-Protocol-Version: 2025-11-25 on requests after initialization. Support the HTTP methods, status codes, media types, and optional SSE channel defined by the public MCP transport specification. The official SDK handles these details.

omp sends numeric JSON-RPC request IDs by default. A server should accept both JSON-RPC-permitted forms; requiring one representation reduces compatibility with other clients.

Define tools that travel well

A listed tool needs a stable name, a concrete description, and an inputSchema whose top level is a JSON Schema object. With the SDK above, a Standard Schema library such as Zod produces that schema for you.

Good schemas help the client select the tool and construct valid arguments:

  • Describe the user-visible action and when it is appropriate.
  • Use explicit property descriptions, bounds, enums, and required fields.
  • Keep optional fields genuinely optional; do not require clients to send empty placeholders.
  • Validate permissions and inputs again in the handler. A schema is guidance and validation, not authorization.
  • Keep names stable once users depend on them.

omp namespaces discovered tools by server, lowercases their names, replaces characters outside [a-z_] with underscores, and caps the mounted name at 64 characters with a stable hash suffix when needed. Distinct raw names that normalize identically collide; omp keeps one deterministic winner. Prefer short, distinct lowercase names such as search_issues or create_invoice. Users ask for the capability in natural language rather than typing the mounted name.

Return results and errors

Every successful call should return content. Text is the most portable content type:

return {
  content: [{ type: "text", text: "Created invoice INV-1042." }],
};

omp also accepts MCP image content (data as base64 plus mimeType) and embedded resource content. If you also return structuredContent, include a useful text representation in content; do not make the human-readable result depend on structured output support.

Use isError: true for an expected, user-correctable failure. This keeps the failure attached to the tool call and gives the model information it can act on:

return {
  content: [{ type: "text", text: "Customer C-17 does not exist." }],
  isError: true,
};

Reserve thrown exceptions or JSON-RPC error responses for protocol violations, unavailable dependencies, and unexpected server faults. omp displays expected isError results as tool errors; transport and JSON-RPC failures surface as MCP errors. Do not include secrets, tokens, or private stack traces in either form.

For protected HTTP tools, a standards-based authorization challenge may be returned with an error in _meta["mcp/www_authenticate"]. omp recognizes that public MCP challenge and can reauthorize before retrying when the server is configured for OAuth.

Debugging loop

  1. Test the server independently. For the stdio example, run node --check mcp/hello-server.mjs, then use the official inspector:

    npx @modelcontextprotocol/inspector node ./mcp/hello-server.mjs
    

    Confirm that greet appears and call it with { "name": "Ada" }.

  2. Reload discovery. After editing .omp/mcp.json, run /mcp reload, then /mcp list to confirm the server name and source file.

  3. Test discovery and schemas. Run /mcp test hello. It verifies initialization and tools/list, but not the handler itself.

  4. Test the real user path. Ask omp to perform the action, then inspect the returned content. For dynamic servers, /mcp notifications helps confirm list-change notifications.

  5. Restart only what changed. Use /mcp reconnect hello after changing local server code. Use /mcp reload after changing discovery or config.

SymptomCheck
ENOENT or command not foundVerify command, the project-relative path, executable permissions, and cwd.
Test hangs or times outEnsure the server answers initialize, then accepts notifications/initialized and tools/list. The default request timeout is 30 seconds; fix the stall before increasing timeout.
Invalid JSON or immediate disconnectRemove every stdout log/banner. Check that each stdio frame is one complete JSON-RPC object followed by a newline.
Test connects but a real call fails/mcp test does not call tools. Reproduce with the Inspector and return an actionable isError result for domain failures.
Tool changes do not appearReconnect a restarted stdio process, or advertise tools.listChanged and send notifications/tools/list_changed.
HTTP 401 or 403Verify server-side token validation and the client's header/OAuth config; use /mcp reauth <name> for managed OAuth.
Tool is missing although the server lists itLook for a normalized-name collision, an invalid top-level input schema, or another server definition shadowing the same name.

Ship checklist

  • The server negotiates 2025-11-25 with omp and does not send session traffic before initialization completes.
  • stdio stdout contains protocol frames only; hosted HTTP has TLS, authentication, and Host/Origin validation.
  • tools/list returns stable names, descriptions, object input schemas, and every pagination cursor correctly.
  • Every tools/call path returns useful content; expected failures return isError: true.
  • The Inspector can list and call each tool, and /mcp test <name> reports the same inventory.
  • A natural-language request in omp exercises the real handler successfully.

Related

  • MCP — config locations, discovery, transports, credentials, and lifecycle commands.
  • Custom tools — omp-only alternative when cross-client compatibility is unnecessary.
  • Plugins — package MCP config with skills, commands, and hooks.
  • MCP specification — public protocol and transport contract.
  • MCP TypeScript SDK — current server APIs and examples.