|
| 1 | +"""MkDocs hook: publish ``.well-known`` agent-discovery files. |
| 2 | +
|
| 3 | +MkDocs ignores files and directories whose names start with a dot, so a |
| 4 | +``.well-known`` directory under ``docs/`` is never copied into the built site. |
| 5 | +Instead we keep the source files under ``well-known/`` (no leading dot) at the |
| 6 | +repo root and copy them into ``<site_dir>/.well-known/`` here, mirroring the way |
| 7 | +``hooks/emit_markdown.py`` writes ``llms.txt`` straight into the site dir. |
| 8 | +
|
| 9 | +Two things are published: |
| 10 | +
|
| 11 | +1. ``/.well-known/mcp/server-card.json`` — MCP Server Card (SEP-1649) advertising |
| 12 | + the DoubleZero Data MCP server at https://data.malbeclabs.com/mcp. |
| 13 | +
|
| 14 | +2. ``/.well-known/agent-skills/index.json`` — Agent Skills discovery index |
| 15 | + (Agent Skills Discovery RFC v0.2.0). Each ``SKILL.md`` found under |
| 16 | + ``well-known/agent-skills/<name>/`` becomes an entry whose ``digest`` is the |
| 17 | + SHA-256 of the copied artifact, computed at build time so it can never drift. |
| 18 | +
|
| 19 | +GitHub Pages serves the ``.well-known`` directory (it is exempt from the usual |
| 20 | +dotfile exclusion), and the deploy workflow tars the site dir with ``-cvf .``, |
| 21 | +so the hidden directory is preserved in the uploaded artifact. |
| 22 | +""" |
| 23 | + |
| 24 | +from __future__ import annotations |
| 25 | + |
| 26 | +import hashlib |
| 27 | +import json |
| 28 | +import os |
| 29 | +import shutil |
| 30 | + |
| 31 | +SKILLS_SCHEMA = "https://schemas.agentskills.io/discovery/0.2.0/schema.json" |
| 32 | +SITE_BASE = "https://docs.malbeclabs.com" |
| 33 | + |
| 34 | + |
| 35 | +def _repo_root(config) -> str: |
| 36 | + """Directory containing mkdocs.yml.""" |
| 37 | + return os.path.dirname(os.path.abspath(config["config_file_path"])) |
| 38 | + |
| 39 | + |
| 40 | +def _frontmatter_description(skill_md_path: str) -> str: |
| 41 | + """Best-effort read of the ``description:`` field from a SKILL.md YAML |
| 42 | + front matter block. Supports a single-line value or a folded/literal block |
| 43 | + (``>-`` / ``|``) whose continuation lines are indented.""" |
| 44 | + try: |
| 45 | + with open(skill_md_path, encoding="utf-8") as fh: |
| 46 | + lines = fh.read().splitlines() |
| 47 | + except OSError: |
| 48 | + return "" |
| 49 | + |
| 50 | + if not lines or lines[0].strip() != "---": |
| 51 | + return "" |
| 52 | + |
| 53 | + desc_parts: list[str] = [] |
| 54 | + capturing = False |
| 55 | + for line in lines[1:]: |
| 56 | + if line.strip() == "---": |
| 57 | + break |
| 58 | + if capturing: |
| 59 | + if line and not line[0].isspace(): |
| 60 | + break # next top-level key |
| 61 | + stripped = line.strip() |
| 62 | + if stripped: |
| 63 | + desc_parts.append(stripped) |
| 64 | + continue |
| 65 | + if line.startswith("description:"): |
| 66 | + value = line[len("description:"):].strip() |
| 67 | + if value and value not in (">", "|", ">-", "|-", ">+", "|+"): |
| 68 | + return value |
| 69 | + capturing = True # block scalar; gather indented lines below |
| 70 | + |
| 71 | + return " ".join(desc_parts) |
| 72 | + |
| 73 | + |
| 74 | +def on_post_build(config, **kwargs) -> None: |
| 75 | + src_root = os.path.join(_repo_root(config), "well-known") |
| 76 | + if not os.path.isdir(src_root): |
| 77 | + return |
| 78 | + |
| 79 | + dest_root = os.path.join(config["site_dir"], ".well-known") |
| 80 | + shutil.copytree(src_root, dest_root, dirs_exist_ok=True) |
| 81 | + |
| 82 | + # Build the Agent Skills discovery index from the copied SKILL.md files. |
| 83 | + skills_dir = os.path.join(dest_root, "agent-skills") |
| 84 | + if not os.path.isdir(skills_dir): |
| 85 | + return |
| 86 | + |
| 87 | + skills = [] |
| 88 | + for name in sorted(os.listdir(skills_dir)): |
| 89 | + skill_md = os.path.join(skills_dir, name, "SKILL.md") |
| 90 | + if not os.path.isfile(skill_md): |
| 91 | + continue |
| 92 | + with open(skill_md, "rb") as fh: |
| 93 | + digest = hashlib.sha256(fh.read()).hexdigest() |
| 94 | + skills.append( |
| 95 | + { |
| 96 | + "name": name, |
| 97 | + "type": "skill-md", |
| 98 | + "description": _frontmatter_description(skill_md), |
| 99 | + "url": f"{SITE_BASE}/.well-known/agent-skills/{name}/SKILL.md", |
| 100 | + "digest": f"sha256:{digest}", |
| 101 | + } |
| 102 | + ) |
| 103 | + |
| 104 | + index = {"$schema": SKILLS_SCHEMA, "skills": skills} |
| 105 | + with open(os.path.join(skills_dir, "index.json"), "w", encoding="utf-8") as fh: |
| 106 | + json.dump(index, fh, indent=2, ensure_ascii=False) |
| 107 | + fh.write("\n") |
0 commit comments