Skip to content

Releases: kinjelom/mkdocsgo

v0.4.0

Choose a tag to compare

@kinjelom kinjelom released this 27 Sep 08:56

Added

  • Prometheus metrics: what the site and the MCP server are asked for.
    Which pages people read (mkdocsgo_site_page_views_total), which tools
    agents call and how the calls end, which pages those tools hand out, how
    many searches found nothing, and requests, latency and bytes by route and
    zone. Every label value comes from a closed set - a page in the build, a
    registered tool, a configured zone - never from a request path or a search
    query, so a scanner cannot mint series. Off unless asked for.

  • -metrics-addr serves /metrics on a listener of its own, never on the
    published -http address.

  • -metrics-push sends the metrics to a Pushgateway - or VictoriaMetrics - every
    -metrics-push-interval, grouped by -metrics-job and -metrics-instance,
    and deletes the group on shutdown. The URL may carry Basic credentials and is
    read from MKDOCSGO_METRICS_PUSH_URL when the flag is absent, so they stay out
    of the process list. This is how metrics leave a Cloud Foundry instance.

  • -metrics-users name|hash labels the metrics that say who read what with
    the principal behind the request - on the site, and inside MCP tool calls,
    where the identity from the zone check reaches the tools per request. hash
    is a 12-character pseudonym, keyed by MKDOCSGO_METRICS_USER_SALT when set;
    unsalted, it can be recomputed from the names in mkdocsgo.yml, and the
    startup log says so. Off by default.

  • METRICS.md: the metrics, both ways out, and the queries worth having.

  • Markdown sources on the site, per zone. A zone with markdown: true
    serves each page's Markdown at the page's address with .md added -
    /guides/deploy/ as /guides/deploy.md - and at its path under docs_dir.
    On a Material site each page gets a download button among Material's page
    actions, and every page gets a <link rel="alternate" type="text/markdown">
    for agents that read HTML. A browser is sent text/plain, so it shows the
    source instead of saving it. Only pages MkDocs built are offered, the source
    is as protected as its page, and every other zone is served exactly what
    MkDocs wrote. markdown: true on a zone that is off stops the start.

  • ?section=<anchor> on any of those addresses returns one section - the
    heading and the text beneath it - which is what get_section returns, from
    the same code. For an agent that fetches URLs rather than speaking MCP, the
    cheap way to read stays cheap. An unknown anchor is a 404 that lists the
    ones the page has.

  • OAuth sign-in for MCP clients, so claude.ai can connect. claude.ai,
    Claude Desktop and Claude mobile add a remote server by its URL and nothing
    else, so a static token was no way in for them. A zone with oauth: true now
    runs an OAuth 2.1 authorization server inside the binary: RFC 8414 metadata,
    dynamic client registration (RFC 7591), the authorization code flow with
    PKCE (S256 only), and refresh. The person signing in is one of the zone's
    principals, with the password already in mkdocsgo.yml. Claude Code signs
    in the same way, through a loopback redirect on any port, and static tokens
    keep working beside it.

  • Nothing is stored. Client registrations, codes, access and refresh tokens
    are sealed with an HMAC under MKDOCSGO_OAUTH_KEY, the one secret, which
    lives in the environment rather than in the file. Any instance accepts what
    another issued and a restart signs nobody out. Taking a principal out of the
    zone or changing its password, then restarting, signs out every client it
    signed in; changing the key signs out everyone.

  • oauth.redirect_uris is the allowlist a client registration may name,
    defaulting to Claude's callback and Claude Code's loopback;
    oauth.access_token_ttl (1 hour) and oauth.refresh_token_ttl (30 days)
    set the lifetimes.

  • docs/adr/0002-built-in-oauth-for-mcp-clients.md: why the authorization
    server is built in and stateless, and what that costs - a secret, no
    per-token revocation, no refresh-token rotation.

Changed

  • Both halves publish the same pages. With a built site loaded
    (-mode site+mcp), the MCP half keeps only the pages MkDocs built a page
    from - the rule the Markdown sources use. A page a plugin excluded is no
    longer searchable and readable through /mcp while missing from the site.
    The startup log lists what was left out.
  • draft_docs is honoured like exclude_docs: mkdocs build leaves drafts
    out, and so does the index, in every mode.
  • The protected resource metadata of a zone with oauth: true names its
    authorization server in authorization_servers.
  • A bearer token presented and refused on /mcp gets error="invalid_token"
    in the WWW-Authenticate challenge, which tells an OAuth client to refresh
    rather than start over.
  • A zone with oauth: true served over HTTP with /mcp will not start without
    MKDOCSGO_OAUTH_KEY, 32 characters or more.

v0.3.0

Choose a tag to compare

@kinjelom kinjelom released this 15 Sep 16:43

Added

  • **. matches a domain at any depth, next to *., which still matches
    exactly one label. **.in covers a.in and a.b.c.in alike, and the longest
    suffix still wins, so **.cfp1.i6e.in beats **.in.

    It is for a policy stated in terms of a domain rather than a host - every
    internal foundation is internal, everything on the public one asks for
    credentials - where the alternative is listing foundations as they are
    created and having the newest one answer 403 until somebody notices. Spelled
    with two stars because the wide match is the one worth writing on purpose:
    nothing that already used *. changes meaning.

v0.2.0

Choose a tag to compare

@kinjelom kinjelom released this 15 Sep 15:56

Added

  • Zones: mkdocsgo.yml beside mkdocs.yml maps the addresses the server
    answers on to a named zone, and a zone to public, restricted or off.
    Several addresses may share one; the most specific pattern wins; an address
    no zone claims is refused. This is what lets one deployment serve an intranet
    route openly and an internet route only to named principals. A project
    without the file behaves exactly as it did before - everything public, no
    challenge - so the upgrade is a no-op until you write one. -config points
    at the file elsewhere.
  • A restricted zone asks a browser for HTTP Basic credentials and an agent for
    Authorization: Bearer. The MCP 401 carries the WWW-Authenticate
    challenge the specification asks for, pointing at RFC 9728 protected resource
    metadata the server publishes at /.well-known/oauth-protected-resource/mcp
    • so the day a zone moves to Keycloak, a client that already follows the
      pointer needs no change. method: is that seam, and oidc is refused at
      startup rather than ignored.
  • Credentials are stored as hashes - Argon2id for a password, SHA-256 for a
    token - which is why the file is not a secret and needs no encryption. Bcrypt
    hashes from htpasswd -B are accepted as well. -new-token and
    -hash-password mint them and print the line to paste.
  • The access log names the zone and, where there is one, the principal and the
    credential id it came in on. The Authorization header is never logged.
  • AUTH.md, and docs/adr/0001-zone-based-authentication.md for why it is
    shaped this way - including what was rejected: zones that also scope content,
    and an encrypted credentials file.

Changed

  • In a restricted zone every Cache-Control the site would have sent as
    public is sent as private, and responses carry
    X-Robots-Tag: noindex, nofollow. The freshness is unchanged; a shared cache
    loses the right to store the page and hand it to the next person.
  • Configuration errors stop the server. A restricted zone with no principals, a
    principal nobody lets in, a plaintext password where a hash belongs, one host
    in two zones, an unknown key - each fails at startup rather than at some
    later request.

v0.1.1

Choose a tag to compare

@kinjelom kinjelom released this 14 Sep 08:54

Fixed

  • get_page and get_section now carry the Markdown in the structured result
    as well as in the content block. Both tools declare an output schema, so a
    client may render structuredContent and never read the content block - and
    such a client saw the metadata and none of the text. The two channels now
    hold the same Markdown. The markdown field is additive, so a client already
    reading the structured result keeps every field it had. search_docs and
    list_pages were never affected: their structured results already carried
    the text, which is why only these two tools looked empty.

v0.1.0

Choose a tag to compare

@kinjelom kinjelom released this 14 Sep 08:07

The first release. Everything below is new.

While the version is 0.x the flag surface and the MCP payload shapes may
still change. A change that breaks either will bump the minor version and say
so here.

Added

  • Serving a built MkDocs site over HTTP: content-hash ETags, compression done
    once at startup, security headers, a per-path cache policy, directory URLs,
    the project's own 404.html, range requests.
  • An MCP server over Streamable HTTP and stdio, with four section-shaped tools
    (search_docs, get_section, get_page, list_pages) and one resource per
    page.
  • A BM25 index over sections, weighting heading terms and preserving version
    strings through tokenisation.
  • site, mcp and site+mcp modes from one binary.
  • -healthcheck and -mcp-probe, so a distroless image with no shell can
    still be probed.
  • cmd/anchorcheck, which compares computed anchors against a real built site.
  • scripts/release.sh and its parts (test.sh, build.sh, dist.sh,
    image.sh): cross-compiled archives with checksums, a container image, an
    annotated tag and a GitHub release from one command.
  • Documentation: SERVING.md (the site half), MCP.md (the agent half),
    ARCHITECTURE.md (how it works inside), DEPLOYMENT.md (Docker, Cloud
    Foundry with both a Docker application and the binary buildpack, and
    Kubernetes), COMPARISON.md (what this replaces and what it costs) and
    RELEASING.md.
  • mkdocsgo-example, a companion repository: a complete MkDocs project wired
    to this server, with every deployment manifest filled in.
  • The MIT licence, shipped inside every release archive.