Skip to content
Merged
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
31 changes: 28 additions & 3 deletions apps/web/scripts/prerender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,34 @@ function homePageHtml(): string {
<p>The OOXML spec, explained by people who actually implemented it.</p>
<p>Live previews, implementation notes, and what the spec doesn't tell you.</p>
<a href="/docs/">Browse Reference</a>
<a href="/cli/">Get the CLI</a>
${navHtml()}
</main>`;
}

function cliPageHtml(): string {
return `<main>
<h1>OOXML reference from your terminal</h1>
<p>Search the ECMA-376 specification and inspect OOXML schemas without leaving your terminal.</p>
<h2>Install</h2>
<p>Node.js 20 or later is required.</p>
<pre><code>npm install --global @ooxml-dev/cli</code></pre>
<h2>Try it</h2>
<pre><code>ooxml login
ooxml search &quot;paragraph spacing&quot;
ooxml element w:p</code></pre>
<p>The CLI uses the free, hosted ooxml.dev service. You may need to create an account and sign in. Sign-in is only used to control usage.</p>
<h2>Use it with a coding agent</h2>
<pre><code>npx skills add superdoc/ooxml-dev --skill research-ooxml -g -y</code></pre>
${navHtml()}
</main>`;
}

function mcpPageHtml(): string {
return `<main>
<h1>OOXML reference for AI assistants</h1>
<p>Connect your MCP-compatible client and get both natural-language search across the ECMA-376 spec and deterministic schema lookup over the parsed XSDs.</p>
<p>Latest MCP (2026-07-28) · Stateless</p>
<p>Connect an MCP client to search spec prose, inspect XSD schemas, look up OPC package metadata, and query preset shape guides.</p>
<h2>Prose search</h2>
<ul>
<li><strong>ooxml_search</strong> — Semantic search across 18,000+ spec chunks.</li>
Expand All @@ -145,8 +165,12 @@ function mcpPageHtml(): string {
<li><strong>ooxml_enum</strong> — Enumeration values for a simpleType.</li>
<li><strong>ooxml_namespace</strong> — What's known about a namespace URI.</li>
</ul>
<h2>Package metadata</h2>
<ul><li><strong>ooxml_package_part</strong> — Look up OPC part types.</li></ul>
<h2>Preset shapes</h2>
<ul><li><strong>ooxml_preset_shape</strong> — Look up adjustment guides for a preset shape.</li></ul>
<h2>What is MCP?</h2>
<p>The Model Context Protocol (MCP) is an open standard that lets AI assistants connect to external data sources and tools. Works with Claude Code, Codex CLI, Cursor, and any MCP-compatible client.</p>
<p>The Model Context Protocol (MCP) is an open standard that lets AI assistants connect to external data sources and tools. This server works with Claude Code, Codex CLI, Cursor, and other clients that support Streamable HTTP.</p>
${navHtml()}
</main>`;
}
Expand Down Expand Up @@ -223,7 +247,7 @@ function buildHead(path: string): string {
},
};
meta.push(`<script type="application/ld+json">${JSON.stringify(jsonLd)}</script>`);
} else if (path === "/mcp" || path === "/spec") {
} else if (path === "/cli" || path === "/mcp" || path === "/spec") {
const jsonLd = {
"@context": "https://schema.org",
"@type": "WebPage",
Expand Down Expand Up @@ -252,6 +276,7 @@ function buildHead(path: string): string {

function getContentHtml(path: string): string {
if (path === "/") return homePageHtml();
if (path === "/cli") return cliPageHtml();
if (path === "/mcp") return mcpPageHtml();
if (path === "/spec") return specPageHtml();

Expand Down
15 changes: 11 additions & 4 deletions apps/web/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function Navbar({ sticky = false, maxWidth = false, onSearchClick }: Navb
const location = useLocation();
const isDocsActive = location.pathname.startsWith("/docs");
const isSpecActive = location.pathname === "/spec";
const isCliActive = location.pathname === "/cli";
const isMcpActive = location.pathname === "/mcp";
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

Expand All @@ -34,9 +35,12 @@ export function Navbar({ sticky = false, maxWidth = false, onSearchClick }: Navb
<NavLink to="/docs/" active={isDocsActive}>
Reference
</NavLink>
<NavLink to="/spec" active={isSpecActive}>
Spec
</NavLink>
<div className="flex items-center gap-1">
<NavLink to="/spec" active={isSpecActive}>
Spec
<NavLink to="/cli" active={isCliActive}>
CLI
</NavLink>
<span className="rounded bg-[var(--color-accent)]/15 px-1 py-0.5 text-[8px] font-medium text-[var(--color-accent)]">
new
Expand Down Expand Up @@ -84,9 +88,12 @@ export function Navbar({ sticky = false, maxWidth = false, onSearchClick }: Navb
<NavLink to="/docs/" active={isDocsActive} onClick={() => setMobileMenuOpen(false)}>
Reference
</NavLink>
<NavLink to="/spec" active={isSpecActive} onClick={() => setMobileMenuOpen(false)}>
Spec
</NavLink>
<div className="flex items-center gap-1">
<NavLink to="/spec" active={isSpecActive} onClick={() => setMobileMenuOpen(false)}>
Spec
<NavLink to="/cli" active={isCliActive} onClick={() => setMobileMenuOpen(false)}>
CLI
</NavLink>
<span className="rounded bg-[var(--color-accent)]/15 px-1 py-0.5 text-[8px] font-medium text-[var(--color-accent)]">
new
Expand Down
10 changes: 8 additions & 2 deletions apps/web/src/data/seo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ const staticPages: Record<string, SeoMeta> = {
"/mcp": {
title: "ECMA-376 MCP Server — Search the OOXML Spec with AI | ooxml.dev",
description:
"Search 18,000+ OOXML spec chunks with natural language. Works with Claude Code, Cursor, and any MCP-compatible client.",
"Search OOXML spec prose, inspect XSD schemas, look up OPC package metadata, and query preset shapes from an MCP client.",
type: "website",
},
"/cli": {
title: "OOXML CLI — Search ECMA-376 from Your Terminal | ooxml.dev",
description:
"Search the ECMA-376 specification and inspect OOXML schemas from your terminal or a coding agent.",
type: "website",
},
"/spec": {
Expand Down Expand Up @@ -54,7 +60,7 @@ export function getSeoMeta(path: string): SeoMeta {
}

export function getAllPaths(): string[] {
const paths = ["/", "/mcp", "/spec", "/docs"];
const paths = ["/", "/cli", "/mcp", "/spec", "/docs"];
for (const slug of Object.keys(docs)) {
if (slug === "index") continue;
paths.push(`/docs/${slug}`);
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
import { createBrowserRouter, Outlet, RouterProvider } from "react-router-dom";
import { DocsLayout } from "./pages/docs/Layout";
import { DocsPage } from "./pages/docs/Page";
import { Cli } from "./pages/Cli";
import { Home } from "./pages/Home";
import { Mcp } from "./pages/Mcp";
import { NotFound } from "./pages/NotFound";
Expand All @@ -14,6 +15,7 @@ const router = createBrowserRouter([
element: <Outlet />,
children: [
{ path: "/", element: <Home /> },
{ path: "/cli", element: <Cli /> },
{ path: "/mcp", element: <Mcp /> },
{ path: "/spec", element: <SpecExplorer /> },
{
Expand Down
131 changes: 131 additions & 0 deletions apps/web/src/pages/Cli.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { useState } from "react";
import { Footer } from "../components/Footer";
import { Navbar } from "../components/Navbar";
import { getSeoMeta } from "../data/seo";
import { useDocumentTitle } from "../hooks/useDocumentTitle";

const INSTALL_COMMAND = "npm install --global @ooxml-dev/cli";
const EXAMPLE_COMMANDS = `ooxml login
ooxml search "paragraph spacing"
ooxml element w:p`;
const SKILL_COMMAND = "npx skills add superdoc/ooxml-dev --skill research-ooxml -g -y";

export function Cli() {
useDocumentTitle(getSeoMeta("/cli").title);

return (
<div className="min-h-screen bg-[var(--color-bg-primary)]">
<Navbar maxWidth />

<main className="mx-auto max-w-3xl px-4 py-12 sm:px-6">
<div className="mb-12 text-center">
<div className="mb-4 inline-flex items-center rounded-full bg-[var(--color-accent)]/10 px-3 py-1 text-sm font-medium text-[var(--color-accent)]">
OOXML CLI
</div>
<h1 className="mb-4 text-3xl font-bold">OOXML reference from your terminal</h1>
<p className="mx-auto max-w-xl text-[var(--color-text-secondary)]">
Search the ECMA-376 specification and inspect OOXML schemas without leaving your
terminal.
</p>
</div>

<section className="mb-8 rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-secondary)] p-6">
<h2 className="mb-2 font-semibold">Install</h2>
<p className="mb-4 text-sm text-[var(--color-text-secondary)]">
Node.js 20 or later is required.
</p>
<CommandBlock command={INSTALL_COMMAND} label="install command" />
</section>

<section className="mb-8">
<h2 className="mb-2 font-semibold">Try it</h2>
<p className="mb-4 text-sm text-[var(--color-text-secondary)]">
Sign in, then search the spec or inspect an OOXML element.
</p>
<CommandBlock command={EXAMPLE_COMMANDS} label="example commands" multiline />
</section>

<div className="mb-10 rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-secondary)] p-5 text-sm text-[var(--color-text-secondary)]">
The CLI uses the free, hosted ooxml.dev service. You may need to create an account and
sign in. Sign-in is only used to control usage.
</div>

<section className="mb-10">
<h2 className="mb-2 font-semibold">Use it with a coding agent</h2>
<p className="mb-4 text-sm text-[var(--color-text-secondary)]">
Install the CLI first, then add the <code>research-ooxml</code> skill. It shows coding
agents how to combine spec search with schema evidence.
</p>
<CommandBlock command={SKILL_COMMAND} label="skill install command" />
</section>

<div className="flex flex-wrap gap-4 text-sm">
<a
href="https://www.npmjs.com/package/@ooxml-dev/cli"
target="_blank"
rel="noopener noreferrer"
className="font-medium text-[var(--color-accent)] hover:text-[var(--color-accent-hover)]"
>
View on npm →
</a>
<a
href="https://github.com/superdoc/ooxml-dev/blob/main/apps/cli/README.md"
target="_blank"
rel="noopener noreferrer"
className="font-medium text-[var(--color-accent)] hover:text-[var(--color-accent-hover)]"
>
View all commands →
</a>
</div>
</main>

<Footer />
</div>
);
}

function CommandBlock({
command,
label,
multiline = false,
}: {
command: string;
label: string;
multiline?: boolean;
}) {
const [copyStatus, setCopyStatus] = useState<"idle" | "copied" | "failed">("idle");

const copy = async () => {
try {
if (!navigator.clipboard?.writeText) throw new Error("Clipboard API unavailable");
await navigator.clipboard.writeText(command);
setCopyStatus("copied");
} catch {
setCopyStatus("failed");
}
setTimeout(() => setCopyStatus("idle"), 2000);
};

return (
<div className="flex items-start gap-3 rounded-lg bg-[var(--color-bg-code)] px-4 py-3">
{multiline ? (
<pre className="min-w-0 flex-1 overflow-x-auto whitespace-pre text-sm text-[var(--color-syntax-value)]">
<code>{command}</code>
</pre>
) : (
<code className="min-w-0 flex-1 overflow-x-auto whitespace-nowrap font-mono text-sm text-[var(--color-syntax-value)]">
{command}
</code>
)}
<button
type="button"
onClick={copy}
className="shrink-0 text-xs font-medium text-white hover:text-[var(--color-syntax-value)]"
aria-label={`Copy ${label}`}
aria-live="polite"
>
{copyStatus === "copied" ? "Copied" : copyStatus === "failed" ? "Copy failed" : "Copy"}
</button>
</div>
);
}
6 changes: 3 additions & 3 deletions apps/web/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ export function Home() {
NEW
</span>
<span className="text-[var(--color-text-secondary)]">
OOXML MCP — deterministic schema lookup for elements, attributes, types, enums
Use the OOXML reference from your terminal
</span>
<Link
to="/mcp"
to="/cli"
className="text-[var(--color-accent)] hover:text-[var(--color-accent-hover)] font-medium text-xs"
>
Connect
Get the CLI
</Link>
</div>
</main>
Expand Down
56 changes: 46 additions & 10 deletions apps/web/src/pages/Mcp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Navbar } from "../components/Navbar";
import { getSeoMeta } from "../data/seo";
import { useDocumentTitle } from "../hooks/useDocumentTitle";

const MCP_ENDPOINT = `${import.meta.env.VITE_API_URL}/mcp`;
const MCP_ENDPOINT = `${import.meta.env.VITE_API_URL ?? "https://api.ooxml.dev"}/mcp`;
const CLAUDE_COMMAND = `claude mcp add --transport http ooxml ${MCP_ENDPOINT}`;
const CODEX_COMMAND = `codex mcp add ooxml --url ${MCP_ENDPOINT}`;
const CODEX_TOML = `[mcp_servers.ooxml]
Expand Down Expand Up @@ -65,6 +65,14 @@ const PACKAGE_TOOLS = [
},
];

const PRESET_SHAPE_TOOLS = [
{
name: "ooxml_preset_shape",
description:
"Look up the ordered adjustment-guide names for a DrawingML preset shape from Part 1 Annex D.",
},
];

const EXAMPLE_QUERIES = [
"How do I add borders to a table cell?",
"How does numbering work in WordprocessingML?",
Expand Down Expand Up @@ -99,17 +107,26 @@ export function Mcp() {
<main className="mx-auto max-w-3xl px-4 py-12 sm:px-6">
{/* Header */}
<div className="text-center mb-12">
<div className="inline-flex items-center gap-2 bg-[var(--color-accent)]/10 text-[var(--color-accent)] px-3 py-1 rounded-full text-sm font-medium mb-4">
<span>⚡</span> MCP Server
<div className="mb-4 flex flex-wrap justify-center gap-2">
<div className="inline-flex items-center gap-2 rounded-full bg-[var(--color-accent)]/10 px-3 py-1 text-sm font-medium text-[var(--color-accent)]">
<span>⚡</span> MCP Server
</div>
<div className="inline-flex items-center rounded-full border border-[var(--color-border)] bg-[var(--color-bg-secondary)] px-3 py-1 text-sm font-medium text-[var(--color-text-secondary)]">
Latest MCP (2026-07-28) · Stateless
</div>
</div>
<h1 className="text-3xl font-bold mb-4">OOXML reference for AI assistants</h1>
<p className="text-[var(--color-text-secondary)] max-w-xl mx-auto">
Three tool families: prose search across 18,000+ spec chunks, deterministic schema
lookup over the parsed XSDs, and curated OPC package metadata. Ask in natural language,
or query the structure directly.
Search spec prose, inspect XSD schemas, look up OPC package metadata, and query preset
shape guides from your AI assistant.
</p>
</div>

<div className="mb-8 rounded-xl border border-[var(--color-border)] bg-[var(--color-bg-secondary)] p-5 text-sm text-[var(--color-text-secondary)]">
The hosted service is free. You may need to create an account and sign in when you
connect. Sign-in is only used to control usage.
</div>

{/* Endpoint */}
<div className="bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded-xl p-6 mb-8">
<h2 className="font-semibold mb-3">Endpoint</h2>
Expand Down Expand Up @@ -214,7 +231,7 @@ export function Mcp() {
{activeTab === "other" && (
<>
<p className="text-sm text-[var(--color-text-secondary)] mb-3">
Use the endpoint URL with any MCP-compatible client:
Use the endpoint URL with an MCP client that supports Streamable HTTP:
</p>
<div className="flex items-center gap-2 bg-[var(--color-bg-code)] rounded-lg px-4 py-3">
<code className="text-[var(--color-syntax-value)] font-mono text-sm flex-1">
Expand Down Expand Up @@ -292,6 +309,26 @@ export function Mcp() {
</div>
</div>

{/* Preset shape tools */}
<div className="mb-10">
<h2 className="font-semibold mb-2">Preset shapes</h2>
<p className="text-sm text-[var(--color-text-secondary)] mb-4">
Adjustment guides extracted from ECMA-376 Fourth Edition, Part 1 Annex D.
</p>
<div className="space-y-3">
{PRESET_SHAPE_TOOLS.map((tool) => (
<div key={tool.name} className="border border-[var(--color-border)] rounded-lg p-4">
<div className="flex items-start gap-3">
<code className="bg-[var(--color-bg-secondary)] text-[var(--color-text-primary)] px-2 py-1 rounded text-sm font-mono shrink-0">
{tool.name}
</code>
<p className="text-sm text-[var(--color-text-secondary)]">{tool.description}</p>
</div>
</div>
))}
</div>
</div>

{/* Example Queries */}
<div className="mb-10">
<h2 className="font-semibold mb-4">Example Queries</h2>
Expand Down Expand Up @@ -325,9 +362,8 @@ export function Mcp() {
tools.
</p>
<p className="text-sm text-[var(--color-text-secondary)]">
By connecting to this MCP server, your AI assistant gains prose search across the
ECMA-376 specification, deterministic schema lookup over the parsed XSDs, and curated
OPC package metadata—making it much easier to work with Office Open XML.
Connect this server to search the ECMA-376 specification, inspect XSD schemas, look up
OPC package metadata, and query preset shape guides.
</p>
</div>
</main>
Expand Down