v2: Session-Login & Rollen, Premium-Admin, Visual-Block-Builder, KI-/MCP-API
- Auth-Umbau: Session-Login (signiertes HMAC-Cookie, scrypt-Hashing) statt Basic-Auth; users-/audit-Tabellen, Initial-Owner aus ENV, Rate-Limit, konfigurierbarer ADMIN_PATH (Middleware-Rewrite), Rollen-Gate (owner/redaktion/versand), Nutzerverwaltung, Audit-Log, Login/Logout/Konto-Seiten. - Premium-Pass: Command-Palette (Cmd-K), Toasts, Account-Menue, aufgewertetes Dashboard (KPI-Trend+Sparkline, Aktivitaets-Feed, Schnellaktionen), schoene Empty-States. - Block-Builder: pages.blocks, Vollbild-Editor (Liste/Live-Vorschau/Settings, Desktop/Mobil), 10 Block-Typen, Storefront-BlockRenderer auf /seite/[slug], Save-Endpoint. - KI-Editierbarkeit: token-gesicherte /api/admin/* (CRUD), Manifest /api/admin + /ai-admin.txt, MCP-Server unter mcp/ (14 Tools). - Docs: README + .env.example + mcp/README aktualisiert.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
@@ -0,0 +1,64 @@
|
||||
# hd-commerce — MCP-Server
|
||||
|
||||
Ein [Model Context Protocol](https://modelcontextprotocol.io)-Server (stdio), mit dem ein LLM/Agent
|
||||
einen hd-commerce-Shop bearbeiten kann. Der Server ruft die token-gesicherte Admin-API
|
||||
(`/api/admin/*`) des Shops auf.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Laufende hd-commerce-Instanz mit gesetztem `HDC_API_TOKEN`.
|
||||
- Node 18+ (für globales `fetch`).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cd mcp
|
||||
npm install
|
||||
```
|
||||
|
||||
## Konfiguration (ENV)
|
||||
|
||||
| Variable | Beschreibung | Beispiel |
|
||||
|---|---|---|
|
||||
| `HDC_BASE_URL` | Basis-URL des Shops | `https://shop.example.com` |
|
||||
| `HDC_API_TOKEN` | gleiches Token wie in der Shop-Instanz | `geheimes-token` |
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Zweck |
|
||||
|---|---|
|
||||
| `list_products` / `get_product` / `upsert_product` / `delete_product` | Produkte verwalten |
|
||||
| `list_pages` / `get_page` / `create_page` | Seiten lesen/anlegen |
|
||||
| `update_page_blocks` | Block-Struktur einer Seite (Visual-Builder) setzen |
|
||||
| `list_slides` / `upsert_slide` | Slider verwalten |
|
||||
| `list_orders` | Bestellungen lesen |
|
||||
| `get_settings` / `update_settings` | Shop-Einstellungen (Name, Farben …) |
|
||||
| `get_manifest` | Vollständiges API-Manifest |
|
||||
|
||||
## In Claude Desktop / Cowork registrieren
|
||||
|
||||
`claude_desktop_config.json` (bzw. die MCP-Konfiguration):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"hd-commerce": {
|
||||
"command": "node",
|
||||
"args": ["/absoluter/pfad/zu/hd-commerce/mcp/server.js"],
|
||||
"env": {
|
||||
"HDC_BASE_URL": "https://shop.example.com",
|
||||
"HDC_API_TOKEN": "geheimes-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Danach den Client neu starten. Der Server meldet sich als `hd-commerce` mit obigen Tools.
|
||||
|
||||
## Schneller Start-Check
|
||||
|
||||
```bash
|
||||
HDC_BASE_URL=http://localhost:4321 HDC_API_TOKEN=testtoken node server.js
|
||||
# Gibt auf stderr "bereit" aus und wartet auf stdio-Anfragen (mit Ctrl-C beenden).
|
||||
```
|
||||
Generated
+1171
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "hd-commerce-mcp",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "MCP-Server für hd-commerce — bearbeitet Produkte, Seiten, Slides, Popups & Einstellungen über die Admin-API.",
|
||||
"bin": { "hd-commerce-mcp": "./server.js" },
|
||||
"scripts": { "start": "node server.js" },
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env node
|
||||
// hd-commerce MCP-Server (stdio).
|
||||
// Spricht die token-gesicherte Admin-API (/api/admin) an.
|
||||
// ENV: HDC_BASE_URL (z.B. https://shop.example.com), HDC_API_TOKEN.
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
|
||||
const BASE = (process.env.HDC_BASE_URL || 'http://localhost:4321').replace(/\/+$/, '');
|
||||
const TOKEN = process.env.HDC_API_TOKEN || '';
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(BASE + path, {
|
||||
method,
|
||||
headers: { 'Authorization': 'Bearer ' + TOKEN, 'Content-Type': 'application/json' },
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
const text = await res.text();
|
||||
let data; try { data = JSON.parse(text); } catch { data = text; }
|
||||
if (!res.ok) throw new Error('API ' + res.status + ': ' + (data && data.error ? data.error : text));
|
||||
return data;
|
||||
}
|
||||
|
||||
const TOOLS = [
|
||||
{ name: 'list_products', description: 'Alle Produkte auflisten.', inputSchema: { type: 'object', properties: {} } },
|
||||
{ name: 'get_product', description: 'Ein Produkt per ID oder Slug holen.', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Produkt-ID' } }, required: ['id'] } },
|
||||
{ name: 'upsert_product', description: 'Produkt anlegen/aktualisieren. Mit id oder slug => Update, sonst Create. Preis in Cent (priceCents).', inputSchema: { type: 'object', properties: { product: { type: 'object', description: 'Produktfelder: name, slug, priceCents, category, desc, cardImage, images[], stock, featured, badge, sizes[], features[]' } }, required: ['product'] } },
|
||||
{ name: 'delete_product', description: 'Produkt löschen (per ID).', inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
|
||||
{ name: 'list_pages', description: 'Alle Seiten auflisten.', inputSchema: { type: 'object', properties: {} } },
|
||||
{ name: 'get_page', description: 'Seite per ID oder Slug (inkl. blocks).', inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
|
||||
{ name: 'create_page', description: 'Neue Seite anlegen (slug, title, body, type, blocks[]).', inputSchema: { type: 'object', properties: { page: { type: 'object' } }, required: ['page'] } },
|
||||
{ name: 'update_page_blocks', description: 'Block-Array einer Seite setzen (Visual-Builder-Struktur).', inputSchema: { type: 'object', properties: { id: { type: 'string' }, blocks: { type: 'array' } }, required: ['id', 'blocks'] } },
|
||||
{ name: 'list_orders', description: 'Bestellungen auflisten (nur lesen).', inputSchema: { type: 'object', properties: {} } },
|
||||
{ name: 'list_slides', description: 'Slider-Slides auflisten.', inputSchema: { type: 'object', properties: {} } },
|
||||
{ name: 'upsert_slide', description: 'Slide anlegen/aktualisieren.', inputSchema: { type: 'object', properties: { slide: { type: 'object' } }, required: ['slide'] } },
|
||||
{ name: 'get_settings', description: 'Shop-Einstellungen (Key/Value) holen.', inputSchema: { type: 'object', properties: {} } },
|
||||
{ name: 'update_settings', description: 'Shop-Einstellungen aktualisieren (Key/Value-Map, z.B. shop_name, brand_accent).', inputSchema: { type: 'object', properties: { settings: { type: 'object' } }, required: ['settings'] } },
|
||||
{ name: 'get_manifest', description: 'API-Manifest (alle Ressourcen, Felder, Block-Typen).', inputSchema: { type: 'object', properties: {} } },
|
||||
];
|
||||
|
||||
const server = new Server({ name: 'hd-commerce', version: '2.0.0' }, { capabilities: { tools: {} } });
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
||||
const { name, arguments: a = {} } = req.params;
|
||||
let out;
|
||||
try {
|
||||
switch (name) {
|
||||
case 'list_products': out = await api('GET', '/api/admin/products'); break;
|
||||
case 'get_product': out = await api('GET', '/api/admin/products/' + encodeURIComponent(a.id)); break;
|
||||
case 'upsert_product': out = await api('POST', '/api/admin/products', a.product); break;
|
||||
case 'delete_product': out = await api('DELETE', '/api/admin/products/' + encodeURIComponent(a.id)); break;
|
||||
case 'list_pages': out = await api('GET', '/api/admin/pages'); break;
|
||||
case 'get_page': out = await api('GET', '/api/admin/pages/' + encodeURIComponent(a.id)); break;
|
||||
case 'create_page': out = await api('POST', '/api/admin/pages', a.page); break;
|
||||
case 'update_page_blocks': out = await api('POST', '/api/admin/pages/' + encodeURIComponent(a.id) + '/blocks', { blocks: a.blocks }); break;
|
||||
case 'list_orders': out = await api('GET', '/api/admin/orders'); break;
|
||||
case 'list_slides': out = await api('GET', '/api/admin/slides'); break;
|
||||
case 'upsert_slide': out = await api('POST', '/api/admin/slides', a.slide); break;
|
||||
case 'get_settings': out = await api('GET', '/api/admin/settings'); break;
|
||||
case 'update_settings': out = await api('POST', '/api/admin/settings', a.settings); break;
|
||||
case 'get_manifest': out = await api('GET', '/api/admin'); break;
|
||||
default: throw new Error('Unbekanntes Tool: ' + name);
|
||||
}
|
||||
return { content: [{ type: 'text', text: JSON.stringify(out, null, 2) }] };
|
||||
} catch (e) {
|
||||
return { content: [{ type: 'text', text: 'Fehler: ' + (e && e.message || e) }], isError: true };
|
||||
}
|
||||
});
|
||||
|
||||
async function main() {
|
||||
if (!TOKEN) console.error('[hd-commerce-mcp] Warnung: HDC_API_TOKEN ist nicht gesetzt.');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error('[hd-commerce-mcp] bereit. Base: ' + BASE);
|
||||
}
|
||||
main().catch((e) => { console.error(e); process.exit(1); });
|
||||
Reference in New Issue
Block a user