Astro
Frontmatter-only reads, graftRoute mounts, and no request memo.
pnpm add @usegraft/sdk-astro @usegraft/auth @usegraft/mcpnpm i @usegraft/sdk-astro @usegraft/auth @usegraft/mcpbun add @usegraft/sdk-astro @usegraft/auth @usegraft/mcpBuild the handle once in a server-only module, then import it from frontmatter, endpoints, and middleware. Never from a client island.
Create the handle
graft init scaffolds the static index. This docs site is that path:
// src/lib/graft.ts
import { openStaticIndex } from "@usegraft/db";
import { createGraft } from "@usegraft/sdk-astro";
import { collections } from "../../graft.config";
export const graft = createGraft({
index: await openStaticIndex(".graft/index.db"),
collections,
});
On Postgres:
import { createDb } from "@usegraft/db";
import { createGraft } from "@usegraft/sdk-astro";
import { collections } from "../../graft.config";
export const db = createDb(process.env.DATABASE_URL!).db;
export const graft = createGraft({ db, collections });
Read one document
---
// src/pages/[slug].astro
import { graft } from "../lib/graft";
const page = await graft.getContent("pages", Astro.params.slug!);
---
Astro frontmatter is server-side unconditionally — the only adapter here that
is. There is no request-level memo, because React.cache has no Astro
equivalent. Reads go straight to the index, which is the right default:
prerendered pages read at build time, and an SSR page makes a handful of reads.
Mount functions and MCP
graftRoute binds a Graft handler to an Astro endpoint. It takes the handler,
not a config object: Astro hands over an APIContext, and the handlers want
its request.
// src/pages/api/fn/[name].ts
import { createFunctionsHandler } from "@usegraft/core";
import { graftRoute } from "@usegraft/sdk-astro";
import { functions } from "../../../graft.config";
import { db } from "../../lib/graft";
const handler = createFunctionsHandler({ db, functions });
export const POST = graftRoute(handler);
export const GET = graftRoute(handler); // 405s with Allow and a fix
// src/pages/api/mcp.ts
import { createActorResolver } from "@usegraft/auth";
import { createGraftMcpHandler } from "@usegraft/mcp";
import { graftRoute } from "@usegraft/sdk-astro";
import { collections, functions } from "../../graft.config";
import { db } from "../lib/graft";
const actor = createActorResolver({
issuers: (process.env.GRAFT_TRUSTED_ISSUERS ?? "")
.split(/[,\s]+/)
.filter(Boolean)
.map((issuer) => ({ issuer })),
devTokens: process.env.GRAFT_DEV_TOKEN
? {
[process.env.GRAFT_DEV_TOKEN]: {
kind: "human",
id: "owner",
scopes: ["content:write"],
},
}
: undefined,
});
export const POST = graftRoute(
createGraftMcpHandler({
contentDir: "./content",
db,
collections,
functions,
actor,
}),
);
This endpoint serves content writes and asset uploads. JWT verification runs
only when issuers is passed; the library does not read
GRAFT_TRUSTED_ISSUERS (graft serve does). See Auth.
Unauthenticated callers get 401. allowAnonymous: true is only for a
loopback local server — never on anything reachable from a network. A public
docs surface is createDocsMcpHandler, which has no write
tools and no anonymous opt-in.
The parameter is typed structurally as { request: Request }, so this package
needs no astro dependency.
Cache and invalidation
Astro has no tag-based data cache. Stamp tagsFor(...) into a CDN
surrogate-key header on SSR responses, and purge
tagsForChanges(branch, changeSet) from your compile webhook.
---
import { tagsFor } from "@usegraft/sdk-astro";
import { graft } from "../lib/graft";
const slug = Astro.params.slug!;
const page = await graft.getContent("pages", slug);
Astro.response.headers.set("Cache-Tag", tagsFor("main", "pages", slug).join(","));
---
Cache-Tag is the surrogate-key header. Purge the same strings
tagsForChanges returns from your compile webhook. The Postgres handle
snippet should export const db next to graft so the mounts above can
import it.
The caveat you cannot infer
Bodies come back as authored source. Render them with Astro's own MDX pipeline.
There is no MdxBody here — that evaluator is Next-only.
See SDK reference for mounts across adapters and the honest gaps.