Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Let your users do this: `npx skills add https://your-website-here.com/`

Bundle [Agent Skills](https://agentskills.io/) into your Astro site, for others to consume by URL. This integration implements the [Agent Skills Discovery RFC](https://github.com/elithrar/agent-skills-discovery-rfc), allowing AI agents to discover and use skills published on your website.

- Automatically generates your `/.well-known/skills/index.json` index file.
- Automatically generates your `/.well-known/agent-skills/index.json` index file.
- Validates your skills, frontmatter, etc. for compliance.
- Designed for Astro [Content Collections](https://docs.astro.build/en/guides/content-collections/).

Expand Down Expand Up @@ -36,6 +36,44 @@ export default defineConfig({
});
```

### Experimental MCP/SEP Skill Resources

To also publish skills as static MCP resource artifacts, enable the experimental MCP mode:

```ts
// astro.config.mjs
import { defineConfig } from 'astro/config';
import skills from 'astro-skills';

export default defineConfig({
integrations: [
skills({
mcp: {
prefix: '/.well-known/mcp/skills',
resourceBase: 'skill://',
directoryManifest: true,
archives: true,
},
}),
],
});
```

This generates:

- `/.well-known/mcp/skills/index.json`
- `/.well-known/mcp/skills/.tree.json`
- Direct static routes for every file in every skill directory
- `.tar.gz` archive resources for multi-file skills

The existing `/.well-known/agent-skills/index.json` discovery output remains enabled, so one Astro site can support both the Agent Skills well-known discovery proposal and SEP-2640/MCP resource publication at the same time.

The MCP index follows the SEP-2640 draft shape: it uses `skill://.../SKILL.md` resource URLs, includes the raw `SKILL.md` SHA-256 digest when `url` is present, and copies the complete `SKILL.md` frontmatter into each `skills[].frontmatter` entry. Multi-file skills also include `archives[]` alternatives whose digests are computed from the generated archive bytes.

The generated `.tree.json` file is a static-host helper, not part of SEP-2640 itself. It lists directory and file resource metadata so an MCP server can implement `resources/directory/read` without rescanning the filesystem at request time.

During static builds, `astro-skills` also writes an `_headers` block for generated skill artifacts so hosts that support `_headers` serve JSON, Markdown, and archive files with the expected content types.

## Configuration

To get started, create a `skills/` directory in your project root with your skills:
Expand Down
11 changes: 10 additions & 1 deletion example/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,14 @@ import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
integrations: [skills()],
integrations: [
skills({
mcp: {
prefix: '/.well-known/mcp/skills',
resourceBase: 'skill://',
directoryManifest: true,
archives: true,
},
}),
],
});
5 changes: 3 additions & 2 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^5.0.0"
"astro": "^5.0.0",
"astro-skills": "file:.."
}
}
}
10 changes: 10 additions & 0 deletions example/skills/acme/billing/refunds/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: refunds
description: Process billing refunds for Acme customers.
metadata:
owner: billing
---

# Refunds

Use this skill when reviewing or processing a customer refund request.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
"exports": {
".": "./dist/index.js",
"./routes/index-json": "./dist/routes/index-json.js",
"./routes/agent-skill-resource": "./dist/routes/agent-skill-resource.js",
"./routes/skill-md": "./dist/routes/skill-md.js",
"./routes/skill-archive": "./dist/routes/skill-archive.js",
"./routes/mcp": "./dist/routes/mcp.js",
"./package.json": "./package.json"
},
"files": [
Expand Down
84 changes: 72 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import type { AstroIntegration } from 'astro';
import { resolveSkillsMcpOptions } from './mcp.js';
import { writeStaticHeaders } from './static-headers.js';
import type { ResolvedSkillsMcpOptions, SkillsIntegrationOptions } from './types.js';

// Re-export the loader for use in content.config.ts
export { skillsLoader } from './loader.js';

// Re-export types
export type { Skill, SkillData, SkillsIndex, SkillsIndexEntry, SkillsLoaderOptions, SkillType } from './types.js';
export type {
ResolvedSkillsMcpOptions,
Skill,
SkillData,
SkillFileData,
SkillFrontmatter,
SkillsIndex,
SkillsIndexEntry,
SkillsIntegrationOptions,
SkillsLoaderOptions,
SkillsMcpArchiveEntry,
SkillsMcpIndex,
SkillsMcpIndexEntry,
SkillsMcpOptions,
SkillsMcpTree,
SkillsMcpTreeDirectoryEntry,
SkillsMcpTreeEntry,
SkillsMcpTreeFileEntry,
SkillType,
} from './types.js';

const PKG_NAME = 'astro-skills';

Expand Down Expand Up @@ -42,11 +64,13 @@ const PKG_NAME = 'astro-skills';
*
* @see https://agentskills.io/
*/
export default function skillsIntegration(): AstroIntegration {
export default function skillsIntegration(options: SkillsIntegrationOptions = {}): AstroIntegration {
const mcpOptions = resolveSkillsMcpOptions(options.mcp);

return {
name: PKG_NAME,
hooks: {
'astro:config:setup': ({ injectRoute, logger }) => {
'astro:config:setup': ({ injectRoute, logger, updateConfig }) => {
logger.info('Setting up Agent Skills Discovery routes');

// Inject the index.json route
Expand All @@ -55,20 +79,56 @@ export default function skillsIntegration(): AstroIntegration {
entrypoint: 'astro-skills/routes/index-json',
});

// Inject the SKILL.md route for skill-md type skills
injectRoute({
pattern: '/.well-known/agent-skills/[skill]/SKILL.md',
entrypoint: 'astro-skills/routes/skill-md',
});

// Inject the archive route for archive type skills
// Inject the resource route for skill-md and archive type skills
injectRoute({
pattern: '/.well-known/agent-skills/[skill].tar.gz',
entrypoint: 'astro-skills/routes/skill-archive',
pattern: '/.well-known/agent-skills/[...path]',
entrypoint: 'astro-skills/routes/agent-skill-resource',
});

logger.info('Agent Skills Discovery routes configured');

if (mcpOptions) {
updateConfig({
vite: {
plugins: [mcpConfigPlugin(mcpOptions)],
},
});

injectRoute({
pattern: `${mcpOptions.prefix}/[...path]`,
entrypoint: 'astro-skills/routes/mcp',
});

logger.info(`Experimental MCP Skills routes configured at ${mcpOptions.prefix}`);
}
},
'astro:build:done': async ({ dir, logger }) => {
const entryCount = await writeStaticHeaders(dir, { mcp: mcpOptions });
if (entryCount > 0) {
logger.info(`Generated _headers entries for ${entryCount} skill artifact(s)`);
}
},
},
};
}

function mcpConfigPlugin(config: ResolvedSkillsMcpOptions) {
const virtualModuleId = 'astro-skills:mcp-config';
const resolvedVirtualModuleId = `\0${virtualModuleId}`;

return {
name: 'astro-skills:mcp-config',
resolveId(id: string) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
return undefined;
},
load(id: string) {
if (id === resolvedVirtualModuleId) {
return `export default ${JSON.stringify(config)};`;
}
return undefined;
},
};
}
Loading