Skip to content

Add agent-readiness improvements (Link headers, markdown negotiation, Content-Signal, agent-skills, WebMCP)#807

Open
IEvangelist wants to merge 5 commits intomainfrom
dapine/agent-readiness
Open

Add agent-readiness improvements (Link headers, markdown negotiation, Content-Signal, agent-skills, WebMCP)#807
IEvangelist wants to merge 5 commits intomainfrom
dapine/agent-readiness

Conversation

@IEvangelist
Copy link
Copy Markdown
Member

Implementation of the checks at https://isitagentready.com. See commit message for full details. Verified: all 51 new + 16 existing C# tests pass, frontend lint clean, full solution build clean. Tests cover middleware ordering, markdown negotiation (incl. HEAD/406/Vary), Link header skip rules, AcceptHeaderParser q-values, agent-skills schema + digest verification, robots Content-Signal, and WebMCP tool registration via Playwright init-script stub. Out of scope (intentionally): OAuth/OIDC discovery, OAuth Protected Resource, MCP Server Card, and api-catalog (RFC 9727 requires real API endpoints; aspire.dev has none).

IEvangelist and others added 4 commits May 4, 2026 11:03
… robots Content-Signal, agent-skills, WebMCP)

Bring aspire.dev up to spec for the checks at https://isitagentready.com:

* RFC 8288 Link headers on HTML responses
  - LinkHeaderMiddleware advertises </llms.txt>; rel="llms",
    </.well-known/agent-skills/index.json>; rel="agent-skills",
    </sitemap-index.xml>; rel="sitemap", and a per-page rel="alternate"
    type="text/markdown" link when a .md companion exists.
  - Header attached via Response.OnStarting on 2xx text/html responses only;
    redirects, JSON, static assets, and well-known JSON are skipped.

* Cloudflare-style "Markdown for Agents" content negotiation
  - MarkdownNegotiationMiddleware handles Accept: text/markdown by streaming
    the .md companion (emitted by starlight-page-actions) directly via
    IFileProvider.SendFileAsync. No path rewrite, so no interaction with
    UseRouting / MapStaticAssets endpoint selection.
  - Cache-Control: private, max-age=0, must-revalidate ensures Front Door
    does NOT cache, avoiding Vary: Accept cache-key explosion.
  - 406 when markdown preferred but no companion AND no HTML acceptable.
  - HEAD parity, Vary: Accept on negotiated responses, infrastructure paths
    (.well-known, _astro, healthz, install., pagefind) bypass negotiation.

* Both new middlewares run BEFORE UseDefaultFiles + UseRouting (UseDefaultFiles
  rewrites /foo/ -> /foo/index.html, breaking companion mapping; MapStaticAssets
  registers endpoints during UseRouting, so post-routing path rewrites do not
  re-trigger endpoint selection).

* robots.txt declares Content-Signal: ai-train=yes, search=yes, ai-input=yes
  inside the User-agent: * group (per draft-romm-aipref-contentsignals).

* /.well-known/agent-skills/index.json (Agent Skills Discovery RFC v0.2.0)
  with a getting-started-with-aspire SKILL.md and a digest field of the form
  sha256:<lowerhex>. compute-skill-digests.mjs recomputes / verifies on every
  build; pnpm lint runs verify-skill-digests in --check mode.
  .gitattributes pins LF for the agent-skills artifacts so digests are
  byte-stable across Windows / Linux checkouts.

* WebMCP integration on the Astro side
  - src/scripts/webmcp.ts feature-detects navigator.modelContext.registerTool
    and registers a single search-aspire-docs tool with a JSON Schema input.
  - Backed by src/scripts/search/* (SearchProvider abstraction with Pagefind
    today and a Typesense stub for the upcoming migration). The WebMCP tool
    surface is engine-agnostic so the Pagefind -> Typesense swap is a one-line
    change in src/scripts/search/index.ts.
  - Hooked into Head.astro via a single import line.

* New host-level tests in tests/StaticHost.Tests/
  - In-process TestServer with a temp wwwroot fixture (no frontend build
    required; PrivateAssets="all" on the frontend.esproj reference prevents
    the dist/ directory from leaking into test compilations).
  - 51 tests covering markdown negotiation (incl. HEAD, 406, fallback, Vary
    behavior, infrastructure-path skip), Link header content + skip rules,
    AcceptHeaderParser q-value handling, and the well-known artifacts.

* New Playwright spec tests/e2e/webmcp.spec.ts asserts that the homepage
  registers exactly one WebMCP tool (search-aspire-docs) when the runtime
  exposes navigator.modelContext, and that the absence of the API is
  non-fatal.

Out of scope (intentionally not advertised, would mislead agents):
* /.well-known/openid-configuration / oauth-authorization-server (no protected APIs).
* /.well-known/oauth-protected-resource (no protected resource).
* /.well-known/mcp/server-card.json (aspire.dev does not host an MCP server).
* /.well-known/api-catalog (RFC 9727 requires real API endpoints; aspire.dev
  exposes documentation, an LLM corpus, a sitemap, and an RSS feed - none of
  which are APIs in the RFC's sense).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ill conventions

The first cut was inaccurate — it told agents to `mkdir my-aspire-app && cd
my-aspire-app && aspire new` and then non-interactively pick a template. In
reality `aspire new` is fully interactive and creates its own project folder,
so the mkdir+cd pattern is wrong and the fabricated template flags would
mislead agents.

Rewrite the skill to align with the conventions used by the official skill
at github.com/microsoft/aspire/tree/main/.agents/skills/aspire while keeping
this one short and focused on getting started:

* Frontmatter description is now a long when-to-use / when-not-to-use
  sentence in the same shape as the official skill.
* Body: install, `aspire new` (interactive, no fabricated flags), `aspire
  start` (called out as the agent-friendly path vs. `aspire run` which
  blocks the terminal), and a concise list of authoritative references.
* Explicit pointer to the official `aspire` skill for the operate-an-existing-
  app workflow, so an agent that has both available picks the right one.
* Updated index.json description to match the new framing.
* compute-skill-digests.mjs refreshed the sha256 to reflect the new bytes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Aspire is a polyglot stack — the AppHost can be authored in C# or TypeScript
today, with additional languages (Java, Go, Python, Rust, …) on the roadmap.
Calling it "the .NET cloud-native stack" was both inaccurate and misleading
to agents who would then assume C#-only tooling and dismiss the TypeScript
AppHost path.

Re-runs compute-skill-digests.mjs to update the index.json digest to match
the corrected SKILL.md bytes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Concerns flagged by the user:
1. LinkHeaderMiddleware.ShouldSkip duplicated the infrastructure path list
   maintained on MarkdownPathMapper.IsInfrastructurePath. Fixed by routing the
   path-skip check through the helper so both middlewares stay in lock-step.
2. Not every page on aspire.dev produces a `.md` companion (DocFX-rendered
   /reference/api/**, the search route, Lunaria stats, redirects, the 404
   page). The previous mapper accepted any `.md` that happened to exist on
   disk, so a stray markdown file with no real HTML page would have been
   advertised via the `Link: rel="alternate"; type="text/markdown"` header
   and served by the negotiation middleware on `Accept: text/markdown`.
   Fixed by requiring BOTH the `.md` AND the corresponding HTML page to
   exist before declaring a companion. Adds new xUnit cases pinning the
   stray-md scenario for both middlewares.

Additional cleanup along the way:
* AcceptHeaderParser.PrefersMarkdown: removed a dead `htmlQ` assignment and
  hoisted `HighestExplicitQuality` to a private static method so markdown
  and html lookups go through the same helper.
* Extracted shared sample HTML/Markdown bodies and seed helpers used by
  LinkHeaderTests + MarkdownNegotiationTests into SamplePages so the
  on-disk Starlight layout is described in one place.

Tests: 57 passing (was 51), 0 warnings, 0 errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@IEvangelist IEvangelist marked this pull request as ready for review May 4, 2026 17:00
Copilot AI review requested due to automatic review settings May 4, 2026 17:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds “agent-readiness” features to the StaticHost and frontend to align aspire.dev with common agent discovery/interaction checks (Link headers, markdown negotiation, Content-Signal, agent-skills artifacts + digesting, and WebMCP tool registration), plus a dedicated StaticHost test project and Playwright coverage.

Changes:

  • Introduces UseAgentReadiness() middleware composition (markdown content negotiation + RFC 8288 Link headers) and wires it into the StaticHost pipeline before UseDefaultFiles/UseRouting.
  • Adds .well-known/agent-skills discovery artifacts (plus digest computation + LF enforcement) and a robots.txt Content-Signal directive.
  • Adds frontend WebMCP registration (search-aspire-docs) backed by a pluggable search provider layer, with Playwright e2e tests.

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/StaticHost.Tests/WellKnownArtifactTests.cs Verifies robots.txt Content-Signal and agent-skills discovery/digests + LF-only enforcement.
tests/StaticHost.Tests/StaticHost.Tests.csproj New StaticHost-focused test project wiring.
tests/StaticHost.Tests/SamplePages.cs Shared HTML/MD fixtures for middleware tests.
tests/StaticHost.Tests/MarkdownPathMapperTests.cs Unit coverage for request-path → markdown-companion mapping rules.
tests/StaticHost.Tests/MarkdownNegotiationTests.cs Host-level tests for Accept negotiation (GET/HEAD/406/Vary/Cache-Control).
tests/StaticHost.Tests/LinkHeaderTests.cs Host-level tests for Link header attach/skip rules.
tests/StaticHost.Tests/GlobalUsings.cs Global usings for the new test project.
tests/StaticHost.Tests/AgentReadinessTestServer.cs In-proc TestServer harness mirroring production middleware ordering.
tests/StaticHost.Tests/AcceptHeaderParserTests.cs Direct q-value parsing/predicate tests.
src/statichost/StaticHost/StaticHost.csproj Makes frontend ESProj reference non-transitive via PrivateAssets=all.
src/statichost/StaticHost/Properties/AssemblyInfo.cs InternalsVisibleTo for StaticHost.Tests.
src/statichost/StaticHost/Program.cs Wires app.UseAgentReadiness() before default files/routing.
src/statichost/StaticHost/GlobalUsings.cs Adds global using for AgentReadiness namespace.
src/statichost/StaticHost/AgentReadiness/MarkdownPathMapper.cs Central path mapping + infrastructure skip rules.
src/statichost/StaticHost/AgentReadiness/MarkdownNegotiationMiddleware.cs Serves .md companions based on Accept preferences + caching semantics.
src/statichost/StaticHost/AgentReadiness/LinkHeaderMiddleware.cs Emits discovery Link headers on successful HTML responses.
src/statichost/StaticHost/AgentReadiness/AgentReadinessExtensions.cs Composition root extension and enforced middleware ordering.
src/statichost/StaticHost/AgentReadiness/AcceptHeaderParser.cs Minimal Accept parser focused on html/markdown preference logic.
src/frontend/tsconfig.json Adds @scripts/* path mapping.
src/frontend/tests/e2e/webmcp.spec.ts Playwright coverage for WebMCP tool registration + non-fatal absence.
src/frontend/src/scripts/webmcp.ts Registers search-aspire-docs tool when navigator.modelContext is present.
src/frontend/src/scripts/search/typesense-provider.ts Stub Typesense provider for future migration.
src/frontend/src/scripts/search/SearchProvider.ts Shared provider interface + response/result shapes.
src/frontend/src/scripts/search/pagefind-provider.ts Pagefind-backed provider (dynamic import) with graceful unavailability.
src/frontend/src/scripts/search/index.ts Provider selection via PUBLIC_SEARCH_PROVIDER.
src/frontend/src/components/starlight/Head.astro Imports WebMCP registration script site-wide.
src/frontend/scripts/compute-skill-digests.mjs Recomputes/validates agent-skills SHA-256 digests from raw bytes.
src/frontend/public/robots.txt Adds Content-Signal directive under User-agent: *.
src/frontend/public/.well-known/agent-skills/index.json New agent-skills discovery index (v0.2.0).
src/frontend/public/.well-known/agent-skills/getting-started-with-aspire/SKILL.md New skill document for “getting started” guidance.
src/frontend/package.json Adds digest compute/verify scripts; runs them in dev/build/lint.
Aspire.Dev.slnx Adds StaticHost.Tests to solution.
.gitattributes Forces LF in agent-skills artifacts to keep digests byte-stable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/frontend/scripts/compute-skill-digests.mjs Outdated
Comment thread tests/StaticHost.Tests/WellKnownArtifactTests.cs
* MarkdownNegotiationMiddleware: drop the stale reference to the
  `HasMarkdownCompanion` API (which never existed in this PR's final
  form); point readers at `MarkdownPathMapper.TryGetMarkdownCompanion`
  instead so the comment matches the live code.
* compute-skill-digests.mjs: harden `resolvePublicPath` against `..`
  traversal. Switches from `path.join` to `path.resolve` and asserts the
  result stays under `publicRoot` so a malicious or malformed `url` in
  index.json (e.g. `/.well-known/agent-skills/../../../../etc/passwd`)
  cannot read bytes outside the published public/ tree in dev/CI.
* WellKnownArtifactTests: rename `Agent_skills_files_are_LF_only` to
  `AgentSkills_files_are_LF_only` to match the surrounding
  `AgentSkills_*` PascalCase prefix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants