aflink: search AF links

us.aflink由 aflink.us 發布官方發布者v1.0.0更新於 Nov 8, 2026

Read-only search of aflink.us, a public mirror of the U.S. Air Force portal quick links.

已驗證Streamable HTTP可網頁執行網頁搜尋與抓取知識與記憶

概覽

AI 產生的概覽

對 aflink.us 的唯讀搜尋服務,該站是美國空軍入口網站快速連結的公開鏡像,助理可依名稱、縮寫或用途查找空軍與國防部網站網址。

可以做什麼
  • 依名稱、縮寫、網址、分類或用途搜尋 aflink.us 連結目錄,查詢中的每個詞都必須相符
  • 回傳每筆連結的標題、網址與分類,標題相符的結果排在前面
  • 區分官方連結與 UNOFFICIAL 分類中的連結,後者會標示為非官方
  • 可選擇限制回傳結果的數量
適用情境
當助理需要找到某個空軍或國防部網站或工具的正確網址(例如薪資、差旅核銷或休假系統),希望快速查目錄而不是做一般網頁搜尋時適用。
執行需求
遠端 streamable HTTP 端點;不需要身分驗證、API 金鑰或 Cookie。
安裝前請注意
只回傳連結;多數官方網站仍需 CAC 或其他國防部登入,本伺服器不提供。UNOFFICIAL 分類中的連結屬於第三方網站,應謹慎對待。此目錄為非官方鏡像,重要網址請自行確認後再使用。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 aflink: search AF links,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/us.aflink%2Fsearch/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。

{
  "mcpServers": {
    "aflink-search-af-links": {
      "type": "http",
      "url": "https://api.aflink.us/mcp"
    }
  }
}

README

Search worker

Cloudflare Worker behind the address-bar autocomplete (OpenSearch suggestions) for aflink.us, and aflink's public, read-only MCP server. Deployed as aflink-autocomplete and served at https://api.aflink.us; the site points at it through suggestedURL in updater.js, which the browser discovers via osdd.xml.

How a suggestion round trips

  1. The browser calls GET /search/{searchTerms} while the user types.
  2. The worker answers ["<term>", ["<title>​ {CATEGORY}", ...]] — the title, a zero-width space, then the category.
  3. Picking one loads aflink.us/?q=<that whole string>.
  4. The page (src/includes/js/search.js) sees the zero-width space, searches on the title, finds the row whose title and category match the suggestion, and follows it.

So a suggestion is only useful if the page can find the same link. Anything the worker offers that the page hides, or matches differently, is a dead suggestion — the user picks it and lands somewhere else, or nowhere.

Staying in sync with the site

Two things keep the two ends together, and both are load-bearing:

  • Matching rules live in src/includes/js/search-match.js. The page inlines that file; this worker imports it. There is one implementation, so normalization, multi-word matching and title/URL/category matching cannot drift. Change matching there and both ends move together.
  • The link list is assembled from src/links/ by scripts/assemble-links.js, which both builds run: updater.js for the site and its links.json, and build-links.js for this worker, which bundles the result in the links.json shape. The worker rebuilds what the page displays from it: official categories in page order (skipping isDeleted links, which the page hides), then the unofficial list.

npm test in the repo root covers both — including checks that the links.json the site build actually produces is what the worker reads, and that the worker's bundle holds the same links.

Deploys

Cloudflare Workers Builds deploys the worker on every push to master that touches one of its watch paths, running cd search_worker && npx wrangler deploy. Wrangler first runs the build command in wrangler.jsonc, node build-links.js, which writes src/links.generated.js (gitignored) from that commit's src/links/. So the deployed worker carries exactly the links the commit lists: nothing is fetched at request time and nothing is cached.

That only works if a link-only change triggers a build, so the watch paths (Workers & Pages → aflink-autocomplete → Settings → Build) must cover everything the worker is built from:

search_worker/*src/links/*src/includes/js/search-match.jsscripts/assemble-links.jsscripts/link-status.js

A new file the worker or build-links.js imports goes on that list too. Retirement is by date (scripts/link-status.js), so a link that retires without any commit leaves the worker at the next link change; the weekly link health commit bounds that to a week.

Changes that need the worker updated

Change on the siteWhat the worker needs
How search matches (normalizing, word splitting, what fields count)Nothing, if the change is made in search-match.js; the worker imports it
A new field or source of links (another section, another JSON file)Add it in assemble-links.js (both builds pick it up); publishedLinks(), build-links.js's slim() and to_entries() must carry it, and a new file needs a watch path
A link becoming hidden on the page (a new flag like isDeleted)to_entries() must filter it out too
Renaming or restructuring links.jsonto_entries() reads the new shape
How the page resolves a chosen suggestion (?q= parsing)The suggestion format here has to match it

MCP server (POST /mcp)

The same worker is a Model Context Protocol server, so an AI agent can look up an Air Force or DoD site the way a person does on aflink.us. It is listed in the MCP Registry as us.aflink/search. The Registry verifies that name against the aflink.us domain itself (see below), and the endpoint is on the same domain, so the listing, the site and the server are visibly one publisher.

  • Endpoint: https://api.aflink.us/mcp (api.aflink.us is a Cloudflare custom domain on this worker, declared in wrangler.jsonc, that also serves the suggestions and submissions), Streamable HTTP transport (MCP spec 2025-11-25; 2025-06-18 and 2025-03-26 clients are also accepted).
  • One tool, search_links: takes a query (and an optional limit, 1–50, default 10) and returns matching links with title, URL, category and summary, title matches first. Links in the UNOFFICIAL category come back with official: false.
  • Read-only: the tool searches the link list bundled into the worker, the same list aflink.us publishes as links.json, and nothing else. It cannot add, change or request links; that still goes through the issue forms and their review.
  • No authentication: it serves only what https://aflink.us/links.json already publishes. No login, no API key, no cookies, and the worker stores nothing about the caller.
  • Same answers as the site: it uses the list and ranking the suggestions use (to_entries() and search() in src/index.js) and the page's own matching rules in search-match.js, so the parity rules above cover it too.

It is stateless: each POST gets one JSON response, no session is kept, and GET /mcp is 405 because the server never sends anything unprompted. Any web origin may call it (CORS *), since there is nothing private to protect; an Origin that is not an http(s) origin is refused, as the transport spec asks. The code is src/mcp.js with no SDK dependency, and test/mcp-server.test.js covers the protocol, the tool and server.json.

Discovery. Besides the Registry listing, the server can be found from the domain itself, as the MCP Server Cards extension (SEP-2127) describes. src/.well-known/ai-catalog.json is served at https://aflink.us/.well-known/ai-catalog.json and lists one entry, which points to the server card at GET https://api.aflink.us/mcp/server-card, the place the spec reserves for a server at /mcp. The card (SERVER_CARD in mcp.js) gives the name, version, description and endpoint, with CORS, an ETag and an hour's caching; it does not list tools, which clients still get from tools/list. It is built in mcp.js rather than read from server.json, so the worker needs no new watch path, and a test checks the two agree. Both formats are new, so update them if the extension changes.

To try it:

sh
claude mcp add --transport http aflink https://api.aflink.us/mcp# or, with the MCP Inspectornpx @modelcontextprotocol/inspector   # transport "Streamable HTTP", the URL above

Publishing to the MCP Registry

server.json in the repository root is the Registry entry. .github/workflows/mcp_publish.yml publishes it, run by hand from the Actions tab:

  1. Merge the change to master. Cloudflare Workers Builds deploys the worker on every push to master that touches search_worker/, and GitHub Pages rebuilds the site; wait for both.
  2. Run Publish MCP server. It runs the MCP tests, checks that https://api.aflink.us/mcp answers initialize as us.aflink/search at the version in server.json and that aflink.us serves the public key, then logs in and publishes.

How the Registry knows aflink.us is ours. The us.aflink/* namespace is proven over HTTP: src/.well-known/mcp-registry-auth holds an Ed25519 public key (v=MCPv1; k=ed25519; p=…), served at https://aflink.us/.well-known/mcp-registry-auth, and the workflow signs its login with the matching private key. Jekyll and the Pages upload skip dot-folders, so src/_config.yml includes it and build.yml uploads hidden files; a test checks both. The private key is the MCP_REGISTRY_KEY secret of the mcp-registry-publish environment, limited to master, because it can publish anything under us.aflink/*. To rotate it, generate a new pair, replace the file and the secret, and let Pages rebuild before publishing.

The custom domain. api.aflink.us was attached once in the dashboard: adding a domain needs zone permission the Workers Builds token does not have, while a deploy that leaves an existing domain as it is needs none. wrangler.jsonc lists it so deploys keep it, and sets workers_dev: true, because a config with routes otherwise turns the workers.dev URL off. The site and getlinks.js now use api.aflink.us; the workers.dev URL stays on only for a month while traffic moves over, and is to be turned off (workers_dev: false) after 2026-12-07.

For a new listing, bump version in server.json and SERVER_VERSION in src/mcp.js together (a test checks they agree, and that the server card matches server.json), merge, let the worker build finish, and run the workflow again. The Registry refuses a version it already has.

Development

sh
npm install          # in this directorynpm run dev          # serves the worker on :8787npm run deploy       # wrangler deploy (by hand; normally Workers Builds deploys on push to master)

A development build of the site (npm run dev:build in the repo root) points its suggestions at localhost:8787, so the two can be exercised together. npm run dev bundles the links from your working copy of src/links/, so a local edit shows up when the worker restarts. npm test in the repo root bundles them too (its pretest step).

Portal link submissions (POST /submit)

The same worker receives link lists from getlinks.js, run in the browser console on any AF Portal page: the script fetches every list the portal has and sends them together as { pages: [{ source, afpCategorizedLinksDto }] }. The worker starts the Portal Sync workflow (.github/workflows/portal_sync.yml), which merges the lists on the portal-sync branch and opens or updates one pull request. Nothing reaches the site until that PR is merged.

The endpoint is public, so src/submit.js is strict:

  • Browser origin must be the AF Portal.
  • Every page's source must be one of KNOWN_SOURCES, each at most once; a new portal list is added there and to PAGES in getlinks.js.
  • Only title, link, contentId and a few known fields are kept, each checked for shape and length; the whole payload is capped at 512 KB.
  • Per-address and daily rate limits, counted in the LINKS KV namespace; an identical payload within a day is acknowledged but not re-sent.
  • The packed payload is kept in KV for a day and served at GET /submission/<id>; the workflow receives only the id, because workflow_dispatch inputs are capped at 64 KB and the lists with their descriptions are larger.
  • On the workflow side, scripts/af-guard.js fails the run (and opens an issue) if a submission would move an existing link to a non-official domain.

It needs one secret, set with wrangler secret put GITHUB_DISPATCH_TOKEN: a fine-grained GitHub token for the aflink repository only, with Actions: read and write and nothing else. That is enough to start a workflow run and not enough to push code. Give it an expiry and rotate it. Without the secret the endpoint answers 503 and getlinks.js falls back to the compressed-data box for the Update AF Links issue form.

來源:search_worker/README.md,提交 de35fed

工具

1

版本歷史

1
  1. v1.0.0最新Nov 8, 2026

評分

已滿足適用檢查項的 100%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具必需15 分
  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分