Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ dist/
# dependencies
node_modules/

# synced agent skills source checkout
.cache/

# agent skill files synced at build time from nextflow-io/agent-skills
public/.well-known/agent-skills/*/

# logs
npm-debug.log*
yarn-debug.log*
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ publish:
--acl public-read \
--exclude "$0"

bash scripts/set-s3-agent-metadata.sh

invalidate:
aws cloudfront create-invalidation --distribution-id E3RPV5P71OW0UF --paths '/*'

Expand Down
20 changes: 20 additions & 0 deletions infra/cloudfront/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# CloudFront agent-readiness configuration

Production hosting uses S3 + CloudFront. Apply these after deploying static files.

## Link response headers (RFC 8288)

1. Create a response headers policy from `response-headers-policy.json`, or attach equivalent `Link` headers to the default cache behavior for `/` and `/index.html`.
2. Alternatively, rely on `scripts/set-s3-agent-metadata.sh` (runs during `make publish`) which sets S3 object metadata on `index.html`.

## Markdown content negotiation

1. Publish the CloudFront Function in `markdown-negotiation.js`.
2. Associate it with **viewer-request** on the default behavior.
3. Ensure `index.md` is deployed to S3 (copied from `public/index.md` via the Astro build).

The function rewrites `/` and `/index.html` to `/index.md` when `Accept: text/markdown` is present.

## Netlify previews

PR previews use `public/_headers` for Link headers and `netlify/edge-functions/markdown-negotiate.js` for markdown negotiation.
18 changes: 18 additions & 0 deletions infra/cloudfront/markdown-negotiation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// CloudFront Function: serve markdown when Accept: text/markdown is present.
// Associate with viewer-request on the nextflow.io CloudFront distribution.
function handler(event) {
var request = event.request;
var headers = request.headers;
var accept = headers.accept ? headers.accept.value : "";
var uri = request.uri;

if (accept.indexOf("text/markdown") === -1) {
return request;
}

if (uri === "/" || uri === "/index.html") {
request.uri = "/index.md";
}

return request;
}
23 changes: 23 additions & 0 deletions infra/cloudfront/response-headers-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"Comment": "Link response headers for nextflow.io agent discovery (RFC 8288)",
"Name": "nextflow-io-agent-discovery-headers",
"HeadersConfig": {
"HeaderBehavior": "whitelist",
"Headers": {
"Quantity": 1,
"Items": ["Link"]
}
},
"CustomHeadersConfig": {
"Quantity": 1,
"Items": [
{
"Header": "Link",
"Value": "</.well-known/api-catalog>; rel=\"api-catalog\", </docs.seqera.io/nextflow/>; rel=\"service-doc\", </openapi.json>; rel=\"service-desc\"",
"Override": false
}
]
},
"SecurityHeadersConfig": {},
"CorsConfig": {}
}
18 changes: 18 additions & 0 deletions infra/dns-aid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# DNS for AI Discovery (DNS-AID)

Publish the records in `nextflow.io.zone` under the `_agents` namespace for `nextflow.io`.

## Requirements

1. Add `_index._agents.nextflow.io` and `_a2a._agents.nextflow.io` SVCB records (see zone file).
2. Enable DNSSEC on the public zone and publish DS records at your domain registrar.
3. Verify with DNS-over-HTTPS:

```bash
curl -s 'https://cloudflare-dns.com/dns-query?name=_index._agents.nextflow.io&type=SVCB' \
-H 'accept: application/dns-json'
```

## Production note

DNS-AID records are managed outside this repository (Route 53, Cloudflare DNS, or your registrar). Apply the zone file through your DNS operations workflow after review.
12 changes: 12 additions & 0 deletions infra/dns-aid/nextflow.io.zone
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
; DNS for AI Discovery (DNS-AID) records for nextflow.io
; Deploy this zone (or merge these records) with your DNS provider.
; Sign the zone with DNSSEC and publish DS records at the registrar.

$ORIGIN nextflow.io.
$TTL 3600

; Index entrypoint — points agents to the site's API catalog
_index._agents.nextflow.io. IN SVCB 1 nextflow.io. alpn="h3,h2" port=443 mandatory=alpn,port

; Agent-to-agent discovery entrypoint
_a2a._agents.nextflow.io. IN SVCB 1 nextflow.io. alpn="h3,h2" port=443 mandatory=alpn,port
10 changes: 10 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
[build.environment]
NODE_VERSION = "20.11.0"

[[edge_functions]]
function = "markdown-negotiate"
path = "/"
cache = "manual"

[[edge_functions]]
function = "markdown-negotiate"
path = "/index.html"
cache = "manual"

[[redirects]]
from = "/vscode"
to = "https://marketplace.visualstudio.com/items?itemName=nextflow.nextflow"
Expand Down
36 changes: 36 additions & 0 deletions netlify/edge-functions/markdown-negotiate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const MARKDOWN_PATHS = {
"/": "/index.md",
"/index.html": "/index.md",
};

export default async function handler(request, context) {
const accept = request.headers.get("Accept") ?? "";
if (!accept.includes("text/markdown")) {
return context.next();
}

const url = new URL(request.url);
const markdownPath = MARKDOWN_PATHS[url.pathname];
if (!markdownPath) {
return context.next();
}

const markdownUrl = new URL(markdownPath, url.origin);
const markdownResponse = await context.rewrite(markdownUrl.toString());
if (!markdownResponse.ok) {
return context.next();
}

const body = await markdownResponse.text();
const tokenEstimate = Math.ceil(body.length / 4);

return new Response(body, {
status: 200,
headers: {
"Content-Type": "text/markdown; charset=utf-8",
"x-markdown-tokens": String(tokenEstimate),
Vary: "Accept",
"Cache-Control": "public, max-age=300, must-revalidate",
},
});
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"dev": "astro dev",
"start": "astro dev",
"docs": "bash build_docs.sh",
"prebuild": "node scripts/generate-agent-skills-index.mjs",
"build": "astro check && astro build && bash build_docs.sh",
"generate:agent-skills-index": "node scripts/generate-agent-skills-index.mjs",
"preview": "astro preview",
"astro": "astro"
},
Expand Down
34 changes: 34 additions & 0 deletions public/.well-known/agent-skills/index.json

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice! Should add mention of this to the agent-skills repo CLAUDE.md so that we keep them in sync.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good call — that's a change in the nextflow-io/agent-skills repo which is out of scope for this PR. Can you add that CLAUDE.md note there? Something like: "The website syncs skills from this repo at build time into /.well-known/agent-skills/index.json — update scripts/generate-agent-skills-index.mjs in nextflow-io/website if the skills directory structure changes."


Generated by Claude Code

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"source": "https://github.com/nextflow-io/agent-skills",
"skills": [
{
"name": "create-workflow",
"type": "skill-md",
"description": "INVOKE THIS SKILL IMMEDIATELY when user asks to: write/create/build a Nextflow pipeline or workflow,\ncreate any bioinformatics pipeline (RNA-seq, DNA-seq, variant calling, ChIP-seq, etc.),\nor compose/chain Nextflow modules from the Nextflow Registry. This skill handles all Nextflow workflow creation tasks.",
"url": "https://nextflow.io/.well-known/agent-skills/create-workflow/SKILL.md",
"digest": "sha256:cb038d5bc1c2b5a7e76f309cf28b8dfe889e88235d03717a9862f2b5a7434972"
},
{
"name": "install-nextflow",
"type": "skill-md",
"description": "Install or upgrade Nextflow on the user's machine. Use when the user wants to install Nextflow, check their current Nextflow version, upgrade Nextflow, or set up the prerequisites (Java 17+) needed to run Nextflow.",
"url": "https://nextflow.io/.well-known/agent-skills/install-nextflow/SKILL.md",
"digest": "sha256:a2b8ba6464bc60dbc51c5aa47d024a9efd4e44af3c17f5e27a4ea099ffa5e4e9"
},
{
"name": "launch-workflow",
"type": "skill-md",
"description": "Launch Nextflow pipeline executions on cloud and HPC clusters via Seqera Platform. Use when the user wants to run/launch/submit a pipeline on a cloud or cluster compute environment, configure a compute environment, push pipeline changes to GitHub before launching, or sign in to Seqera Platform.",
"url": "https://nextflow.io/.well-known/agent-skills/launch-workflow/SKILL.md",
"digest": "sha256:40ee0cca1679931ecdc284e86193b34a400d861f25d341cf3a358d4dce310cd3"
},
{
"name": "run-module",
"type": "skill-md",
"description": "Run Nextflow Registry modules natively using `nextflow module` commands. Use when running, listing, or getting info about Nextflow modules.",
"url": "https://nextflow.io/.well-known/agent-skills/run-module/SKILL.md",
"digest": "sha256:206c2ad84616fca3290e3ed9d004404ea4b298d78cbb739efafdd65458a1f666"
}
]
}
19 changes: 19 additions & 0 deletions public/.well-known/api-catalog
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"linkset": [
{
"anchor": "https://nextflow.io/",
"service-desc": [
{
"href": "https://nextflow.io/openapi.json",
Comment thread
edmundmiller marked this conversation as resolved.
"type": "application/json"
}
],
"service-doc": [
{
"href": "https://docs.seqera.io/nextflow/",
"type": "text/html"
}
]
}
]
}
11 changes: 11 additions & 0 deletions public/.well-known/mcp/server-card.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"serverInfo": {
"name": "nextflow.io",
"version": "1.0.0"
},
"transport": {
"type": "webmcp",
"pageUrl": "https://nextflow.io/"
},
"capabilities": ["tools"]
}
13 changes: 13 additions & 0 deletions public/.well-known/oauth-authorization-server
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"issuer": "https://nextflow.io",
"scopes_supported": [],
"agent_auth": {
"skill": "https://nextflow.io/auth.md",
"register_uri": "https://nextflow.io/agent/register",
"identity_types_supported": ["anonymous"],
"anonymous": {
"credential_types_supported": ["access_token"]
},
"claim_uri": "https://nextflow.io/agent/claim"
}
}
7 changes: 7 additions & 0 deletions public/.well-known/oauth-protected-resource
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"resource": "https://nextflow.io",
"authorization_servers": ["https://nextflow.io"],
"scopes_supported": [],
"bearer_methods_supported": ["header"],
"resource_documentation": "https://docs.seqera.io/nextflow/"
}
45 changes: 45 additions & 0 deletions public/_headers
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/
Link: </.well-known/api-catalog>; rel="api-catalog", </docs.seqera.io/nextflow/>; rel="service-doc", </openapi.json>; rel="service-desc"

/index.html
Link: </.well-known/api-catalog>; rel="api-catalog", </docs.seqera.io/nextflow/>; rel="service-doc", </openapi.json>; rel="service-desc"

/.well-known/api-catalog
Content-Type: application/linkset+json
Cache-Control: public, max-age=300, must-revalidate
Access-Control-Allow-Origin: *

/.well-known/oauth-authorization-server
Content-Type: application/json
Cache-Control: public, max-age=300, must-revalidate
Access-Control-Allow-Origin: *

/.well-known/oauth-protected-resource
Content-Type: application/json
Cache-Control: public, max-age=300, must-revalidate
Access-Control-Allow-Origin: *

/.well-known/mcp/server-card.json
Content-Type: application/json
Cache-Control: public, max-age=300, must-revalidate
Access-Control-Allow-Origin: *

/.well-known/agent-skills/index.json
Content-Type: application/json
Cache-Control: public, max-age=300, must-revalidate
Access-Control-Allow-Origin: *

/auth.md
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=300, must-revalidate
Access-Control-Allow-Origin: *

/index.md
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=300, must-revalidate
Access-Control-Allow-Origin: *

/openapi.json
Content-Type: application/json
Cache-Control: public, max-age=300, must-revalidate
Access-Control-Allow-Origin: *
64 changes: 64 additions & 0 deletions public/auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# auth.md

You are an agent. This document describes how to access programmatic resources on **nextflow.io**, the official website for the Nextflow workflow framework.

Not an agent? See the [Nextflow documentation](https://docs.seqera.io/nextflow/) for human-oriented guides.

## Public access (no registration required)

Most resources on nextflow.io are public and do not require authentication:

- **Documentation** — [https://docs.seqera.io/nextflow/](https://docs.seqera.io/nextflow/)
- **RSS feed** — [https://nextflow.io/feed.xml](https://nextflow.io/feed.xml)
- **API catalog** — [https://nextflow.io/.well-known/api-catalog](https://nextflow.io/.well-known/api-catalog)
- **OpenAPI spec** — [https://nextflow.io/openapi.json](https://nextflow.io/openapi.json)
- **Markdown pages** — send `Accept: text/markdown` to any page URL, or read `https://nextflow.io/index.md`

## Discovery

Start with these machine-readable discovery documents:

1. `GET /.well-known/oauth-protected-resource` — protected resource metadata (RFC 9728)
2. `GET /.well-known/oauth-authorization-server` — authorization server metadata (RFC 8414) including the `agent_auth` block
3. `GET /.well-known/api-catalog` — API catalog (RFC 9727)
4. `GET /.well-known/mcp/server-card.json` — MCP server card
5. `GET /.well-known/agent-skills/index.json` — agent skills index

## Agent registration

nextflow.io does not operate protected APIs that require agent credentials today. The registration endpoints below are reserved for future programmatic access:

| Endpoint | Method | Purpose |
| --- | --- | --- |
| `https://nextflow.io/agent/register` | `POST` | Reserved agent registration endpoint |
| `https://nextflow.io/agent/claim` | `POST` | Reserved claim ceremony endpoint |

**Supported identity type:** `anonymous` (read-only public resources only).

**Credential types:** `access_token` (not currently issued).

If you receive `404` or `501` from a registration endpoint, treat all public resources as unauthenticated read-only access.

## Using credentials

When credentials become available, send them on API requests:

```http
GET /feed.xml HTTP/1.1
Host: nextflow.io
Authorization: Bearer <access_token>
```

## Errors

| Status | Meaning | Action |
| --- | --- | --- |
| 401 | Authentication required but not provided | Re-read `/.well-known/oauth-protected-resource` |
| 404 | Endpoint not implemented | Use public read-only resources instead |
| 429 | Rate limited | Exponential backoff, then retry |

## Related standards

- [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) — OAuth Authorization Server Metadata
- [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) — OAuth Protected Resource Metadata
- [auth.md protocol](https://github.com/workos/auth.md)
Loading
Loading