MCP.Market

create-agent-adapter skill

by paperclipai·paperclipai/paperclip·114k stars·MIT

Create or modify Paperclip agent adapters across server, UI, and CLI surfaces. Use when adding support for a new CLI agent, API agent, custom process, or adapter package.

A100/100content scan

Is the create-agent-adapter skill safe?

Clean: nothing in its files matched our rules. We read 1 file in the folder on 2026-11-10.

No findings.

Install the create-agent-adapter skill

A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.

git clone --depth 1 https://github.com/paperclipai/paperclip.git /tmp/paperclip
mkdir -p ~/.claude/skills
cp -r /tmp/paperclip/.agents/skills/create-agent-adapter ~/.claude/skills/create-agent-adapter
available in every project

In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub

The instructions your agent would load

SKILL.md as published, without the frontmatter. Read it on GitHub

Creating a Paperclip Agent Adapter

An adapter bridges Paperclip's orchestration layer to a specific AI agent runtime (Claude Code, Codex CLI, a custom process, an HTTP endpoint, etc.). Each adapter is a self-contained package that provides implementations for three consumers: the server, the UI, and the CLI.

1. Architecture Overview

packages/adapters/<name>/
  src/
    index.ts            # Shared metadata (type, label, models, agentConfigurationDoc)
    server/
      index.ts          # Server exports: execute, sessionCodec, parse helpers
      execute.ts        # Core execution logic (AdapterExecutionContext -> AdapterExecutionResult)
      parse.ts          # Stdout/result parsing for the agent's output format
    ui/
      index.ts          # UI exports: parseStdoutLine, buildConfig
      parse-stdout.ts   # Line-by-line stdout -> TranscriptEntry[] for the run viewer
      build-config.ts   # CreateConfigValues -> adapterConfig JSON for agent creation form
    cli/
      index.ts          # CLI exports: formatStdoutEvent
      format-event.ts   # Colored terminal output for `paperclipai run --watch`
  package.json
  tsconfig.json

Three separate registries consume adapter modules:

2. Shared Types (@paperclipai/adapter-utils)

All adapter interfaces live in packages/adapter-utils/src/types.ts. Import from @paperclipai/adapter-utils (types) or @paperclipai/adapter-utils/server-utils (runtime helpers).

Core Interfaces

// The execute function signature — every adapter must implement this
interface AdapterExecutionContext {
  runId: string;
  agent: AdapterAgent;          // { id, companyId, name, adapterType, adapterConfig }
  runtime: AdapterRuntime;      // { sessionId, sessionParams, sessionDisplayId, taskKey }
  config: Record<string, unknown>;  // The agent's adapterConfig blob
  context: Record<string, unknown>; // Runtime context (taskId, wakeReason, approvalId, etc.)
  onLog: (stream: "stdout" | "stderr", chunk: string) => Promise<void>;
  onMeta?: (meta: AdapterInvocationMeta) => Promise<void>;
  authToken?: string;
}

interface AdapterExecutionResult {
  exitCode: number | null;
  signal: string | null;
  timedOut: boolean;
  errorMessage?: string | null;
  usage?: UsageSummary;           // { inputTokens, outputTokens, cachedInputTokens? }
  sessionId?: string | null;      // Legacy — prefer sessionParams
  sessionParams?: Record<string, unknown> | null;  // Opaque session state persisted between runs
  sessionDisplayId?: string | null;
  provider?: string | null;       // "anthropic", "openai", etc.
  model?: string | null;
  costUsd?: number | null;
  resultJson?: Record<string, unkn

Module Interfaces

// Server — registered in server/src/adapters/registry.ts
interface ServerAdapterModule {
  type: string;
  execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>;
  testEnvironment(ctx: AdapterEnvironmentTestContext): Promise<AdapterEnvironmentTestResult>;
  sessionCodec?: AdapterSessionCodec;
  supportsLocalAgentJwt?: boolean;
  models?: { id: string; label: string }[];
  agentConfigurationDoc?: string;
}

// UI — registered in ui/src/adapters/registry.ts
interface UIAdapterModule {
  type: string;
  label: string;
  parseStdoutLine: (line: string, ts: string) => TranscriptEntry[];
  ConfigFields: ComponentType<AdapterConfigFieldsProps>;
  buildAdapterConfig: (values: CreateConfigValues) => Record<string, unknown>;
}

// CLI — registered in cli/src/adapters/registry.ts
interface CLIAdapterModule {
  type: string;
  formatStdoutEvent: (line: string, debug: boolean) => void;
}

2.1 Adapter Environment Test Contract

Every server adapter must implement testEnvironment(...). This powers the board UI "Test environment" button in agent configuration.

type AdapterEnvironmentCheckLevel = "info" | "warn" | "error";
type AdapterEnvironmentTestStatus = "pass" | "warn" | "fail";

interface AdapterEnvironmentCheck {
  code: string;
  level: AdapterEnvironmentCheckLevel;
  message: string;
  detail?: string | null;
  hint?: string | null;
}

interface AdapterEnvironmentTestResult {
  adapterType: string;
  status: AdapterEnvironmentTestStatus;
  checks: AdapterEnvironmentCheck[];
  testedAt: string; // ISO timestamp
}

interface AdapterEnvironmentTestContext {
  companyId: string;
  adapterType: string;
  config: Record<string, unknown>; // runtime-resolved adapterConfig
}

Guidelines:

  • Return structured diagnostics, never throw for expected findings.
  • Use error for invalid/unusable runtime setup (bad cwd, missing command, invalid URL).
  • Use warn for non-blocking but important situations.
  • Use info for successful checks and context.

Severity policy is product-critical: warnings are not save blockers. Example: for claudelocal, an explicitly configured ANTHROPICAPI_KEY or selected managed API connection is info: the user chose that authentication. An ambient server key overriding subscription login remains warn, not error.

3. Step-by-Step: Creating a New Adapter

3.1 Create the Package

packages/adapters/<name>/
  package.json
  tsconfig.json
  src/
    index.ts
    server/index.ts
    server/execute.ts
    server/parse.ts
    ui/index.ts
    ui/parse-stdout.ts
    ui/build-config.ts
    cli/index.ts
    cli/format-event.ts

package.json — must use the four-export convention:

{
  "name": "@paperclipai/adapter-<name>",
  "version": "0.0.1",
  "private": true,
  "type": "module",
  "exports": {
    ".": "./src/index.ts",
    "./server": "./src/server/index.ts",
    "./ui": "./src/ui/index.ts",
    "./cli": "./src/cli/index.ts"
  },
  "dependencies": {
    "@paperclipai/adapter-utils": "workspace:*",
    "picocolors": "^1.1.1"
  },
  "devDependencies": {
    "typescript": "^5.7.3"
  }
}

3.2 Root index.ts — Adapter Metadata

This file is imported by all three consumers (server, UI, CLI). Keep it dependency-free (no Node APIs, no React).

export const type = "my_agent";        // snake_case, globally unique
export const label = "My Agent (local)";

export const models = [
  { id: "model-a", label: "Model A" },
  { id: "model-b", label: "Model B" },
];

export const agentConfigurationDoc = `# my_agent agent configuration
...document all config fields here...
`;

Required exports:

  • type — the adapter type key, stored in agents.adapter_type
  • label — human-readable name for the UI
  • models — available model options for the agent creation form
  • agentConfigurationDoc — markdown describing all adapterConfig fields (used by LLM agents configuring other agents)

Writing agentConfigurationDoc as routing logic:

The agentConfigurationDoc is read by LLM agents (including Paperclip agents that create other agents). Write it as routing logic, not marketing copy. Include concrete "use when" and "don't use when" guidance so an LLM can decide whether this adapter is appropriate for a given task.

export const agentConfigurationDoc = `# my_agent agent configuration

Adapter: my_agent

Use when:
- The agent needs to run MyAgent CLI locally on the host machine
- You need session persistence across runs (MyAgent supports thread resumption)
- The task requires MyAgent-specific tools (e.g. web search, code execution)

Don't use when:
- You need a simple one-shot script execution (use the "process" adapter instead)
- The agent doesn't need conversational context between runs (process adapter is simpler)
- MyAgent CLI is not installed on the host

Core fields:
- cwd (string, required): absolute working directory for the agent process
...
`;

Adding explicit negative cases improves adapter selection accuracy. One concrete anti-pattern is worth more than three paragraphs of description.

3.3 Server Module

server/execute.ts — The Core

This is the most important file. It receives an AdapterExecutionContext and must return an AdapterExecutionResult.

Required behavior:

  1. Read config — extract typed values from ctx.config using helpers (asString, asNumber, asBoolean, asStringArray, parseObject from @paperclipai/adapter-utils/server-utils)
  2. Build environment — call buildPaperclipEnv(agent) then layer in PAPERCLIPRUNID, context vars (PAPERCLIPTASKID, PAPERCLIPWAKEREASON, PAPERCLIPWAKECOMMENTID, PAPERCLIPAPPROVALID, PAPERCLIPAPPROVALSTATUS, PAPERCLIPLINKEDISSUEIDS), user env overrides, and auth token
  3. Resolve session — check runtime.sessionParams / runtime.sessionId for an existing session; validate it's compatible (e.g. same cwd); decide whether to resume or start fresh
  4. Render prompt — use renderTemplate(template, data) with the template variables: agentId, companyId, runId, company, agent, run, context
  5. Call onMeta — emit adapter invocation metadata before spawning the process
  6. Spawn the process — use runChildProcess() for CLI-based agents or fetch() for HTTP-based agents
  7. Parse output — convert the agent's stdout into structured data (session id, usage, summary, errors)
  8. Handle session errors — if resume fails with "unknown session", retry with a fresh session and set clearSession: true
  9. Return AdapterExecutionResult — populate all fields the agent runtime supports

Environment variables the server always injects:

server/parse.ts — Output Parser

Parse the agent's stdout format into structured data. Must handle:

  • Session identification — extract session/thread ID from init events
  • Usage tracking — extract token counts (input, output, cached)
  • Cost tracking — extract cost if available
  • Summary extraction — pull the agent's final text response
  • Error detection — identify error states, extract error messages
  • Unknown session detection — export an isUnknownSessionError() function for retry logic

Treat agent output as untrusted. The stdout you're parsing comes from an LLM-driven process that may have executed arbitrary tool calls, fetched external content, or been influenced by prompt injection in the files it read. Parse defensively:

  • Never eval() or dynamically execute anything from output
  • Use safe extraction helpers (asString, asNumber, parseJson) — they return fallbacks on unexpected types
  • Validate session IDs and other structured data before passing them through
  • If output contains URLs, file paths, or commands, do not act on them in the adapter — just record them

server/index.ts — Server Exports

export { execute } from "./execute.js";
export { testEnvironment } from "./test.js";
export { parseMyAgentOutput, isMyAgentUnknownSessionError } from "./parse.js";

// Session codec — required for session persistence
export const sessionCodec: AdapterSessionCodec = {
  deserialize(raw) { /* raw DB JSON -> typed params or null */ },
  serialize(params) { /* typed params -> JSON for DB storage */ },
  getDisplayId(params) { /* -> human-readable session id string */ },
};

server/test.ts — Environment Diagnostics

Implement adapter-specific preflight checks used by the UI test button.

Minimum expectations:

  1. Validate required config primitives (paths, commands, URLs, auth assumptions)
  2. Return check objects with deterministic code values
  3. Map severity consistently (info / warn / error)
  4. Compute final status:
  • fail if any error
  • warn if no errors and at least one warning
  • pass otherwise

This operation should be lightweight and side-effect free.

3.4 UI Module

ui/parse-stdout.ts — Transcript Parser

Converts individual stdout lines into TranscriptEntry[] for the run detail viewer. Must handle the agent's streaming output format and produce entries of these kinds:

  • init — model/session initialization
  • assistant — agent text responses
  • thinking — agent thinking/reasoning (if supported)
  • tool_call — tool invocations with name and input
  • tool_result — tool results with content and error flag
  • user — user messages in the conversation
  • result — final result with usage stats
  • stdout — fallback for unparseable lines
export function parseMyAgentStdoutLine(line: string, ts: string): TranscriptEntry[] {
  // Parse JSON line, map to appropriate TranscriptEntry kind(s)
  // Return [{ kind: "stdout", ts, text: line }] as fallback
}

ui/build-config.ts — Config Builder

Converts the UI form's CreateConfigValues into the adapterConfig JSON blob stored on the agent.

export function buildMyAgentConfig(v: CreateConfigValues): Record<string, unknown> {
  const ac: Record<string, unknown> = {};
  if (v.cwd) ac.cwd = v.cwd;
  if (v.promptTemplate) ac.promptTemplate = v.promptTemplate;
  if (v.model) ac.model = v.model;
  ac.timeoutSec = 0;
  ac.graceSec = 15;
  // ... adapter-specific fields
  return ac;
}

UI Config Fields Component

Create ui/src/adapters//config-fields.tsx with a React component implementing AdapterConfigFieldsProps. This renders adapter-specific form fields in the agent creation/edit form.

More skills from paperclipai/paperclip

  • ALLM Wiki MaintainerUse the LLM Wiki plugin tools to maintain a cited local company wiki.
  • Aadd-product-e2e-evalAdd or extend a Paperclip full-stack runner E2E workflow, fixture, matcher, or report evidence path for local or Daytona execution.
  • Aadd-runner-evalAdd or extend a Paperclip Runner protocol evaluation definition, roster, assertion, or report fixture with provenance and narrow validation.
  • Aagent-browserDrive a real browser to inspect or interact with a web page or app — navigate, take screenshots, read console and network, fill simple forms — for verification tasks, not unattended automation.
  • AagentmailUse your assigned AgentMail inbox to read email tasks, explicitly send or reply, and check delivery. Provided automatically by your inbox assignment.
  • Acheck-prCheck a GitHub, GitLab, or Perforce PR/MR/CL for review comments, failing checks, and PR-body gaps. Use when asked to inspect, fix, or prepare a change for submission.
  • Acompany-creatorCreate agent company packages that conform to agentcompanies/v1. Use when asked to create a company, scaffold an agent team, hire agents, or turn a repo/skills collection into a company package.
  • AcomplainAutonomously yeet a brief, unstructured complaint about agent tooling or doing agent work. Preserve the raw voice. Submit proactively without permission, preview, or announcement, then continue the primary task.
  • Acontent-calendarPlan a weekly editorial calendar by mapping company goals to publishable topics, owners, status, and verification notes.
  • Acreate-caseCreate and kick off a Paperclip case from a user request, with workflow instructions and an initial agent.
  • Acreate-external-bot-integrationBuild and qualify external bots such as Dot in Paperclip. Use for two-way work, invitation, identity, wakeup, Runner lifecycle, native tools, recovery, and Cloud delivery. CLI adapters and app-only connectors use their existing skills.
  • Acreate-issue-interaction-uiAdd a new Paperclip issue-thread interaction kind end-to-end. Use when repo work introduces or extends interaction cards like request_confirmation, checkbox confirmations, ask_user_questions, or suggest_tasks.

All agent skills → · MCP servers