Add OG social cards, Mermaid diagrams, and robots.txt

- OG images: auto-generated per page via astro-og-canvas with maritime
  teal theme, wired through Starlight route data middleware
- Mermaid: request flow and parallel fetch diagrams on architecture page
- robots.txt: sitemap and llms.txt references for crawlers
This commit is contained in:
Ryan Malloy 2026-02-24 13:33:34 -07:00
parent e519c100ed
commit a5009941ab
7 changed files with 1444 additions and 3 deletions

View File

@ -1,4 +1,5 @@
import { defineConfig } from "astro/config"; import { defineConfig } from "astro/config";
import mermaid from "astro-mermaid";
import starlight from "@astrojs/starlight"; import starlight from "@astrojs/starlight";
import starlightLlmsTxt from "starlight-llms-txt"; import starlightLlmsTxt from "starlight-llms-txt";
@ -10,10 +11,12 @@ export default defineConfig({
telemetry: false, telemetry: false,
devToolbar: { enabled: false }, devToolbar: { enabled: false },
integrations: [ integrations: [
mermaid(),
starlight({ starlight({
title: "mcnoaa-tides", title: "mcnoaa-tides",
description: description:
"FastMCP server for NOAA CO-OPS tide predictions, water levels, and marine conditions.", "FastMCP server for NOAA CO-OPS tide predictions, water levels, and marine conditions.",
routeMiddleware: "./src/routeData.ts",
plugins: [ plugins: [
starlightLlmsTxt({ starlightLlmsTxt({
projectName: "mcnoaa-tides", projectName: "mcnoaa-tides",

1314
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,8 @@
"@iconify-json/lucide": "^1.2.91", "@iconify-json/lucide": "^1.2.91",
"astro": "^5.6.1", "astro": "^5.6.1",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"astro-mermaid": "^1.3.1",
"astro-og-canvas": "^0.10.1",
"sharp": "^0.34.2", "sharp": "^0.34.2",
"starlight-llms-txt": "^0.7.0" "starlight-llms-txt": "^0.7.0"
} }

7
docs/public/robots.txt Normal file
View File

@ -0,0 +1,7 @@
User-agent: *
Allow: /
Sitemap: https://mcnoaa-tides.warehack.ing/sitemap-index.xml
# LLM documentation — https://llmstxt.org/
LLMs-Txt: https://mcnoaa-tides.warehack.ing/llms.txt

View File

@ -9,6 +9,35 @@ import { Aside, Card, CardGrid, Steps, FileTree, Badge } from "@astrojs/starligh
mcnoaa-tides is a FastMCP server that wraps the NOAA CO-OPS Tides and Currents API. This page describes how the pieces fit together -- the server lifecycle, caching strategy, parallel fetch patterns, and module organization. mcnoaa-tides is a FastMCP server that wraps the NOAA CO-OPS Tides and Currents API. This page describes how the pieces fit together -- the server lifecycle, caching strategy, parallel fetch patterns, and module organization.
## Request Flow
```mermaid
flowchart LR
User([User])
LLM[LLM Client]
MCP[MCP Transport<br/>stdio / HTTP]
Server[mcnoaa-tides<br/>FastMCP Server]
Cache[(Station<br/>Cache)]
DataAPI[NOAA Data API<br/>Predictions &<br/>Observations]
MetaAPI[NOAA Metadata API<br/>Station Catalog]
User -->|natural language| LLM
LLM -->|tool call| MCP
MCP --> Server
Server -->|cache hit| Cache
Server -->|parallel fetch| DataAPI
Server -->|catalog refresh| MetaAPI
Cache -.->|24h TTL| MetaAPI
style User fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
style LLM fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
style MCP fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
style Server fill:#1a8a8f,stroke:#5ec4c8,color:#0a1517
style Cache fill:#0d3b3e,stroke:#5ec4c8,color:#e8f0f0
style DataAPI fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
style MetaAPI fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
```
## Server Lifecycle ## Server Lifecycle
The server uses FastMCP's lifespan context manager to own the `NOAAClient` instance. Every tool call receives the same client through the lifespan context, so there is exactly one HTTP connection pool and one station cache for the entire server process. The server uses FastMCP's lifespan context manager to own the `NOAAClient` instance. Every tool call receives the same client through the lifespan context, so there is exactly one HTTP connection pool and one station cache for the entire server process.
@ -86,9 +115,28 @@ Several tools fire multiple API calls simultaneously using `asyncio.gather`. Thi
Fetches 6 products in parallel: Fetches 6 products in parallel:
``` ```mermaid
predictions (hilo) | water_level | water_temperature flowchart LR
air_temperature | wind | air_pressure Tool[marine_conditions<br/>_snapshot]
P[predictions<br/>hilo]
WL[water_level]
WT[water_temperature]
AT[air_temperature]
W[wind]
AP[air_pressure]
R[Combined<br/>Response]
Tool --> P & WL & WT & AT & W & AP
P & WL & WT & AT & W & AP --> R
style Tool fill:#1a8a8f,stroke:#5ec4c8,color:#0a1517
style R fill:#1a8a8f,stroke:#5ec4c8,color:#0a1517
style P fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
style WL fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
style WT fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
style AT fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
style W fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
style AP fill:#0d3b3e,stroke:#1a8a8f,color:#e8f0f0
``` ```
Each product is fetched independently. If a product fails (sensor not available at the station, temporary API error), it is recorded under an `"unavailable"` key in the response rather than failing the entire request. Each product is fetched independently. If a product fails (sensor not available at the station, temporary API error), it is recorded under an `"unavailable"` key in the response rather than failing the entire request.

View File

@ -0,0 +1,40 @@
import { getCollection } from "astro:content";
import { OGImageRoute } from "astro-og-canvas";
const entries = await getCollection("docs");
const pages = Object.fromEntries(
entries.map(({ data, id }) => [id, { data }]),
);
export const { getStaticPaths, GET } = await OGImageRoute({
pages,
param: "slug",
getImageOptions: (_id, page: (typeof pages)[number]) => ({
title: page.data.title,
description: page.data.description,
bgGradient: [[10, 21, 23]],
border: { color: [26, 138, 143], width: 20 },
padding: 120,
font: {
title: {
size: 64,
weight: "Bold",
color: [232, 240, 240],
families: ["Inter"],
},
description: {
size: 28,
color: [196, 212, 214],
families: ["Inter"],
},
},
fonts: [
"https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-600-normal.woff2",
"https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-400-normal.woff2",
],
logo: {
path: "./public/favicon.svg",
size: [60],
},
}),
});

27
docs/src/routeData.ts Normal file
View File

@ -0,0 +1,27 @@
import { defineRouteMiddleware } from "@astrojs/starlight/route-data";
export const onRequest = defineRouteMiddleware((context) => {
const ogImageUrl = new URL(
`/og/${context.locals.starlightRoute.id || "index"}.png`,
context.site,
);
const { head } = context.locals.starlightRoute;
head.push({
tag: "meta",
attrs: { property: "og:image", content: ogImageUrl.href },
});
head.push({
tag: "meta",
attrs: { property: "og:image:width", content: "1200" },
});
head.push({
tag: "meta",
attrs: { property: "og:image:height", content: "630" },
});
head.push({
tag: "meta",
attrs: { name: "twitter:image", content: ogImageUrl.href },
});
});