Server-side Skills

Load skills from files and URLs on the server with loadSkills()

Experimental. APIs may change before they are stabilized.

For file and url skill sources, or when you need server-controlled skill loading, use loadSkills() in your API route.


Basic Setup

// app/api/chat/route.ts
import path from "path";
import { loadSkills } from "@yourgpt/copilot-sdk/server";

export async function POST(req: Request) {
  const { messages, __skills } = await req.json();

  const { buildSystemPrompt, tools } = await loadSkills({
    // Source 1: .md files from a local directory (highest precedence)
    dir: path.join(process.cwd(), "skills"),

    // Source 2: Remote .md URLs
    remoteUrls: ["https://cdn.myapp.com/skills/support-policy.md"],

    // Source 3: Inline skills forwarded from client (lowest precedence)
    clientSkills: __skills ?? [],
  });

  return streamText({
    model: anthropic("claude-sonnet-4-6"),
    system: buildSystemPrompt("You are a helpful assistant for Acme Corp."),
    messages,
    tools: {
      ...tools,         // includes load_skill automatically
      ...myOtherTools,
    },
  }).toDataStreamResponse();
}

loadSkills Options

interface LoadSkillsOptions {
  dir?: string;                     // Path to /skills directory (Node.js only)
  remoteUrls?: string[];            // Remote .md URLs to fetch
  clientSkills?: ClientInlineSkill[]; // Forwarded from useSkill() hooks
}

loadSkills Result

interface LoadSkillsResult {
  skills: ResolvedSkill[];
  diagnostics: SkillDiagnostic[];

  // Build system prompt: prepends eager content, appends auto catalog
  buildSystemPrompt(basePrompt?: string): string;

  // Ready-to-use load_skill tool definition
  tools: {
    load_skill: ToolDefinition;
  };
}

Forwarding Client Skills

<SkillProvider> automatically syncs inline skills to CopilotProvider, which includes them in every API request as __skills. Read them in your route:

const { messages, __skills } = await req.json();

const { buildSystemPrompt, tools } = await loadSkills({
  dir: path.join(process.cwd(), "skills"),
  clientSkills: __skills ?? [],
});

Source Precedence & Collision Detection

When the same skill name appears in multiple sources, the higher-precedence source wins:

server-dir  >  remote-url  >  client-inline
const { diagnostics } = await loadSkills({ ... });

// [{
//   type: "collision",
//   name: "code-review",
//   winner: "server-dir",
//   loser: "client-inline",
// }]
if (diagnostics.length) {
  console.warn("Skill collisions:", diagnostics);
}

This lets you safely override client-provided skills with authoritative server versions.


Full Example

Project structure

skills/
├── brand-voice.md     # eager — always active
└── sql-expert.md      # auto — loaded on demand

API route

// app/api/chat/route.ts
import path from "path";
import { loadSkills } from "@yourgpt/copilot-sdk/server";
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

export async function POST(req: Request) {
  const { messages, __skills } = await req.json();

  const { buildSystemPrompt, tools } = await loadSkills({
    dir: path.join(process.cwd(), "skills"),
    clientSkills: __skills ?? [],
  });

  return streamText({
    model: anthropic("claude-sonnet-4-6"),
    system: buildSystemPrompt("You are a helpful assistant for Acme Corp."),
    messages,
    tools,
  }).toDataStreamResponse();
}

Type Reference

type SkillStrategy = "eager" | "auto" | "manual";

type SkillSource =
  | { type: "inline"; content: string }
  | { type: "url"; url: string }
  | { type: "file"; path: string };

interface ResolvedSkill {
  name: string;
  description: string;
  content: string;
  strategy?: SkillStrategy;
  version?: string;
}

interface SkillDiagnostic {
  type: "collision";
  name: string;
  winner: "server-dir" | "remote-url" | "client-inline";
  loser: "server-dir" | "remote-url" | "client-inline";
}

On this page