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-inlineconst { 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 demandAPI 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";
}