mcps and skills
This commit is contained in:
@@ -217,6 +217,122 @@ curl -X POST {{BASE_URL}}/api/tools \
|
||||
|
||||
Same fields as create, all optional.
|
||||
|
||||
## MCP Servers
|
||||
|
||||
A directory of Model Context Protocol servers shown at `/mcp-servers` on the public site. Fields: `id`, `name`, `slug`, `description`, `url`, `docs_url`, `logo_url`, `category`, `category_label`, `install_command`, `config_example`, `transport`, `transport_label`, `language`, `capabilities` (array of strings), `tags` (array of strings), `views`, `clicks`, `featured`, `approved`, `submitted_by`, `created_at`, `updated_at`.
|
||||
|
||||
**An MCP server is only visible on the public site once `approved` is `true`.** New servers default to `approved: false` unless you explicitly set `approved: true` in the create request — this is a moderation gate, use it deliberately.
|
||||
|
||||
### `GET /api/mcp-servers`
|
||||
|
||||
Query params (all optional): `category` (one of the keys below), `approved` (`true`/`false`).
|
||||
|
||||
Valid `category` values: `official`, `filesystem`, `databases`, `developer-tools`, `productivity`, `search`, `browser-automation`, `communication`, `data-analytics`, `other`.
|
||||
|
||||
### `POST /api/mcp-servers`
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| `name` | string | yes | max 255 |
|
||||
| `slug` | string | no | must be unique; auto-generated from `name` if omitted |
|
||||
| `description` | string | yes | |
|
||||
| `url` | string (URL) | yes | link to the repository or homepage |
|
||||
| `docs_url` | string (URL) | no | dedicated documentation link, if different from `url` |
|
||||
| `logo_url` | string (URL) | no | |
|
||||
| `category` | string | yes | one of the category keys above |
|
||||
| `install_command` | string | no | e.g. `"npx -y @modelcontextprotocol/server-filesystem /path"` |
|
||||
| `config_example` | string | no | a config snippet for an MCP client (e.g. the `mcpServers` JSON block) |
|
||||
| `transport` | string | no | one of `stdio`, `sse`, `http` |
|
||||
| `language` | string | no | e.g. `"TypeScript"`, `"Python"` |
|
||||
| `capabilities` | array\<string\> | no | short bullet points of what it exposes, e.g. `["Read files", "Write files"]` |
|
||||
| `tags` | array\<string\> | no | e.g. `["official", "read-only"]` |
|
||||
| `featured` | boolean | no | defaults to `false` |
|
||||
| `approved` | boolean | no | defaults to `false` — see note above |
|
||||
|
||||
**Submit an MCP server listing:**
|
||||
|
||||
```bash
|
||||
curl -X POST {{BASE_URL}}/api/mcp-servers \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" -H "Accept: application/json" \
|
||||
-d '{
|
||||
"name": "Example Filesystem Server",
|
||||
"description": "Gives an MCP client read/write access to a local directory.",
|
||||
"url": "https://github.com/example/example-mcp-filesystem",
|
||||
"docs_url": "https://github.com/example/example-mcp-filesystem#readme",
|
||||
"category": "filesystem",
|
||||
"install_command": "npx -y @example/mcp-filesystem /path/to/dir",
|
||||
"config_example": "{\n \"mcpServers\": {\n \"filesystem\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@example/mcp-filesystem\", \"/path/to/dir\"]\n }\n }\n}",
|
||||
"transport": "stdio",
|
||||
"language": "TypeScript",
|
||||
"capabilities": ["Read files", "Write files", "List directories"],
|
||||
"tags": ["official", "filesystem"],
|
||||
"approved": true
|
||||
}'
|
||||
```
|
||||
|
||||
`views` and `clicks` are tracked automatically by the public site and cannot be set via the API.
|
||||
|
||||
### `GET /api/mcp-servers/{id}` · `PUT/PATCH /api/mcp-servers/{id}` · `DELETE /api/mcp-servers/{id}`
|
||||
|
||||
Same fields as create, all optional.
|
||||
|
||||
## Claude Skills
|
||||
|
||||
A directory of Claude Skills shown at `/skills` on the public site. Fields: `id`, `name`, `slug`, `description`, `url`, `docs_url`, `logo_url`, `category`, `category_label`, `install_instructions`, `example_prompt`, `requirements`, `tags` (array of strings), `views`, `clicks`, `featured`, `approved`, `submitted_by`, `created_at`, `updated_at`.
|
||||
|
||||
**A skill is only visible on the public site once `approved` is `true`.** New skills default to `approved: false` unless you explicitly set `approved: true` in the create request — this is a moderation gate, use it deliberately.
|
||||
|
||||
### `GET /api/skills`
|
||||
|
||||
Query params (all optional): `category` (one of the keys below), `approved` (`true`/`false`).
|
||||
|
||||
Valid `category` values: `document-processing`, `development`, `design`, `productivity`, `data-analysis`, `communication`, `other`.
|
||||
|
||||
### `POST /api/skills`
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|---|---|---|---|
|
||||
| `name` | string | yes | max 255 |
|
||||
| `slug` | string | no | must be unique; auto-generated from `name` if omitted |
|
||||
| `description` | string | yes | |
|
||||
| `url` | string (URL) | yes | link to the repository or homepage |
|
||||
| `docs_url` | string (URL) | no | dedicated documentation link, if different from `url` |
|
||||
| `logo_url` | string (URL) | no | |
|
||||
| `category` | string | yes | one of the category keys above |
|
||||
| `install_instructions` | string | no | e.g. `"Copy into ~/.claude/skills/ and restart Claude"` |
|
||||
| `example_prompt` | string | no | a sample prompt that would trigger/use this skill |
|
||||
| `requirements` | string | no | e.g. `"Claude Code"`, `"Claude with Code Execution tool"` |
|
||||
| `tags` | array\<string\> | no | e.g. `["official", "anthropic"]` |
|
||||
| `featured` | boolean | no | defaults to `false` |
|
||||
| `approved` | boolean | no | defaults to `false` — see note above |
|
||||
|
||||
**Submit a skill listing:**
|
||||
|
||||
```bash
|
||||
curl -X POST {{BASE_URL}}/api/skills \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" -H "Accept: application/json" \
|
||||
-d '{
|
||||
"name": "Example PDF Skill",
|
||||
"description": "Extracts text and tables from PDF files and can fill out PDF forms.",
|
||||
"url": "https://github.com/example/example-pdf-skill",
|
||||
"docs_url": "https://github.com/example/example-pdf-skill#readme",
|
||||
"category": "document-processing",
|
||||
"install_instructions": "Copy this folder into ~/.claude/skills/pdf/ and restart Claude.",
|
||||
"example_prompt": "Extract the line items from this invoice PDF into a table.",
|
||||
"requirements": "Claude Code",
|
||||
"tags": ["official", "pdf"],
|
||||
"approved": true
|
||||
}'
|
||||
```
|
||||
|
||||
`views` and `clicks` are tracked automatically by the public site and cannot be set via the API.
|
||||
|
||||
### `GET /api/skills/{id}` · `PUT/PATCH /api/skills/{id}` · `DELETE /api/skills/{id}`
|
||||
|
||||
Same fields as create, all optional.
|
||||
|
||||
## End-to-end example: publish a post as an agent
|
||||
|
||||
```bash
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
<nav class="flex shrink-0 gap-6 text-sm text-neutral-600">
|
||||
<a href="{{ route('blog.index') }}" class="hover:text-neutral-900 hover:underline underline-offset-4">Blog</a>
|
||||
<a href="{{ route('tools.index') }}" class="hover:text-neutral-900 hover:underline underline-offset-4">Tools</a>
|
||||
<a href="{{ route('mcp-servers.index') }}" class="hover:text-neutral-900 hover:underline underline-offset-4">MCP Servers</a>
|
||||
<a href="{{ route('skills.index') }}" class="hover:text-neutral-900 hover:underline underline-offset-4">Skills</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<article class="flex flex-col rounded-lg border border-neutral-200 p-5">
|
||||
<div class="flex items-center gap-3">
|
||||
@if ($server->logo_url)
|
||||
<img src="{{ $server->logo_url }}" alt="{{ $server->name }} logo" class="h-10 w-10 rounded-md object-cover" loading="lazy">
|
||||
@endif
|
||||
<div>
|
||||
<h3 class="font-semibold leading-tight">
|
||||
<a href="{{ route('mcp-servers.show', $server) }}" class="hover:underline">{{ $server->name }}</a>
|
||||
</h3>
|
||||
<a href="{{ route('mcp-servers.category', $server->category) }}" class="text-xs text-neutral-500 hover:text-neutral-900">
|
||||
{{ $server->category_label }}@if ($server->language) · {{ $server->language }} @endif
|
||||
</a>
|
||||
</div>
|
||||
@if ($server->featured)
|
||||
<span class="ml-auto rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800">Featured</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<p class="mt-3 flex-1 text-sm text-neutral-600">{{ str($server->description)->limit(120) }}</p>
|
||||
|
||||
<div class="mt-4 flex flex-wrap items-center justify-between gap-2">
|
||||
@if ($server->transport_label)
|
||||
<span class="rounded-full bg-neutral-100 px-2 py-0.5 text-xs text-neutral-700">{{ $server->transport_label }}</span>
|
||||
@endif
|
||||
<a
|
||||
href="{{ route('mcp-servers.show', $server) }}"
|
||||
class="ml-auto text-sm font-medium text-neutral-900 hover:underline"
|
||||
>
|
||||
View details →
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
@@ -0,0 +1,32 @@
|
||||
<article class="flex flex-col rounded-lg border border-neutral-200 p-5">
|
||||
<div class="flex items-center gap-3">
|
||||
@if ($skill->logo_url)
|
||||
<img src="{{ $skill->logo_url }}" alt="{{ $skill->name }} logo" class="h-10 w-10 rounded-md object-cover" loading="lazy">
|
||||
@endif
|
||||
<div>
|
||||
<h3 class="font-semibold leading-tight">
|
||||
<a href="{{ route('skills.show', $skill) }}" class="hover:underline">{{ $skill->name }}</a>
|
||||
</h3>
|
||||
<a href="{{ route('skills.category', $skill->category) }}" class="text-xs text-neutral-500 hover:text-neutral-900">
|
||||
{{ $skill->category_label }}
|
||||
</a>
|
||||
</div>
|
||||
@if ($skill->featured)
|
||||
<span class="ml-auto rounded-full bg-amber-100 px-2 py-0.5 text-xs font-medium text-amber-800">Featured</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<p class="mt-3 flex-1 text-sm text-neutral-600">{{ str($skill->description)->limit(120) }}</p>
|
||||
|
||||
<div class="mt-4 flex flex-wrap items-center justify-between gap-2">
|
||||
@if ($skill->requirements)
|
||||
<span class="rounded-full bg-neutral-100 px-2 py-0.5 text-xs text-neutral-700">{{ $skill->requirements }}</span>
|
||||
@endif
|
||||
<a
|
||||
href="{{ route('skills.show', $skill) }}"
|
||||
class="ml-auto text-sm font-medium text-neutral-900 hover:underline"
|
||||
>
|
||||
View details →
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
@@ -0,0 +1,47 @@
|
||||
<x-layouts.app :title="$title" :description="$description" :canonical="$canonical">
|
||||
@push('jsonld')
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@@context' => 'https://schema.org',
|
||||
'@type' => 'BreadcrumbList',
|
||||
'itemListElement' => [
|
||||
['@type' => 'ListItem', 'position' => 1, 'name' => 'MCP Servers', 'item' => route('mcp-servers.index')],
|
||||
['@type' => 'ListItem', 'position' => 2, 'name' => $label, 'item' => $canonical],
|
||||
],
|
||||
]) !!}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<p class="text-sm text-neutral-500">
|
||||
<a href="{{ route('mcp-servers.index') }}" class="hover:underline">MCP Servers</a>
|
||||
<span aria-hidden="true">/</span>
|
||||
{{ $label }}
|
||||
</p>
|
||||
<h1 class="mt-2 text-3xl font-bold tracking-tight">{{ $label }} MCP Servers</h1>
|
||||
|
||||
<nav class="mt-4 flex flex-wrap gap-2">
|
||||
<a href="{{ route('mcp-servers.index') }}" class="rounded-full bg-neutral-100 px-3 py-1 text-sm text-neutral-700 hover:bg-neutral-200">
|
||||
All
|
||||
</a>
|
||||
@foreach ($categories as $slug => $categoryLabel)
|
||||
<a
|
||||
href="{{ route('mcp-servers.category', $slug) }}"
|
||||
class="rounded-full px-3 py-1 text-sm {{ $category === $slug ? 'bg-neutral-900 text-white' : 'bg-neutral-100 text-neutral-700 hover:bg-neutral-200' }}"
|
||||
>
|
||||
{{ $categoryLabel }}
|
||||
</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
<div class="mt-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
@forelse ($servers as $server)
|
||||
<x-mcp-server-card :server="$server" />
|
||||
@empty
|
||||
<p class="text-neutral-500 sm:col-span-2">No MCP servers in this category yet.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
{{ $servers->links() }}
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,54 @@
|
||||
<x-layouts.app :title="$title" :description="$description" :canonical="$canonical">
|
||||
@push('jsonld')
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@@context' => 'https://schema.org',
|
||||
'@type' => 'CollectionPage',
|
||||
'name' => 'MCP Servers Directory',
|
||||
'url' => $canonical,
|
||||
]) !!}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<h1 class="text-3xl font-bold tracking-tight">MCP Servers Directory</h1>
|
||||
<p class="mt-2 text-neutral-600">A curated list of Model Context Protocol servers, with docs, install instructions, and config examples.</p>
|
||||
|
||||
<form action="{{ route('mcp-servers.index') }}" method="GET" class="mt-6">
|
||||
<input
|
||||
type="search"
|
||||
name="q"
|
||||
value="{{ $search }}"
|
||||
placeholder="Search MCP servers…"
|
||||
class="w-full rounded-lg border border-neutral-300 px-4 py-2 text-sm focus:border-neutral-900 focus:outline-none"
|
||||
>
|
||||
</form>
|
||||
|
||||
<nav class="mt-4 flex flex-wrap gap-2">
|
||||
<a
|
||||
href="{{ route('mcp-servers.index') }}"
|
||||
class="rounded-full px-3 py-1 text-sm {{ $activeCategory === '' ? 'bg-neutral-900 text-white' : 'bg-neutral-100 text-neutral-700 hover:bg-neutral-200' }}"
|
||||
>
|
||||
All
|
||||
</a>
|
||||
@foreach ($categories as $slug => $label)
|
||||
<a
|
||||
href="{{ route('mcp-servers.category', $slug) }}"
|
||||
class="rounded-full px-3 py-1 text-sm {{ $activeCategory === $slug ? 'bg-neutral-900 text-white' : 'bg-neutral-100 text-neutral-700 hover:bg-neutral-200' }}"
|
||||
>
|
||||
{{ $label }}
|
||||
</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
<div class="mt-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
@forelse ($servers as $server)
|
||||
<x-mcp-server-card :server="$server" />
|
||||
@empty
|
||||
<p class="text-neutral-500 sm:col-span-2">No MCP servers found.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
{{ $servers->links() }}
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,103 @@
|
||||
<x-layouts.app :title="$title" :description="$description" :canonical="$canonical" :image="$image ?? null">
|
||||
@push('jsonld')
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@@context' => 'https://schema.org',
|
||||
'@type' => 'SoftwareApplication',
|
||||
'name' => $server->name,
|
||||
'description' => $description,
|
||||
'url' => $server->url,
|
||||
...($server->logo_url ? ['image' => $server->logo_url] : []),
|
||||
'applicationCategory' => $server->category_label,
|
||||
...($server->language ? ['programmingLanguage' => $server->language] : []),
|
||||
]) !!}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<p class="text-sm text-neutral-500">
|
||||
<a href="{{ route('mcp-servers.index') }}" class="hover:underline">MCP Servers</a>
|
||||
<span aria-hidden="true">/</span>
|
||||
<a href="{{ route('mcp-servers.category', $server->category) }}" class="hover:underline">{{ $server->category_label }}</a>
|
||||
</p>
|
||||
|
||||
<div class="mt-4 flex items-start gap-4">
|
||||
@if ($server->logo_url)
|
||||
<img src="{{ $server->logo_url }}" alt="{{ $server->name }} logo" class="h-16 w-16 rounded-lg object-cover">
|
||||
@endif
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">{{ $server->name }}</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500">
|
||||
{{ $server->category_label }}
|
||||
@if ($server->language) · {{ $server->language }} @endif
|
||||
@if ($server->transport_label) · {{ $server->transport_label }} @endif
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-3">
|
||||
<a
|
||||
href="{{ route('mcp-servers.visit', $server) }}"
|
||||
target="_blank"
|
||||
rel="nofollow noopener"
|
||||
class="inline-block rounded-lg bg-neutral-900 px-5 py-2.5 text-sm font-medium text-white hover:bg-neutral-700"
|
||||
>
|
||||
View repository →
|
||||
</a>
|
||||
@if ($server->docs_url)
|
||||
<a
|
||||
href="{{ $server->docs_url }}"
|
||||
target="_blank"
|
||||
rel="nofollow noopener"
|
||||
class="inline-block rounded-lg border border-neutral-300 px-5 py-2.5 text-sm font-medium text-neutral-900 hover:bg-neutral-50"
|
||||
>
|
||||
Documentation →
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<p class="mt-8 text-neutral-700">{{ $server->description }}</p>
|
||||
|
||||
@if ($server->install_command)
|
||||
<div class="mt-8">
|
||||
<h2 class="text-lg font-semibold">Install</h2>
|
||||
<pre class="mt-3 overflow-x-auto rounded-lg bg-neutral-900 p-4 text-sm text-neutral-100"><code>{{ $server->install_command }}</code></pre>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($server->config_example)
|
||||
<div class="mt-8">
|
||||
<h2 class="text-lg font-semibold">Example configuration</h2>
|
||||
<pre class="mt-3 overflow-x-auto rounded-lg bg-neutral-900 p-4 text-sm text-neutral-100"><code>{{ $server->config_example }}</code></pre>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (! empty($server->capabilities))
|
||||
<div class="mt-8">
|
||||
<h2 class="text-lg font-semibold">Capabilities</h2>
|
||||
<ul class="mt-3 list-inside list-disc space-y-1 text-neutral-700">
|
||||
@foreach ($server->capabilities as $capability)
|
||||
<li>{{ $capability }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (! empty($server->tags))
|
||||
<div class="mt-8 flex flex-wrap gap-2">
|
||||
@foreach ($server->tags as $tag)
|
||||
<span class="rounded-full bg-neutral-100 px-3 py-1 text-sm text-neutral-700">#{{ $tag }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($related->isNotEmpty())
|
||||
<div class="mt-12 border-t border-neutral-200 pt-8">
|
||||
<h2 class="text-lg font-semibold">More {{ $server->category_label }} servers</h2>
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
@foreach ($related as $relatedServer)
|
||||
<x-mcp-server-card :server="$relatedServer" />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</x-layouts.app>
|
||||
@@ -10,6 +10,16 @@
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>{{ route('mcp-servers.index') }}</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>{{ route('skills.index') }}</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@foreach ($categories as $category)
|
||||
<url>
|
||||
<loc>{{ route('blog.category', $category) }}</loc>
|
||||
@@ -49,6 +59,36 @@
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
@endforeach
|
||||
@foreach ($mcpServerCategories as $mcpServerCategory)
|
||||
<url>
|
||||
<loc>{{ route('mcp-servers.category', $mcpServerCategory) }}</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
@endforeach
|
||||
@foreach ($mcpServers as $server)
|
||||
<url>
|
||||
<loc>{{ route('mcp-servers.show', $server) }}</loc>
|
||||
<lastmod>{{ $server->updated_at->toAtomString() }}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
@endforeach
|
||||
@foreach ($skillCategories as $skillCategory)
|
||||
<url>
|
||||
<loc>{{ route('skills.category', $skillCategory) }}</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
@endforeach
|
||||
@foreach ($skills as $skill)
|
||||
<url>
|
||||
<loc>{{ route('skills.show', $skill) }}</loc>
|
||||
<lastmod>{{ $skill->updated_at->toAtomString() }}</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
@endforeach
|
||||
@foreach ($pages as $page)
|
||||
<url>
|
||||
<loc>{{ route('pages.show', $page) }}</loc>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<x-layouts.app :title="$title" :description="$description" :canonical="$canonical">
|
||||
@push('jsonld')
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@@context' => 'https://schema.org',
|
||||
'@type' => 'BreadcrumbList',
|
||||
'itemListElement' => [
|
||||
['@type' => 'ListItem', 'position' => 1, 'name' => 'Claude Skills', 'item' => route('skills.index')],
|
||||
['@type' => 'ListItem', 'position' => 2, 'name' => $label, 'item' => $canonical],
|
||||
],
|
||||
]) !!}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<p class="text-sm text-neutral-500">
|
||||
<a href="{{ route('skills.index') }}" class="hover:underline">Claude Skills</a>
|
||||
<span aria-hidden="true">/</span>
|
||||
{{ $label }}
|
||||
</p>
|
||||
<h1 class="mt-2 text-3xl font-bold tracking-tight">{{ $label }} Claude Skills</h1>
|
||||
|
||||
<nav class="mt-4 flex flex-wrap gap-2">
|
||||
<a href="{{ route('skills.index') }}" class="rounded-full bg-neutral-100 px-3 py-1 text-sm text-neutral-700 hover:bg-neutral-200">
|
||||
All
|
||||
</a>
|
||||
@foreach ($categories as $slug => $categoryLabel)
|
||||
<a
|
||||
href="{{ route('skills.category', $slug) }}"
|
||||
class="rounded-full px-3 py-1 text-sm {{ $category === $slug ? 'bg-neutral-900 text-white' : 'bg-neutral-100 text-neutral-700 hover:bg-neutral-200' }}"
|
||||
>
|
||||
{{ $categoryLabel }}
|
||||
</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
<div class="mt-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
@forelse ($skills as $skill)
|
||||
<x-skill-card :skill="$skill" />
|
||||
@empty
|
||||
<p class="text-neutral-500 sm:col-span-2">No skills in this category yet.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
{{ $skills->links() }}
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,54 @@
|
||||
<x-layouts.app :title="$title" :description="$description" :canonical="$canonical">
|
||||
@push('jsonld')
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@@context' => 'https://schema.org',
|
||||
'@type' => 'CollectionPage',
|
||||
'name' => 'Claude Skills Directory',
|
||||
'url' => $canonical,
|
||||
]) !!}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<h1 class="text-3xl font-bold tracking-tight">Claude Skills Directory</h1>
|
||||
<p class="mt-2 text-neutral-600">A curated list of Claude Skills, with docs, install instructions, and example prompts.</p>
|
||||
|
||||
<form action="{{ route('skills.index') }}" method="GET" class="mt-6">
|
||||
<input
|
||||
type="search"
|
||||
name="q"
|
||||
value="{{ $search }}"
|
||||
placeholder="Search skills…"
|
||||
class="w-full rounded-lg border border-neutral-300 px-4 py-2 text-sm focus:border-neutral-900 focus:outline-none"
|
||||
>
|
||||
</form>
|
||||
|
||||
<nav class="mt-4 flex flex-wrap gap-2">
|
||||
<a
|
||||
href="{{ route('skills.index') }}"
|
||||
class="rounded-full px-3 py-1 text-sm {{ $activeCategory === '' ? 'bg-neutral-900 text-white' : 'bg-neutral-100 text-neutral-700 hover:bg-neutral-200' }}"
|
||||
>
|
||||
All
|
||||
</a>
|
||||
@foreach ($categories as $slug => $label)
|
||||
<a
|
||||
href="{{ route('skills.category', $slug) }}"
|
||||
class="rounded-full px-3 py-1 text-sm {{ $activeCategory === $slug ? 'bg-neutral-900 text-white' : 'bg-neutral-100 text-neutral-700 hover:bg-neutral-200' }}"
|
||||
>
|
||||
{{ $label }}
|
||||
</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
<div class="mt-8 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
@forelse ($skills as $skill)
|
||||
<x-skill-card :skill="$skill" />
|
||||
@empty
|
||||
<p class="text-neutral-500 sm:col-span-2">No skills found.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="mt-10">
|
||||
{{ $skills->links() }}
|
||||
</div>
|
||||
</x-layouts.app>
|
||||
@@ -0,0 +1,90 @@
|
||||
<x-layouts.app :title="$title" :description="$description" :canonical="$canonical" :image="$image ?? null">
|
||||
@push('jsonld')
|
||||
<script type="application/ld+json">
|
||||
{!! json_encode([
|
||||
'@@context' => 'https://schema.org',
|
||||
'@type' => 'SoftwareApplication',
|
||||
'name' => $skill->name,
|
||||
'description' => $description,
|
||||
'url' => $skill->url,
|
||||
...($skill->logo_url ? ['image' => $skill->logo_url] : []),
|
||||
'applicationCategory' => $skill->category_label,
|
||||
]) !!}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<p class="text-sm text-neutral-500">
|
||||
<a href="{{ route('skills.index') }}" class="hover:underline">Claude Skills</a>
|
||||
<span aria-hidden="true">/</span>
|
||||
<a href="{{ route('skills.category', $skill->category) }}" class="hover:underline">{{ $skill->category_label }}</a>
|
||||
</p>
|
||||
|
||||
<div class="mt-4 flex items-start gap-4">
|
||||
@if ($skill->logo_url)
|
||||
<img src="{{ $skill->logo_url }}" alt="{{ $skill->name }} logo" class="h-16 w-16 rounded-lg object-cover">
|
||||
@endif
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">{{ $skill->name }}</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500">
|
||||
{{ $skill->category_label }}
|
||||
@if ($skill->requirements) · {{ $skill->requirements }} @endif
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-3">
|
||||
<a
|
||||
href="{{ route('skills.visit', $skill) }}"
|
||||
target="_blank"
|
||||
rel="nofollow noopener"
|
||||
class="inline-block rounded-lg bg-neutral-900 px-5 py-2.5 text-sm font-medium text-white hover:bg-neutral-700"
|
||||
>
|
||||
View repository →
|
||||
</a>
|
||||
@if ($skill->docs_url)
|
||||
<a
|
||||
href="{{ $skill->docs_url }}"
|
||||
target="_blank"
|
||||
rel="nofollow noopener"
|
||||
class="inline-block rounded-lg border border-neutral-300 px-5 py-2.5 text-sm font-medium text-neutral-900 hover:bg-neutral-50"
|
||||
>
|
||||
Documentation →
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<p class="mt-8 text-neutral-700">{{ $skill->description }}</p>
|
||||
|
||||
@if ($skill->install_instructions)
|
||||
<div class="mt-8">
|
||||
<h2 class="text-lg font-semibold">How to install</h2>
|
||||
<p class="mt-3 text-neutral-700">{{ $skill->install_instructions }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($skill->example_prompt)
|
||||
<div class="mt-8">
|
||||
<h2 class="text-lg font-semibold">Example prompt</h2>
|
||||
<pre class="mt-3 overflow-x-auto rounded-lg bg-neutral-900 p-4 text-sm text-neutral-100"><code>{{ $skill->example_prompt }}</code></pre>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (! empty($skill->tags))
|
||||
<div class="mt-8 flex flex-wrap gap-2">
|
||||
@foreach ($skill->tags as $tag)
|
||||
<span class="rounded-full bg-neutral-100 px-3 py-1 text-sm text-neutral-700">#{{ $tag }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($related->isNotEmpty())
|
||||
<div class="mt-12 border-t border-neutral-200 pt-8">
|
||||
<h2 class="text-lg font-semibold">More {{ $skill->category_label }} skills</h2>
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
@foreach ($related as $relatedSkill)
|
||||
<x-skill-card :skill="$relatedSkill" />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</x-layouts.app>
|
||||
Reference in New Issue
Block a user