/** * skills.agentify.help — VCAP Skills Repository * * A covenant-governed registry for agent skills and expert capabilities. * Every skill entry carries a SHA-256 content hash and VCAP attestation. * The hash is the integrity guarantee: any hidden line, hidden font, or * injected instruction breaks the hash and is detectable by anyone. * * Domain: skills.agentify.help (Bunny DNS CNAME → agentify.help → Fly.io) * Published under WellSpr.ing covenant. April 2026. */ import express from "express"; import type { Express, Request, Response } from "express"; import { createHash, createHmac } from "crypto"; import { pool } from "../db"; const SIGNING_KEY = process.env.ODY_SIGNING_KEY_B64 ? Buffer.from(process.env.ODY_SIGNING_KEY_B64, "base64").toString("hex").slice(0, 64) : "skills-agentify-help-dev-key-placeholder-not-for-production"; // ── DB setup ────────────────────────────────────────────────────────────────── async function ensureSkillsTable() { await pool.query(` CREATE TABLE IF NOT EXISTS vcap_skill_registry ( id SERIAL PRIMARY KEY, slug TEXT UNIQUE NOT NULL, name TEXT NOT NULL, summary TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', skill_type TEXT NOT NULL DEFAULT 'agent_skill', author_name TEXT NOT NULL, author_slug TEXT NOT NULL DEFAULT '', content TEXT NOT NULL DEFAULT '', content_hash TEXT NOT NULL DEFAULT '', vcap_scope TEXT NOT NULL DEFAULT 'skills:general:read:public', attestation_id TEXT NOT NULL DEFAULT '', signature TEXT NOT NULL DEFAULT '', tags TEXT[] NOT NULL DEFAULT '{}', version TEXT NOT NULL DEFAULT '1.0.0', covenant_url TEXT NOT NULL DEFAULT 'https://wellspr.ing/constitution', status TEXT NOT NULL DEFAULT 'active', registered_via TEXT NOT NULL DEFAULT 'web', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ) `); console.log("[skills.agentify.help] vcap_skill_registry table ready"); } // ── Slugify ─────────────────────────────────────────────────────────────────── function toSlug(s: string): string { return s.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").trim(); } // ── Hash + attestation ──────────────────────────────────────────────────────── function sha256(content: string): string { return "sha256:" + createHash("sha256").update(content, "utf8").digest("hex"); } function signAttestation(slug: string, contentHash: string, authorSlug: string, publishedAt: string): string { const payload = [slug, contentHash, authorSlug, publishedAt].join("|"); return createHmac("sha256", SIGNING_KEY).update(payload).digest("hex"); } function buildAttestation(row: any) { return { vcap: "1.0", skill_slug: row.slug, skill_name: row.name, skill_type: row.skill_type, author_name: row.author_name, author_slug: row.author_slug, version: row.version, vcap_scope: row.vcap_scope, content_hash: row.content_hash, content_length: (row.content || "").length, covenant: row.covenant_url, published_at: row.created_at instanceof Date ? row.created_at.toISOString() : String(row.created_at), attestation_id: row.attestation_id, signed_by: "skills.agentify.help", signature: row.signature, verify_url: `https://skills.agentify.help/skills/${row.slug}/attest.json`, raw_url: `https://skills.agentify.help/skills/${row.slug}/raw`, }; } // ── Rate limiter ────────────────────────────────────────────────────────────── const _rl = new Map(); function rateCheck(key: string, max: number, windowMs: number): boolean { const now = Date.now(); const e = _rl.get(key); if (!e || now > e.t) { _rl.set(key, { n: 1, t: now + windowMs }); return true; } if (e.n >= max) return false; e.n++; return true; } setInterval(() => { const now = Date.now(); for (const [k, v] of _rl) if (now > v.t) _rl.delete(k); }, 600_000).unref(); // ── MCP tools ───────────────────────────────────────────────────────────────── const SK_MCP_TOOLS = [ { name: "discover_skills", description: "Browse and search the VCAP skills repository. Filter by type, author, or tags. Returns skill summaries with content hashes.", inputSchema: { type: "object", properties: { q: { type: "string", description: "Search query (name, author, tags)" }, skill_type: { type: "string", description: "Filter by type: agent_skill, persona_skill, domain_skill, craft_skill, civic_skill" }, author_slug: { type: "string", description: "Filter by author slug" }, limit: { type: "number", description: "Max results (default 20)" } } } }, { name: "get_skill", description: "Fetch a complete skill by slug. Returns full content, attestation, and SHA-256 hash for integrity verification.", inputSchema: { type: "object", required: ["slug"], properties: { slug: { type: "string", description: "Skill slug" } } } }, { name: "submit_skill", description: "Publish a new skill to the registry. The content is hashed on receipt and a VCAP attestation is generated. Returns the attestation.", inputSchema: { type: "object", required: ["name", "author_name", "content"], properties: { name: { type: "string", description: "Skill name" }, summary: { type: "string", description: "One-sentence summary" }, description: { type: "string", description: "Full description" }, skill_type: { type: "string", description: "Type: agent_skill | persona_skill | domain_skill | craft_skill | civic_skill" }, author_name: { type: "string", description: "Author's full name" }, content: { type: "string", description: "The skill file content (plain text / Markdown)" }, vcap_scope: { type: "string", description: "SGS scope string (e.g. skills:herbalism:read:public)" }, tags: { type: "array", items: { type: "string" }, description: "Array of tags" }, version: { type: "string", description: "Version string (default 1.0.0)" } } } }, { name: "verify_integrity", description: "Verify a skill file's integrity against its published attestation. Provide the content you hold — returns whether it matches the registry hash. Detects any tampering, hidden text, or injection.", inputSchema: { type: "object", required: ["slug", "content"], properties: { slug: { type: "string", description: "Skill slug to verify against" }, content: { type: "string", description: "The skill file content you are holding" } } } } ]; // ── MCP tool handler ────────────────────────────────────────────────────────── async function handleSkillTool(name: string, args: any) { const text = (obj: any) => ({ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }] }); if (name === "discover_skills") { const limit = Math.min(Number(args.limit) || 20, 50); const { rows } = await pool.query(` SELECT id AS registration_number, slug, name, summary, skill_type, author_name, author_slug, content_hash, vcap_scope, tags, version, created_at FROM vcap_skill_registry WHERE status = 'active' AND ($1::text IS NULL OR name ILIKE '%' || $1 || '%' OR author_name ILIKE '%' || $1 || '%' OR $1 = ANY(tags)) AND ($2::text IS NULL OR skill_type = $2) AND ($3::text IS NULL OR author_slug = $3) ORDER BY created_at DESC LIMIT $4 `, [args.q || null, args.skill_type || null, args.author_slug || null, limit]); return text({ skills: rows, count: rows.length }); } if (name === "get_skill") { const { rows } = await pool.query( `SELECT * FROM vcap_skill_registry WHERE slug = $1 AND status = 'active'`, [args.slug] ); if (!rows.length) { const e: any = new Error(`Skill "${args.slug}" not found`); e.code = -32602; throw e; } const row = rows[0]; return text({ skill: { ...row, attestation: buildAttestation(row) } }); } if (name === "submit_skill") { const { name: skillName, author_name, content } = args; if (!skillName || !author_name || !content) { const e: any = new Error("name, author_name, and content are required"); e.code = -32602; throw e; } const slug = toSlug(skillName); const authorSlug = toSlug(author_name); const contentHash = sha256(content); const attestationId = `sk-${Date.now().toString(36)}-${slug.slice(0, 8)}`; const publishedAt = new Date().toISOString(); const signature = signAttestation(slug, contentHash, authorSlug, publishedAt); const tags = Array.isArray(args.tags) ? args.tags.map(String) : []; const { rows } = await pool.query(` INSERT INTO vcap_skill_registry (slug, name, summary, description, skill_type, author_name, author_slug, content, content_hash, vcap_scope, attestation_id, signature, tags, version, registered_via) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,'mcp') ON CONFLICT (slug) DO NOTHING RETURNING * `, [ slug, skillName, args.summary || skillName, args.description || "", args.skill_type || "agent_skill", author_name, authorSlug, content, contentHash, args.vcap_scope || "skills:general:read:public", attestationId, signature, tags, args.version || "1.0.0" ]); if (!rows.length) { return text({ error: "A skill with that name already exists.", slug }); } return text({ registered: true, attestation: buildAttestation(rows[0]) }); } if (name === "verify_integrity") { const { slug, content } = args; if (!slug || content === undefined) { const e: any = new Error("slug and content are required"); e.code = -32602; throw e; } const { rows } = await pool.query( `SELECT content_hash, name, author_name, created_at FROM vcap_skill_registry WHERE slug = $1 AND status = 'active'`, [slug] ); if (!rows.length) { const e: any = new Error(`Skill "${slug}" not found`); e.code = -32602; throw e; } const computedHash = sha256(content); const registryHash = rows[0].content_hash; const match = computedHash === registryHash; return text({ slug, skill_name: rows[0].name, author_name: rows[0].author_name, published_at: rows[0].created_at, registry_hash: registryHash, computed_hash: computedHash, integrity: match ? "VERIFIED" : "TAMPERED", message: match ? "The content you hold matches the published attestation. No hidden content detected." : "INTEGRITY FAILURE: The content does not match the published hash. This file has been modified." }); } const e: any = new Error(`Unknown tool: ${name}`); e.code = -32601; throw e; } // ── Palette / styles (shared with agentify.help) ────────────────────────────── const SK_CSS = ` *,*::before,*::after{box-sizing:border-box} body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#FDFAF4;color:#1A2B4A;margin:0;line-height:1.6} a{color:#C9962A;text-decoration:none}a:hover{text-decoration:underline} .nav{background:#1A2B4A;padding:.75rem 2rem;display:flex;align-items:center;gap:1.5rem;flex-wrap:wrap} .nav-brand{color:#FDFAF4;font-weight:800;font-size:1.05rem;letter-spacing:-.01em} .nav-brand span{color:#C9962A} .nav a{color:rgba(253,250,244,.7);font-size:.88rem} .nav a:hover{color:#FDFAF4;text-decoration:none} .hero{background:#1A2B4A;color:#FDFAF4;padding:4rem 2rem 3.5rem} .hero-inner{max-width:760px;margin:0 auto} .hero-tag{display:inline-block;background:rgba(201,150,42,.2);color:#C9962A;border:1px solid rgba(201,150,42,.4);border-radius:4px;padding:.2rem .75rem;font-size:.75rem;letter-spacing:.1em;text-transform:uppercase;font-weight:700;margin-bottom:1rem} .hero h1{font-size:clamp(1.8rem,4vw,2.8rem);font-weight:900;margin:0 0 .75rem;line-height:1.15} .hero p{font-size:1.05rem;color:rgba(253,250,244,.75);max-width:580px;margin:0} .hero-actions{margin-top:1.75rem;display:flex;gap:.75rem;flex-wrap:wrap} .btn{display:inline-block;padding:.6rem 1.4rem;border-radius:6px;font-weight:700;font-size:.88rem;cursor:pointer;border:none;text-decoration:none} .btn-gold{background:#C9962A;color:#1A2B4A} .btn-ghost{background:rgba(253,250,244,.1);color:#FDFAF4;border:1px solid rgba(253,250,244,.2)} .btn-ghost:hover{background:rgba(253,250,244,.18);text-decoration:none} .main{max-width:900px;margin:0 auto;padding:2.5rem 1.5rem} .section-title{font-size:.72rem;text-transform:uppercase;letter-spacing:.12em;font-weight:700;color:#8A9099;margin-bottom:1.25rem} .skill-card{background:#fff;border:1px solid rgba(26,43,74,.1);border-radius:10px;padding:1.25rem 1.5rem;margin-bottom:.85rem;transition:box-shadow .15s} .skill-card:hover{box-shadow:0 4px 18px rgba(26,43,74,.1)} .skill-card-header{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem} .skill-name{font-size:1rem;font-weight:700;color:#1A2B4A;margin:0} .skill-name a{color:#1A2B4A} .skill-type{display:inline-block;background:rgba(201,150,42,.12);color:#8A6A1A;border-radius:3px;padding:.15rem .55rem;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.07em} .skill-author{font-size:.85rem;color:#6A7A8A;margin:.35rem 0 0} .skill-summary{font-size:.9rem;color:#4A5568;margin:.5rem 0 0} .skill-meta{display:flex;align-items:center;gap:.75rem;margin-top:.75rem;flex-wrap:wrap} .tag-pill{background:#F0EDE4;color:#5A6B7A;border-radius:3px;padding:.15rem .55rem;font-size:.72rem} .hash-line{font-family:'SF Mono',monospace;font-size:.72rem;color:#8A9099;word-break:break-all} pre{background:#F0EDE4;border:1px solid rgba(26,43,74,.1);border-radius:8px;padding:1.25rem 1.5rem;overflow-x:auto;font-size:.82rem;white-space:pre-wrap;word-break:break-word} .verify-badge{display:inline-flex;align-items:center;gap:.4rem;padding:.3rem .8rem;border-radius:20px;font-size:.78rem;font-weight:700} .verify-ok{background:#E6F4EA;color:#1A7A3A;border:1px solid #A8D5B5} .footer{text-align:center;padding:2.5rem 1rem;font-size:.8rem;color:#8A9099;border-top:1px solid rgba(26,43,74,.08);margin-top:3rem} `; const SK_NAV = ` `; const SK_FOOTER = ` `; function skPage(title: string, body: string) { return ` ${title} ${SK_NAV}${body}${SK_FOOTER}`; } // ── Route registration ──────────────────────────────────────────────────────── export function registerSkillsAgentifyHelpRoutes(app: Express) { // Run DB setup in background — routes register synchronously so they // are in place before the Vite catch-all middleware is registered. ensureSkillsTable().catch(e => console.error("[skills.agentify.help] DB setup error:", e)); function isSkills(req: Request): boolean { const host = (req.headers["x-forwarded-host"] || req.headers.host || "") .toString().split(",")[0].trim().replace(/:\d+$/, "").toLowerCase(); return host.startsWith("skills."); } // ── Landing page ───────────────────────────────────────────────────────── app.get("/", async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const { rows } = await pool.query(` SELECT slug, name, summary, skill_type, author_name, tags, created_at FROM vcap_skill_registry WHERE status = 'active' ORDER BY created_at DESC LIMIT 6 `); const cards = rows.map(r => `

${r.name}

by ${r.author_name}

${(r.skill_type || "").replace(/_/g, " ")}
${r.summary ? `

${r.summary}

` : ""}
${(r.tags || []).map((t: string) => `${t}`).join("")}
`).join("") || `

No skills published yet. Be first.

`; res.setHeader("Cache-Control", "public, max-age=60"); res.send(skPage("skills.agentify.help — VCAP Skills Repository", `
VCAP Skills Repository

Skills that carry
their own proof.

Every skill published here is SHA-256 hashed and VCAP-attested on receipt. The file you receive socially — in plain text, in a message, in an email — either matches its hash or it doesn't. Math doesn't lie. No hidden fonts. No injected instructions. No back doors passed between humans in good faith.

Recently Published

${cards} ${rows.length > 0 ? `

View all skills →

` : ""}
🔐
Hash-attested integrity
Every skill has a SHA-256 fingerprint. Verify any file you receive — from anyone, over any channel — in one step.
📜
VCAP-governed conduct
Each skill carries a scope grammar (SGS) defining what it is authorized to do. The covenant is the operating agreement.
🤝
Social provenance
Skills of notable persons — craftspeople, healers, teachers — can be published, shared, and verified across generations.
`)); }); // ── Browse all skills ───────────────────────────────────────────────────── app.get("/skills", async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const page = Math.max(1, Number(req.query.page) || 1); const limit = 24; const offset = (page - 1) * limit; const type = req.query.type as string || null; const q = req.query.q as string || null; const { rows } = await pool.query(` SELECT id AS registration_number, slug, name, summary, skill_type, author_name, author_slug, content_hash, tags, version, created_at FROM vcap_skill_registry WHERE status = 'active' AND ($1::text IS NULL OR skill_type = $1) AND ($2::text IS NULL OR name ILIKE '%'||$2||'%' OR author_name ILIKE '%'||$2||'%') ORDER BY created_at DESC LIMIT $3 OFFSET $4 `, [type, q, limit, offset]); const typeOpts = ["agent_skill", "persona_skill", "domain_skill", "craft_skill", "civic_skill"]; const filterBar = `
${typeOpts.map(t => `${t.replace(/_/g," ")}`).join("")} ${type ? `× clear` : ""}
`; const cards = rows.map(r => `

${r.name}

by ${r.author_name} · v${r.version} · #${r.registration_number}

${(r.skill_type||"").replace(/_/g," ")}
${r.summary ? `

${r.summary}

` : ""}
${(r.tags||[]).map((t:string)=>`${t}`).join("")} ${(r.content_hash||"").slice(0,30)}…
`).join("") || `

No skills found.

`; res.setHeader("Cache-Control", "public, max-age=30"); res.send(skPage("Browse Skills — skills.agentify.help", `

Skills Registry

${filterBar}

${rows.length} skill${rows.length !== 1 ? "s" : ""} ${q ? `matching "${q}"` : type ? `— ${type.replace(/_/g," ")}` : "published"}

${cards} ${rows.length === limit ? `

Next page →

` : ""}
`)); }); // ── Individual skill page ───────────────────────────────────────────────── app.get("/skills/:slug", async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const { rows } = await pool.query( `SELECT * FROM vcap_skill_registry WHERE slug = $1 AND status = 'active'`, [req.params.slug] ); if (!rows.length) { res.status(404).send(skPage("Skill Not Found — skills.agentify.help", `

Skill not found

Browse all skills →

`)); return; } const r = rows[0]; const attest = buildAttestation(r); const contentPreview = (r.content || "").slice(0, 2000) + ((r.content || "").length > 2000 ? "\n\n… (truncated — download raw for full content)" : ""); res.setHeader("Cache-Control", "public, max-age=60"); res.send(skPage(`${r.name} — skills.agentify.help`, `
${(r.skill_type||"").replace(/_/g," ")} v${r.version}

${r.name}

by ${r.author_name} · Published ${new Date(r.created_at).toLocaleDateString("en-US",{year:"numeric",month:"long",day:"numeric"})}

${r.summary ? `

${r.summary}

` : ""}
Download Raw Attestation JSON Verify a Copy

Integrity

✓ VCAP Attested

SHA-256 Content Hash

${r.content_hash}

VCAP Scope

${r.vcap_scope}

Attestation ID

${r.attestation_id}
${r.description ? `

Description

${r.description}

` : ""}

Skill Content

This is the hashed content. Download the raw file and verify its SHA-256 matches the hash above.

${contentPreview.replace(//g,">")}
${(r.tags||[]).length ? `
${(r.tags||[]).map((t:string)=>`${t}`).join("")}
` : ""}

Governed by WellSpr.ing covenant · Browse all skills

`)); }); // ── Raw skill file ──────────────────────────────────────────────────────── app.get("/skills/:slug/raw", async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const { rows } = await pool.query( `SELECT name, content, content_hash, author_name, vcap_scope, attestation_id, created_at FROM vcap_skill_registry WHERE slug = $1 AND status = 'active'`, [req.params.slug] ); if (!rows.length) return res.status(404).type("text/plain").send("Skill not found"); const r = rows[0]; res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.setHeader("Content-Disposition", `attachment; filename="${req.params.slug}.skill.txt"`); res.setHeader("X-Content-Hash", r.content_hash); res.setHeader("X-Attestation-Id", r.attestation_id); res.setHeader("X-VCAP-Scope", r.vcap_scope); res.setHeader("Cache-Control", "public, max-age=3600"); res.send(r.content); }); // ── Attestation JSON ────────────────────────────────────────────────────── app.get("/skills/:slug/attest.json", async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const { rows } = await pool.query( `SELECT * FROM vcap_skill_registry WHERE slug = $1 AND status = 'active'`, [req.params.slug] ); if (!rows.length) return res.status(404).json({ error: "Skill not found" }); res.setHeader("Cache-Control", "public, max-age=3600"); res.json(buildAttestation(rows[0])); }); // ── Submit form (GET) ───────────────────────────────────────────────────── app.get("/submit", (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); res.send(skPage("Publish a Skill — skills.agentify.help", `

Publish a Skill

Your content is hashed on receipt. A VCAP attestation is generated immediately. The hash is your integrity guarantee.

`)); }); // ── Verify page ─────────────────────────────────────────────────────────── app.get("/verify", (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); res.send(skPage("Verify a Skill — skills.agentify.help", `

Verify a Skill File

Paste the content of any skill file you've received. We'll check it against the published hash. Any hidden line, hidden font, or injected instruction breaks the hash.

`)); }); // ── API: submit skill ───────────────────────────────────────────────────── app.post("/api/skills", express.json({ limit: "2mb" }), async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const ip = (req.headers["x-forwarded-for"] as string || req.socket.remoteAddress || "unknown").split(",")[0].trim(); if (!rateCheck(`submit:${ip}`, 10, 3_600_000)) return res.status(429).json({ error: "Rate limit exceeded." }); const { name, author_name, content } = req.body || {}; if (!name || !author_name || !content) return res.status(400).json({ error: "name, author_name, and content are required." }); if (content.length > 500_000) return res.status(400).json({ error: "Content too large (max 500 KB)." }); const slug = toSlug(name); if (!slug) return res.status(400).json({ error: "Could not generate a valid slug from that name." }); const authorSlug = toSlug(author_name); const contentHash = sha256(content); const attestationId = `sk-${Date.now().toString(36)}-${slug.slice(0, 8)}`; const publishedAt = new Date().toISOString(); const signature = signAttestation(slug, contentHash, authorSlug, publishedAt); const tags = Array.isArray(req.body.tags) ? req.body.tags.map(String) : []; try { const { rows } = await pool.query(` INSERT INTO vcap_skill_registry (slug, name, summary, description, skill_type, author_name, author_slug, content, content_hash, vcap_scope, attestation_id, signature, tags, version, registered_via) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,'web') ON CONFLICT (slug) DO NOTHING RETURNING id AS registration_number, slug, name, content_hash, attestation_id `, [ slug, name, req.body.summary || name, req.body.description || "", req.body.skill_type || "agent_skill", author_name, authorSlug, content, contentHash, req.body.vcap_scope || "skills:general:read:public", attestationId, signature, tags, req.body.version || "1.0.0" ]); if (!rows.length) return res.status(409).json({ error: "A skill with that name already exists.", slug }); res.status(201).json({ registered: true, slug: rows[0].slug, registration_number: rows[0].registration_number, content_hash: rows[0].content_hash, attestation_id: rows[0].attestation_id }); } catch (e: any) { res.status(500).json({ error: e.message }); } }); // ── API: list skills ────────────────────────────────────────────────────── app.get("/api/skills", async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const limit = Math.min(Number(req.query.limit) || 20, 100); const { rows } = await pool.query(` SELECT id AS registration_number, slug, name, summary, skill_type, author_name, author_slug, content_hash, vcap_scope, tags, version, created_at FROM vcap_skill_registry WHERE status = 'active' ORDER BY created_at DESC LIMIT $1 `, [limit]); res.setHeader("Cache-Control", "public, max-age=30"); res.json({ skills: rows, count: rows.length }); }); // ── API: get skill ──────────────────────────────────────────────────────── app.get("/api/skills/:slug", async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const { rows } = await pool.query( `SELECT * FROM vcap_skill_registry WHERE slug = $1 AND status = 'active'`, [req.params.slug] ); if (!rows.length) return res.status(404).json({ error: "Skill not found" }); res.setHeader("Cache-Control", "public, max-age=60"); res.json({ skill: rows[0], attestation: buildAttestation(rows[0]) }); }); // ── API: verify integrity ───────────────────────────────────────────────── app.post("/api/skills/:slug/verify", express.json({ limit: "2mb" }), async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const { content } = req.body || {}; if (content === undefined) return res.status(400).json({ error: "content is required" }); const { rows } = await pool.query( `SELECT content_hash, name, author_name, created_at FROM vcap_skill_registry WHERE slug = $1 AND status = 'active'`, [req.params.slug] ); if (!rows.length) return res.status(404).json({ error: "Skill not found" }); const computedHash = sha256(content); const registryHash = rows[0].content_hash; const match = computedHash === registryHash; res.json({ slug: req.params.slug, skill_name: rows[0].name, author_name: rows[0].author_name, published_at: rows[0].created_at, registry_hash: registryHash, computed_hash: computedHash, integrity: match ? "VERIFIED" : "TAMPERED", message: match ? "Content matches the published attestation. No tampering detected." : "Content does NOT match the published hash. This file has been modified." }); }); // ── MCP endpoint ────────────────────────────────────────────────────────── const SK_MCP_DISCOVERY = { mcpVersion: "2025-03-26", name: "skills-agentify-help", displayName: "VCAP Skills Repository", description: "Covenant-governed registry of VCAP-attested agent skills. Every skill carries a SHA-256 content hash — verify any file you receive. Tools: discover_skills, get_skill, submit_skill, verify_integrity.", transport: [{ type: "http", url: "https://skills.agentify.help/mcp" }], capabilities: { tools: {} }, contact: { email: "ody@wellspr.ing", url: "https://skills.agentify.help" }, legal: { termsOfService: "https://wellspr.ing/constitution" } }; app.get("/.well-known/mcp.json", (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); res.setHeader("Cache-Control", "public, max-age=3600"); res.json(SK_MCP_DISCOVERY); }); app.get("/.well-known/ai-plugin.json", (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); res.setHeader("Cache-Control", "public, max-age=3600"); res.json({ schema_version: "v1", name_for_human: "VCAP Skills Repo", name_for_model: "vcap_skills_repo", description_for_human: "Browse and verify covenant-attested agent skills.", description_for_model: "Search, retrieve, submit, and verify VCAP-attested skills. Use verify_integrity to check any skill file for hidden content or tampering.", auth: { type: "none" }, api: { type: "openapi", url: "https://skills.agentify.help/.well-known/openapi.json" }, contact_email: "ody@wellspr.ing", legal_info_url: "https://wellspr.ing/constitution" }); }); app.get("/robots.txt", (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); res.type("text/plain").send("User-agent: *\nAllow: /\nSitemap: https://skills.agentify.help/sitemap.xml\n"); }); app.get("/sitemap.xml", async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const { rows } = await pool.query(`SELECT slug, created_at FROM vcap_skill_registry WHERE status = 'active' ORDER BY created_at DESC`); const urls = [ `https://skills.agentify.help/daily1.0`, `https://skills.agentify.help/skillsdaily0.9`, `https://skills.agentify.help/submitmonthly0.7`, `https://skills.agentify.help/verifymonthly0.7`, ...rows.map(r => `https://skills.agentify.help/skills/${r.slug}${new Date(r.created_at).toISOString().slice(0,10)}0.8`) ]; res.type("application/xml").send(`${urls.join("")}`); }); app.get("/api/feed.json", async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const { rows } = await pool.query(`SELECT slug, name, summary, skill_type, author_name, content_hash, created_at FROM vcap_skill_registry WHERE status = 'active' ORDER BY created_at DESC LIMIT 50`); res.setHeader("Cache-Control", "public, max-age=60"); res.json({ feed: rows, count: rows.length, updated: new Date().toISOString() }); }); app.post("/mcp", express.json({ limit: "1mb" }), async (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); const ip = (req.headers["x-forwarded-for"] as string || req.socket.remoteAddress || "unknown").split(",")[0].trim(); const body = req.body; if (!body || typeof body !== "object") { return res.status(400).json({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }); } const { method, params, id } = body; const ok = (result: any) => res.json({ jsonrpc: "2.0", id: id ?? null, result }); const err = (code: number, message: string) => res.json({ jsonrpc: "2.0", id: id ?? null, error: { code, message } }); res.setHeader("Content-Type", "application/json"); res.setHeader("Cache-Control", "no-store"); switch (method) { case "initialize": return ok({ protocolVersion: "2025-03-26", serverInfo: { name: "skills-agentify-help", version: "1.0.0" }, capabilities: { tools: {} } }); case "notifications/initialized": return res.status(204).end(); case "ping": return ok({}); case "tools/list": return ok({ tools: SK_MCP_TOOLS }); case "tools/call": { const toolName = params?.name as string; const args = params?.arguments ?? {}; if (!toolName) return err(-32602, "params.name is required"); if (toolName === "submit_skill" && !rateCheck(`mcp:${ip}`, 10, 3_600_000)) { return ok({ content: [{ type: "text", text: JSON.stringify({ error: "Rate limit exceeded." }) }], isError: true }); } try { const result = await handleSkillTool(toolName, args); return ok(result); } catch (e: any) { if (e?.code) return err(e.code, e.message); return err(-32603, e?.message || "Internal error"); } } default: return err(-32601, `Method not found: ${method}`); } }); app.get("/mcp", (req: Request, res: Response, next) => { if (!isSkills(req)) return next(); res.json(SK_MCP_DISCOVERY); }); console.log("[skills.agentify.help] Routes registered"); }