Server-side Skills

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

Beta — This feature is in alpha. APIs may change before stable release.

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