748 lines
38 KiB
HTML
748 lines
38 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Install Verae timestamping</title>
|
||
<meta name="description" content="Verae MCP timestamping: works with or without AI. Embed in web apps, call from local programs, or connect Claude/ChatGPT/Grok. Hash locally; stamp digests only.">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="icon" href="assets/VeraeFullLogo.png">
|
||
<style>
|
||
:root {
|
||
--ink: oklch(0.21 0.020 310);
|
||
--ink-soft: oklch(0.42 0.018 310);
|
||
--ink-mute: oklch(0.60 0.014 310);
|
||
--paper: oklch(0.992 0.004 90);
|
||
--soft: oklch(0.972 0.008 85);
|
||
--hair: oklch(0.90 0.010 80);
|
||
--purple: oklch(0.32 0.110 310);
|
||
--purple-mid: oklch(0.42 0.130 312);
|
||
--purple-wash: oklch(0.96 0.020 312);
|
||
--gold: oklch(0.80 0.090 85);
|
||
--dark: oklch(0.19 0.022 312);
|
||
--ease: cubic-bezier(0.16, 1, 0.3, 1);
|
||
--ok: oklch(0.55 0.12 145);
|
||
}
|
||
* { box-sizing: border-box; }
|
||
html { scroll-behavior: smooth; }
|
||
body {
|
||
margin: 0;
|
||
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
||
color: var(--ink);
|
||
background: var(--paper);
|
||
line-height: 1.55;
|
||
}
|
||
a { color: var(--purple); }
|
||
header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 28px 28px 0;
|
||
}
|
||
.brand img { height: 46px; width: auto; display: block; }
|
||
nav { display: flex; gap: 22px; font-size: 14px; font-weight: 600; flex-wrap: wrap; }
|
||
nav a { color: var(--ink-soft); text-decoration: none; }
|
||
nav a:hover, nav a.active { color: var(--purple); }
|
||
.hero {
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 64px 28px 24px;
|
||
}
|
||
.eyebrow {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.14em;
|
||
text-transform: uppercase;
|
||
color: var(--purple-mid);
|
||
}
|
||
h1 {
|
||
font-family: Fraunces, Georgia, serif;
|
||
font-weight: 560;
|
||
font-size: clamp(40px, 6vw, 68px);
|
||
line-height: 1.05;
|
||
letter-spacing: -0.03em;
|
||
margin: 12px 0 18px;
|
||
max-width: 18ch;
|
||
}
|
||
.lede { max-width: 42rem; font-size: 18px; color: var(--ink-soft); margin: 0; }
|
||
.wrap { max-width: 1100px; margin: 0 auto; padding: 12px 28px 80px; }
|
||
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 36px; }
|
||
.card {
|
||
background: white;
|
||
border: 1px solid var(--hair);
|
||
border-radius: 16px;
|
||
padding: 22px 22px 18px;
|
||
}
|
||
.card h2 { font-size: 18px; margin: 0 0 8px; }
|
||
.card p { margin: 0; color: var(--ink-soft); font-size: 15px; }
|
||
.step { font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--purple-mid); }
|
||
h2.section {
|
||
font-family: Fraunces, Georgia, serif;
|
||
font-size: 34px;
|
||
font-weight: 560;
|
||
margin: 56px 0 8px;
|
||
}
|
||
.section-note { color: var(--ink-soft); max-width: 44rem; margin-top: 0; }
|
||
pre {
|
||
background: var(--dark);
|
||
color: oklch(0.95 0.01 90);
|
||
border-radius: 14px;
|
||
padding: 18px 20px;
|
||
overflow: auto;
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||
font-size: 13.5px;
|
||
line-height: 1.5;
|
||
}
|
||
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.92em; }
|
||
.path { background: var(--purple-wash); padding: 2px 6px; border-radius: 6px; }
|
||
ol.steps { padding-left: 1.2rem; max-width: 44rem; }
|
||
ol.steps li { margin: 0.55rem 0; }
|
||
.gold { height: 3px; width: 72px; background: var(--gold); border: 0; margin: 28px 0; }
|
||
.detect {
|
||
background: white;
|
||
border: 1px solid var(--hair);
|
||
border-radius: 18px;
|
||
padding: 22px;
|
||
margin-top: 22px;
|
||
}
|
||
.detect .status { font-size: 15px; color: var(--ink-soft); margin: 0 0 14px; }
|
||
.detect .status strong { color: var(--ink); }
|
||
.btn-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
background: var(--purple);
|
||
color: white;
|
||
text-decoration: none;
|
||
border: 0;
|
||
border-radius: 999px;
|
||
padding: 12px 18px;
|
||
font: inherit;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
}
|
||
.btn.secondary {
|
||
background: white;
|
||
color: var(--purple);
|
||
border: 1px solid var(--hair);
|
||
}
|
||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||
.meta { font-size: 13px; color: var(--ink-mute); margin-top: 12px; }
|
||
.ok { color: var(--ok); font-weight: 600; }
|
||
table.artifacts {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 16px;
|
||
font-size: 14px;
|
||
}
|
||
table.artifacts th, table.artifacts td {
|
||
text-align: left;
|
||
padding: 10px 8px;
|
||
border-bottom: 1px solid var(--hair);
|
||
}
|
||
table.artifacts th { color: var(--ink-mute); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; }
|
||
footer {
|
||
border-top: 1px solid var(--hair);
|
||
padding: 28px;
|
||
color: var(--ink-mute);
|
||
font-size: 14px;
|
||
}
|
||
footer .wrap { padding: 0; display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
|
||
|
||
.connectors { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 18px; margin-top: 28px; }
|
||
.connector {
|
||
background: white;
|
||
border: 1px solid var(--hair);
|
||
border-radius: 16px;
|
||
padding: 22px 22px 18px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
.connector h3 { font-size: 18px; margin: 0; }
|
||
.connector .tag {
|
||
display: inline-block;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
color: var(--purple-mid);
|
||
background: var(--purple-wash);
|
||
padding: 3px 8px;
|
||
border-radius: 999px;
|
||
width: fit-content;
|
||
}
|
||
.connector .tag.warn { color: oklch(0.45 0.10 70); background: oklch(0.96 0.04 85); }
|
||
.connector ol { margin: 6px 0 0; padding-left: 1.15rem; color: var(--ink-soft); font-size: 14.5px; }
|
||
.connector ol li { margin: 0.4rem 0; }
|
||
.connector .urlbox {
|
||
margin-top: 8px;
|
||
background: var(--soft);
|
||
border-radius: 10px;
|
||
padding: 10px 12px;
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||
font-size: 12.5px;
|
||
word-break: break-all;
|
||
}
|
||
.connector .footnote { font-size: 13px; color: var(--ink-mute); margin: 6px 0 0; }
|
||
|
||
@media (max-width: 800px) {
|
||
.grid { grid-template-columns: 1fr; }
|
||
header { padding-top: 18px; }
|
||
nav { gap: 14px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<a class="brand" href="https://www.verae.com/"><img src="assets/VeraeFullLogo.png" alt="Verae"></a>
|
||
<nav>
|
||
<a href="#hasher">Hasher</a>
|
||
<a href="#connect">Connect</a>
|
||
<a href="#register">Register</a>
|
||
<a href="ways.html">Ways to use</a>
|
||
<a href="developers.html">Developers</a>
|
||
<a href="sample.html">Try a stamp</a>
|
||
</nav>
|
||
</header>
|
||
<main>
|
||
<section class="hero">
|
||
<p class="eyebrow">Model Context Protocol · not AI-only</p>
|
||
<h1>Hash locally. Stamp remotely.</h1>
|
||
<p class="lede">This MCP is a normal remote API. It runs <strong>outside any AI</strong>: embed it in a web page, call it from a local program or service, or optionally wire it into Claude / ChatGPT / Grok. Download a small <code>verae-sha256</code> binary (or use WASM in the browser). Digests go to the server; file bytes stay on your machine.</p>
|
||
<hr class="gold">
|
||
<p class="lede" style="margin-top:22px">See <a href="#ways"><strong>Ways to use</strong></a> for AI MCP, browser, macOS direct download / TestFlight / Windows GUI / explorer, CLI, JS, and curl. New: <a href="windows.html"><strong>Windows GUI + CLI</strong></a> (amd64 EXEs with screenshots & checksums), <a href="app.html"><strong>full browser app</strong></a> (Register + Verify, auth defaults, green/red status), and <a href="register.html"><strong>multi-document registration</strong></a> — drop many files, check global timestamps, export CSV / JSON / text / official PDF certificates with QR lookup.</p>
|
||
</section>
|
||
<div class="wrap">
|
||
<section id="ways">
|
||
<h2 class="section">Ways to use Verae timestamps</h2>
|
||
<p class="section-note">Same public MCP for every path. Hash on the device; only digests are sent. Start with <strong>AI MCP</strong>, then pick a local or browser client. Full detail — including <strong>macOS direct download</strong>, explorer, and TestFlight — on the <a href="ways.html">Ways to use</a> hub.</p>
|
||
<ol class="steps" style="margin-top:14px">
|
||
<li><strong>AI MCP</strong> — Claude.ai, ChatGPT, Grok, Claude Desktop, or Cursor → <code class="mcp-endpoint-label">https://mcp.verae.ai/mcp</code> (<a href="#connectors">Connectors</a>).</li>
|
||
<li><strong>Browser</strong> — <a href="app.html">Browser app</a>, <a href="register.html">Register docs</a>, <a href="sample.html">Sample</a>.</li>
|
||
<li><strong>UI</strong> — <a href="macos.html">macOS notarized zip</a> (no TestFlight) · <a href="ways.html#testflight">TestFlight</a> · <a href="windows.html">Windows GUI</a> · <a href="explorer.html">Explorer</a>.</li>
|
||
<li><strong>CLI</strong> — <code>uvx verae-mcp-timestamping-client</code> or Windows <code>verae-timestamp.exe</code> (<a href="#apps">without AI</a> · <a href="windows.html#cli">Windows CLI</a>).</li>
|
||
<li><strong>JavaScript</strong> — browser <code>fetch</code> to <code>/mcp</code> (<a href="#apps">embed snippet</a>).</li>
|
||
<li><strong>curl</strong> — raw JSON-RPC (<a href="#apps">curl examples</a>).</li>
|
||
<li><strong>Also</strong> — local <a href="#hasher">hasher</a> / <a href="hasher.html">WASM hasher</a>, optional <a href="#register">machine login</a>.</li>
|
||
</ol>
|
||
<div class="btn-row" style="margin-top:16px">
|
||
<a class="btn" href="ways.html">Ways to use hub</a>
|
||
<a class="btn secondary" href="macos.html">macOS download</a>
|
||
<a class="btn secondary" href="explorer.html">Explorer</a>
|
||
<a class="btn secondary" href="ways.html#testflight">Request Mac TestFlight</a>
|
||
<a class="btn secondary" href="windows.html">Windows downloads</a>
|
||
<a class="btn secondary" href="#connectors">AI connectors</a>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="hasher">
|
||
<h2 class="section">Get the local hasher</h2>
|
||
<p class="section-note">No repo clone. This page detects your platform, fetches the matching published binary from <code>/hasher/</code>, and falls back to WASM/JS when a native build is not available.</p>
|
||
<div class="detect" id="hasher-panel">
|
||
<p class="status" id="hasher-status">Detecting platform…</p>
|
||
<div class="btn-row" id="hasher-actions"></div>
|
||
<p class="meta" id="hasher-meta"></p>
|
||
<pre id="hasher-snippet" hidden></pre>
|
||
</div>
|
||
<h3 style="margin-top:36px;font-size:18px;">All published binaries</h3>
|
||
<table class="artifacts" id="artifact-table">
|
||
<thead><tr><th>Target</th><th>File</th><th>Size</th><th>SHA-256</th></tr></thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
<p class="meta">Manifest: <a id="manifest-link" href="/hasher/latest.json">/hasher/latest.json</a> · Source rebuild: <code>./scripts/publish-hasher.sh</code> in the client repo.</p>
|
||
</section>
|
||
|
||
<section id="windows">
|
||
<h2 class="section">Windows desktop</h2>
|
||
<p class="section-note">Frozen amd64 EXEs — GUI (PySide6), CLI, and optional local hasher. SmartScreen will warn (not code-signed); verify SHA-256 before running. Digests only; bytes never upload.</p>
|
||
<div class="btn-row" style="margin-top:16px">
|
||
<a class="btn" href="windows.html">Windows download page</a>
|
||
<a class="btn secondary" href="/downloads/windows/verae-timestamp-gui.exe">GUI EXE</a>
|
||
<a class="btn secondary" href="/downloads/windows/verae-timestamp.exe">CLI EXE</a>
|
||
<a class="btn secondary" href="/downloads/windows/SHA256SUMS">SHA256SUMS</a>
|
||
</div>
|
||
<p class="meta">Stable URLs: <code>/downloads/windows/</code> · instructions, screenshots, and real register/verify sample output on the <a href="windows.html">Windows page</a>.</p>
|
||
</section>
|
||
|
||
<section id="connect">
|
||
<h2 class="section">Connect an MCP client</h2>
|
||
<p class="section-note">Point any MCP-capable host at this public URL — an AI assistant <em>or</em> your own app. You do not need to run a local server. For embedding in web pages and local programs (no AI), see <a href="#apps">Use without AI</a>. Browser assistants (Claude.ai, ChatGPT, Grok) use Connectors below.</p>
|
||
<pre id="mcp-config">{
|
||
"mcpServers": {
|
||
"verae-timestamping": {
|
||
"url": "https://HOST/mcp"
|
||
}
|
||
}
|
||
}</pre>
|
||
<p class="section-note">Public endpoints: <code id="mcp-url-primary">https://HOST/mcp</code>
|
||
· also <a href="https://mcp.veraetime.net/mcp">mcp.veraetime.net/mcp</a>
|
||
· <a href="https://mcp.georgelambert.org/mcp">mcp.georgelambert.org/mcp</a></p>
|
||
|
||
<h2 class="section">Hash a file, then stamp</h2>
|
||
<pre id="hash-flow"># after installing the binary as verae-sha256 on your PATH:
|
||
DIGEST=$(verae-sha256 ./document.pdf)
|
||
echo "$DIGEST"
|
||
# then ask your MCP client to call tool "stamp" with { "sha256": "$DIGEST", "scope": "global" }
|
||
# or try the browser sample:
|
||
# open sample.html and paste the digest</pre>
|
||
</section>
|
||
|
||
<section id="connectors">
|
||
<h2 class="section">Connectors by host</h2>
|
||
<p class="section-note">MCP endpoint for this page’s host is filled in automatically below. First connection may open a browser login; the assistant keeps the token in its OAuth store — never paste tokens into chat or <code>mcp.json</code>.</p>
|
||
<div class="connectors">
|
||
<article class="connector">
|
||
<span class="tag">Browser</span>
|
||
<h3>Claude.ai</h3>
|
||
<ol>
|
||
<li>Open <strong>Customize → Connectors</strong> (Team/Enterprise Owners: Organization settings → Connectors).</li>
|
||
<li>Click <strong>Add custom connector</strong> (or Add → Custom → Web).</li>
|
||
<li>Paste the MCP server URL. Leave Advanced OAuth blank unless you registered a fixed client.</li>
|
||
<li>Click <strong>Add</strong>, then <strong>Connect</strong> and finish the Verae browser login.</li>
|
||
</ol>
|
||
<div class="urlbox mcp-endpoint">https://mcp.verae.ai/mcp</div>
|
||
<p class="footnote">Docs: <a href="https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp">Claude Help — remote MCP</a></p>
|
||
</article>
|
||
<article class="connector">
|
||
<span class="tag">Browser</span>
|
||
<h3>ChatGPT</h3>
|
||
<ol>
|
||
<li>Avatar → <strong>Settings → Connectors</strong>.</li>
|
||
<li>Under Advanced, enable <strong>Developer Mode</strong> (Pro / Team / Enterprise / Edu).</li>
|
||
<li><strong>Add custom connector</strong>: name <code>Verae timestamping</code>, URL below, Authentication <strong>OAuth</strong>.</li>
|
||
<li>Create, complete the Verae login, then enable the connector in a chat and confirm tool calls.</li>
|
||
</ol>
|
||
<div class="urlbox mcp-endpoint">https://mcp.verae.ai/mcp</div>
|
||
<p class="footnote">Needs a public Streamable HTTP <code>/mcp</code> URL. ChatGPT OAuth redirect: <code>https://chatgpt.com/connector_platform_oauth_redirect</code>.</p>
|
||
</article>
|
||
<article class="connector">
|
||
<span class="tag">Browser</span>
|
||
<h3>Grok (grok.com)</h3>
|
||
<ol>
|
||
<li>Go to <a href="https://grok.com/connectors">grok.com/connectors</a>.</li>
|
||
<li>Click <strong>New Connector</strong> → <strong>Custom</strong>.</li>
|
||
<li>Enter the MCP server URL and complete any OAuth/login prompts.</li>
|
||
<li>Business/Enterprise: a team admin provisions it first in <a href="https://console.x.ai">console.x.ai</a> (Grok Business → Connectors).</li>
|
||
</ol>
|
||
<div class="urlbox mcp-endpoint">https://mcp.verae.ai/mcp</div>
|
||
<p class="footnote">Must be public HTTPS. Docs: <a href="https://docs.x.ai/grok/connectors">xAI — Connectors</a></p>
|
||
</article>
|
||
<article class="connector">
|
||
<span class="tag">Desktop</span>
|
||
<h3>Claude Desktop</h3>
|
||
<ol>
|
||
<li>Edit the config file for your OS (paths below).</li>
|
||
<li>Add the <code>mcpServers</code> JSON from <a href="#connect">Connect</a> (remote <code>url</code>).</li>
|
||
<li>Restart Claude Desktop; approve Verae browser login on first use.</li>
|
||
</ol>
|
||
<div class="urlbox">macOS: ~/Library/Application Support/Claude/claude_desktop_config.json<br>Windows: %APPDATA%\Claude\claude_desktop_config.json</div>
|
||
</article>
|
||
<article class="connector">
|
||
<span class="tag">IDE</span>
|
||
<h3>Cursor</h3>
|
||
<ol>
|
||
<li>Open user <span class="path">~/.cursor/mcp.json</span> or project <span class="path">.cursor/mcp.json</span>.</li>
|
||
<li>Add the remote <code>url</code> entry from <a href="#connect">Connect</a>.</li>
|
||
<li>Reload MCP in Cursor; complete browser login if prompted.</li>
|
||
</ol>
|
||
<div class="urlbox mcp-endpoint">https://mcp.verae.ai/mcp</div>
|
||
</article>
|
||
<article class="connector">
|
||
<span class="tag">Local</span>
|
||
<h3>uvx on this machine</h3>
|
||
<ol>
|
||
<li>Prefer the remote public URL when you can.</li>
|
||
<li>For a local process: <code>command: uvx</code>, <code>args: ["verae-mcp-timestamping-server"]</code>.</li>
|
||
<li>Optional account: <code>uvx verae-mcp-timestamping-server setup</code> → <span class="path">~/.verae/auth.properties</span> (mode 600).</li>
|
||
</ol>
|
||
<p class="footnote">Local stdio works for Claude Desktop / Cursor on the same computer — not for ChatGPT or grok.com (those need the public URL).</p>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
|
||
|
||
<section id="apps">
|
||
<h2 class="section">Use without AI — web apps & local processes</h2>
|
||
<p class="section-note"><strong>No assistant required.</strong> The same public MCP endpoint is a plain Streamable HTTP JSON-RPC service. Embed it in a web application (browser <code>fetch</code>), call it from a desktop/CLI process, a backend worker, or a mobile app. AI connectors are optional. Endpoint: <code class="mcp-endpoint-label">https://mcp.verae.ai/mcp</code>. Hash on the device; send only the hex digest. Never upload file bytes.</p>
|
||
<div class="grid" style="margin-top:18px;margin-bottom:8px">
|
||
<article class="card">
|
||
<h2>Web page / SPA</h2>
|
||
<p>Hash with WASM in the browser, then <code>fetch</code> <code>/mcp</code> (CORS <code>*</code>). Live: <a href="app.html">app.html</a>, <a href="register.html">register.html</a>, and <a href="sample.html">sample.html</a>.</p>
|
||
</article>
|
||
<article class="card">
|
||
<h2>Local program</h2>
|
||
<p>CLI, daemon, or any language with HTTP: official Python client, curl JSON-RPC, or an MCP SDK. Runs on your machine with no chat UI.</p>
|
||
</article>
|
||
<article class="card">
|
||
<h2>Optional: AI hosts</h2>
|
||
<p>Same URL works in Claude.ai, ChatGPT, Grok, Claude Desktop, and Cursor when you want an assistant to call <code>stamp</code> / <code>verify</code> for you.</p>
|
||
</article>
|
||
</div>
|
||
|
||
<h3 style="margin-top:28px;font-size:20px;">Local process (Python CLI / scripts)</h3>
|
||
<p class="section-note">Official client for scripts and desktop tools — no AI involved. Hash locally, stamp via MCP, write receipts under <span class="path">~/.verae/receipts/</span>.</p>
|
||
<pre id="cli-snippet"># install hasher (or let the client auto-fetch)
|
||
DIGEST=$(verae-sha256 ./document.pdf)
|
||
|
||
# stamp against the public server (digest only — file stays local)
|
||
uvx verae-mcp-timestamping-client \
|
||
--url https://mcp.verae.ai/mcp \
|
||
--scope global \
|
||
./document.pdf
|
||
|
||
# hash only (no network)
|
||
uvx verae-mcp-timestamping-client --hash-only ./document.pdf</pre>
|
||
<p class="meta">Optional machine OAuth: set <code>VERAE_OAUTH_CLIENT_ID</code> / <code>VERAE_OAUTH_CLIENT_SECRET</code> (and usually <code>VERAE_OAUTH_ISSUER</code>). Tokens land under <span class="path">~/.verae/oauth/</span>, not in chat.</p>
|
||
|
||
<h3 style="margin-top:36px;font-size:20px;">Any language (MCP SDK or raw JSON-RPC)</h3>
|
||
<p class="section-note">Transport is <strong>Streamable HTTP</strong> at <code>/mcp</code>. Session id comes back in the <code>Mcp-Session-Id</code> response header — send it on later requests. Tools: <code>stamp</code> (create), <code>hash_report</code> (lookup by digest), <code>verify</code> (check one receipt id), plus account helpers.</p>
|
||
|
||
<h3 style="margin-top:36px;font-size:20px;">Lookup receipts for an existing hash</h3>
|
||
<p class="section-note"><code>hash_report</code> returns the JSON receipt history for one SHA-256 (Carfax-style). Optional <code>want_scope=global</code> asks api.veraetime.net as the central source of truth. <code>verify</code> checks a single <code>receipt_id</code> from a prior stamp.</p>
|
||
<pre id="lookup-snippet"># after initialize + notifications/initialized (same session as above):
|
||
curl -sS -X POST https://mcp.verae.ai/mcp \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'Accept: application/json, text/event-stream' \
|
||
-H 'Mcp-Session-Id: SESSION' \
|
||
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"hash_report","arguments":{"sha256":"DIGEST_HEX","want_scope":"global"}}}'
|
||
|
||
# verify one receipt id from a previous stamp:
|
||
curl -sS -X POST https://mcp.verae.ai/mcp \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'Accept: application/json, text/event-stream' \
|
||
-H 'Mcp-Session-Id: SESSION' \
|
||
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"verify","arguments":{"receipt_id":"RECEIPT_ID","want_scope":"global"}}}'</pre>
|
||
<p class="meta">In the browser sample, pick tool <code>hash_report</code> or <code>verify</code>. In JS, use the same <code>tools/call</code> shape with <code>name: "hash_report"</code> and <code>{ sha256, want_scope }</code>.</p>
|
||
<pre id="rpc-snippet"># 1) initialize
|
||
curl -sS -D - -X POST https://mcp.verae.ai/mcp \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'Accept: application/json, text/event-stream' \
|
||
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"my-app","version":"1.0.0"}}}'
|
||
|
||
# 2) notifications/initialized (include Mcp-Session-Id from step 1)
|
||
# 3) tools/call stamp (a write: needs your Verae API key and is charged to your account)
|
||
curl -sS -X POST https://mcp.verae.ai/mcp \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'Accept: application/json, text/event-stream' \
|
||
-H 'Authorization: Bearer YOUR_VERAE_API_KEY' \
|
||
-H 'Mcp-Session-Id: SESSION' \
|
||
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"stamp","arguments":{"sha256":"DIGEST_HEX","scope":"global"}}}'</pre>
|
||
<p class="meta"><strong>Stamping needs your own Verae API key</strong> (<code>Authorization: Bearer xVerae-…</code>): get one with <code>verae login</code>, in the <a href="https://zap.verae.ai/portal/">Verae portal</a>, or with <strong>Sign in with Verae</strong> on the <a href="register.html">register page</a>. Each new document is charged to that account; a document you already registered is free. <code>verify</code> and <code>hash_report</code> stay anonymous (rate limited).</p>
|
||
<p class="meta">Python SDK shape (same as the official client): <code>Client(url)</code> then <code>await client.call_tool("stamp", {"sha256": digest, "scope": "global"})</code>.</p>
|
||
|
||
<h3 style="margin-top:36px;font-size:20px;">Embed in a web page (browser JavaScript)</h3>
|
||
<p class="section-note">Drop this into any web app — not an AI product. Same-origin pages on this host (and CORS <code>*</code> on <code>/mcp</code>) can <code>fetch</code> the endpoint. Live demos: <a href="app.html">full browser app</a>, <a href="register.html">multi-doc registration</a>, <a href="sample.html">raw MCP sample</a>. Hash in-page with WASM (or Web Crypto), then stamp the digest only.</p>
|
||
<pre id="js-snippet">const url = "https://mcp.verae.ai/mcp";
|
||
|
||
async function post(body, session) {
|
||
const headers = {
|
||
"Content-Type": "application/json",
|
||
"Accept": "application/json, text/event-stream"
|
||
};
|
||
if (session) headers["Mcp-Session-Id"] = session;
|
||
headers["Authorization"] = "Bearer " + VERAE_API_KEY; // stamp needs your key; omit it for verify / hash_report
|
||
const res = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
|
||
const text = await res.text();
|
||
const sessionId = res.headers.get("mcp-session-id") || session;
|
||
let parsed; try { parsed = JSON.parse(text); } catch { parsed = text; }
|
||
if (!res.ok) throw new Error(res.status + " " + text);
|
||
return { parsed, sessionId };
|
||
}
|
||
|
||
const init = await post({
|
||
jsonrpc: "2.0", id: 1, method: "initialize",
|
||
params: {
|
||
protocolVersion: "2025-03-26",
|
||
capabilities: {},
|
||
clientInfo: { name: "browser-app", version: "0.1.0" }
|
||
}
|
||
});
|
||
await post({ jsonrpc: "2.0", method: "notifications/initialized" }, init.sessionId);
|
||
const call = await post({
|
||
jsonrpc: "2.0", id: 2, method: "tools/call",
|
||
params: { name: "stamp", arguments: { sha256: DIGEST_HEX, scope: "global" } }
|
||
}, init.sessionId);
|
||
console.log(call.parsed);</pre>
|
||
<p class="meta">For hashing in the browser, use the WASM module from <a href="#hasher">Get the local hasher</a> (<code>sha256_hex_js</code> / <code>Sha256Stream</code>). Do not <code>POST</code> file contents to <code>/mcp</code>.</p>
|
||
</section>
|
||
|
||
<section id="register">
|
||
<h2 class="section">Optional: register on your machine</h2>
|
||
<p class="section-note">For a local <code>uvx</code> server (or global stamps that need a VeraeTime account), register once. You still do not clone a repository.</p>
|
||
<pre>uvx verae-mcp-timestamping-server setup</pre>
|
||
<ol class="steps">
|
||
<li>Choose <strong>new</strong> to create a VeraeTime account, or <strong>existing</strong> to log in.</li>
|
||
<li>It writes <span class="path">~/.verae/auth.properties</span> with mode <code>600</code>. It does not print the token.</li>
|
||
<li>Check later with <code>uvx verae-mcp-timestamping-server session</code>.</li>
|
||
</ol>
|
||
</section>
|
||
|
||
<section id="token">
|
||
<h2 class="section">Where credentials live</h2>
|
||
<div class="grid">
|
||
<article class="card">
|
||
<p class="step">Your machine</p>
|
||
<h2>~/.verae/auth.properties</h2>
|
||
<p>Mode 600. Holds the JWT for local/global stamps. Do not commit this file.</p>
|
||
</article>
|
||
<article class="card">
|
||
<p class="step">Your assistant</p>
|
||
<h2>OAuth store</h2>
|
||
<p>Browser login on the public server keeps tokens in the assistant’s OAuth store, not in mcp.json.</p>
|
||
</article>
|
||
<article class="card">
|
||
<p class="step">Never</p>
|
||
<h2>Chat or mcp.json</h2>
|
||
<p>Do not paste tokens into a conversation or client config.</p>
|
||
</article>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</main>
|
||
<footer>
|
||
<div class="wrap">
|
||
<span>Verae timestamping · <a href="https://www.verae.com/">verae.com</a></span>
|
||
<span><a href="ways.html">Ways to use</a> · <a href="explorer.html">Explorer</a> · <a href="macos.html">macOS</a> · <a href="ways.html#testflight">TestFlight</a> · <a href="windows.html">Windows</a> · <a href="app.html">Browser app</a> · <a href="register.html">Register docs</a> · <a href="sample.html">Sample</a> · <a href="/hasher/latest.json">hasher latest</a> · <a href="https://api.veraetime.net/docs/swagger/">API docs</a></span>
|
||
</div>
|
||
</footer>
|
||
<script>
|
||
(function () {
|
||
const HASHER_BASE = (location.origin || "https://mcp.georgelambert.org") + "/hasher";
|
||
// On veraetime host, hasher artifacts still live on georgelambert CDN-style path.
|
||
const ARTIFACT_BASES = [
|
||
HASHER_BASE,
|
||
"https://mcp.georgelambert.org/hasher"
|
||
];
|
||
const host = location.hostname || "mcp.georgelambert.org";
|
||
const mcpUrl = "https://" + host + "/mcp";
|
||
document.getElementById("mcp-config").textContent =
|
||
`{
|
||
"mcpServers": {
|
||
"verae-timestamping": {
|
||
"url": "${mcpUrl}"
|
||
}
|
||
}
|
||
}`;
|
||
document.getElementById("mcp-url-primary").textContent = mcpUrl;
|
||
document.querySelectorAll(".mcp-endpoint").forEach(function (el) { el.textContent = mcpUrl; });
|
||
document.querySelectorAll(".mcp-endpoint-label").forEach(function (el) { el.textContent = mcpUrl; });
|
||
(function () {
|
||
var cli = document.getElementById("cli-snippet");
|
||
if (cli) cli.textContent = cli.textContent.replaceAll("https://mcp.verae.ai/mcp", mcpUrl);
|
||
var rpc = document.getElementById("rpc-snippet");
|
||
if (rpc) rpc.textContent = rpc.textContent.replaceAll("https://mcp.verae.ai/mcp", mcpUrl);
|
||
var js = document.getElementById("js-snippet");
|
||
if (js) js.textContent = js.textContent.replaceAll("https://mcp.verae.ai/mcp", mcpUrl);
|
||
var lu = document.getElementById("lookup-snippet");
|
||
if (lu) lu.textContent = lu.textContent.replaceAll("https://mcp.verae.ai/mcp", mcpUrl);
|
||
})();
|
||
|
||
function detectPlatform() {
|
||
const uaData = navigator.userAgentData;
|
||
let os = null, arch = null;
|
||
if (uaData && uaData.platform) {
|
||
const p = uaData.platform.toLowerCase();
|
||
if (p.includes("mac")) os = "darwin";
|
||
else if (p.includes("win")) os = "windows";
|
||
else if (p.includes("linux") || p.includes("chrome os")) os = "linux";
|
||
else if (p.includes("android")) os = "android";
|
||
else if (p.includes("ios") || p.includes("iphone") || p.includes("ipad")) os = "ios";
|
||
}
|
||
const ua = navigator.userAgent || "";
|
||
const plat = (navigator.platform || "").toLowerCase();
|
||
if (!os) {
|
||
if (/android/i.test(ua)) os = "android";
|
||
else if (/iphone|ipad|ipod/i.test(ua)) os = "ios";
|
||
else if (/mac/i.test(plat) || /mac os/i.test(ua)) os = "darwin";
|
||
else if (/win/i.test(plat) || /windows/i.test(ua)) os = "windows";
|
||
else if (/linux/i.test(plat) || /linux/i.test(ua)) os = "linux";
|
||
}
|
||
// arch hints
|
||
if (uaData && Array.isArray(uaData.architecture)) {
|
||
// not standard; ignore
|
||
}
|
||
if (/aarch64|arm64|windows arm/i.test(ua) || /arm64/i.test(plat)) arch = "aarch64";
|
||
else if (/x86_64|win64|wow64|amd64/i.test(ua) || /x86_64|macintel/i.test(plat)) arch = "x86_64";
|
||
// Apple Silicon often reports MacIntel in platform historically; prefer arm on modern Macs when possible
|
||
if (os === "darwin" && !arch) {
|
||
// Safari on Apple Silicon still often says MacIntel; default arm64 for darwin when unknown in 2026
|
||
arch = "aarch64";
|
||
}
|
||
if (os === "windows" && !arch) arch = "x86_64";
|
||
if (os === "linux" && !arch) arch = "x86_64";
|
||
|
||
// Optional URL override: ?libc=musl|gnu on Linux
|
||
const params = new URLSearchParams(location.search);
|
||
const libcPref = (params.get("libc") || "").toLowerCase();
|
||
const wantMusl = libcPref === "musl";
|
||
|
||
let key = null;
|
||
let label = "unknown";
|
||
let kind = "wasm";
|
||
if (os === "darwin" && arch === "aarch64") { key = "aarch64-apple-darwin"; label = "macOS Apple Silicon"; kind = "native"; }
|
||
else if (os === "darwin" && arch === "x86_64") { key = "x86_64-apple-darwin"; label = "macOS Intel"; kind = "native"; }
|
||
else if (os === "linux" && arch === "x86_64") {
|
||
key = wantMusl ? "x86_64-unknown-linux-musl" : "x86_64-unknown-linux-gnu";
|
||
label = wantMusl ? "Linux x86_64 (musl/static)" : "Linux x86_64 (glibc)";
|
||
kind = "native";
|
||
}
|
||
else if (os === "linux" && arch === "aarch64") {
|
||
key = wantMusl ? "aarch64-unknown-linux-musl" : "aarch64-unknown-linux-gnu";
|
||
label = wantMusl ? "Linux aarch64 (musl/static)" : "Linux aarch64 (glibc)";
|
||
kind = "native";
|
||
}
|
||
else if (os === "windows" && arch === "x86_64") { key = "x86_64-pc-windows-gnu"; label = "Windows x86_64"; kind = "native"; }
|
||
else if (os === "windows" && arch === "aarch64") { key = "aarch64-pc-windows-gnullvm"; label = "Windows ARM64"; kind = "native"; }
|
||
else if (os === "android" || os === "ios") { label = os === "ios" ? "iOS / iPadOS" : "Android"; kind = "wasm"; }
|
||
else { label = (os || "browser") + (arch ? (" / " + arch) : ""); kind = "wasm"; }
|
||
|
||
return { os, arch, key, label, kind };
|
||
}
|
||
|
||
function fmtSize(n) {
|
||
if (n < 1024) return n + " B";
|
||
if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB";
|
||
return (n / (1024 * 1024)).toFixed(2) + " MB";
|
||
}
|
||
|
||
async function fetchJson(urls) {
|
||
let lastErr;
|
||
for (const url of urls) {
|
||
try {
|
||
const res = await fetch(url, { cache: "no-cache" });
|
||
if (!res.ok) throw new Error(res.status + " " + url);
|
||
return { data: await res.json(), url };
|
||
} catch (e) { lastErr = e; }
|
||
}
|
||
throw lastErr || new Error("fetch failed");
|
||
}
|
||
|
||
function installSnippet(fileUrl, key, isWindows) {
|
||
if (isWindows) {
|
||
return `# PowerShell
|
||
Invoke-WebRequest -Uri "${fileUrl}" -OutFile verae-sha256.exe
|
||
Get-FileHash .\\verae-sha256.exe -Algorithm SHA256
|
||
.\\verae-sha256.exe .\\document.pdf`;
|
||
}
|
||
const name = "verae-sha256";
|
||
return `# curl
|
||
curl -fsSL -o ${name} "${fileUrl}"
|
||
chmod +x ${name}
|
||
shasum -a 256 ${name} # or: sha256sum ${name}
|
||
./${name} ./document.pdf
|
||
|
||
# wget
|
||
wget -O ${name} "${fileUrl}"
|
||
chmod +x ${name}
|
||
./${name} ./document.pdf`;
|
||
}
|
||
|
||
async function run() {
|
||
const status = document.getElementById("hasher-status");
|
||
const actions = document.getElementById("hasher-actions");
|
||
const meta = document.getElementById("hasher-meta");
|
||
const snippet = document.getElementById("hasher-snippet");
|
||
const det = detectPlatform();
|
||
|
||
let latest, manifest, baseUsed;
|
||
try {
|
||
const latestRes = await fetchJson(ARTIFACT_BASES.map(b => b + "/latest.json"));
|
||
latest = latestRes.data;
|
||
// Prefer manifest URL from latest, else construct
|
||
const manifestUrls = [];
|
||
if (latest.manifest) manifestUrls.push(latest.manifest);
|
||
for (const b of ARTIFACT_BASES) {
|
||
manifestUrls.push(b + "/" + latest.version + "/manifest.json");
|
||
}
|
||
const manRes = await fetchJson([...new Set(manifestUrls)]);
|
||
manifest = manRes.data;
|
||
baseUsed = (manifest.base_url || "").replace(/\/$/, "");
|
||
} catch (err) {
|
||
status.innerHTML = "Could not load hasher manifest: <code>" + String(err) + "</code>";
|
||
return;
|
||
}
|
||
|
||
document.getElementById("manifest-link").href = latest.manifest || (baseUsed + "/manifest.json");
|
||
document.getElementById("manifest-link").textContent = latest.manifest || (baseUsed + "/manifest.json");
|
||
|
||
// fill table
|
||
const tbody = document.querySelector("#artifact-table tbody");
|
||
tbody.innerHTML = "";
|
||
const arts = manifest.artifacts || {};
|
||
Object.keys(arts).sort().forEach(k => {
|
||
const a = arts[k];
|
||
const tr = document.createElement("tr");
|
||
const url = baseUsed + "/" + a.file;
|
||
tr.innerHTML = "<td><code>" + k + "</code></td><td><a href=\"" + url + "\">" + a.file + "</a></td><td>" + fmtSize(a.size) + "</td><td><code style=\"font-size:11px\">" + a.sha256.slice(0, 16) + "…</code></td>";
|
||
tbody.appendChild(tr);
|
||
});
|
||
if (manifest.wasm) {
|
||
const tr = document.createElement("tr");
|
||
tr.innerHTML = "<td><code>wasm32</code> (browser/mobile)</td><td><a href=\"" + baseUsed + "/" + manifest.wasm.module + "\">" + manifest.wasm.module + "</a> · <a href=\"" + baseUsed + "/" + manifest.wasm.js + "\">" + manifest.wasm.js + "</a></td><td>—</td><td><code style=\"font-size:11px\">" + (manifest.wasm.sha256 || "").slice(0, 16) + "…</code></td>";
|
||
tbody.appendChild(tr);
|
||
}
|
||
|
||
const art = det.key ? arts[det.key] : null;
|
||
if (det.kind === "native" && art) {
|
||
const fileUrl = baseUsed + "/" + art.file;
|
||
const isWin = det.key.indexOf("windows") !== -1;
|
||
status.innerHTML = "Detected <strong>" + det.label + "</strong> → <code>" + det.key + "</code> · hasher <strong>v" + manifest.version + "</strong>";
|
||
const a = document.createElement("a");
|
||
a.className = "btn";
|
||
a.href = fileUrl;
|
||
a.textContent = "Download " + art.file;
|
||
actions.appendChild(a);
|
||
const copy = document.createElement("button");
|
||
copy.className = "btn secondary";
|
||
copy.type = "button";
|
||
copy.textContent = "Show install commands";
|
||
copy.onclick = () => {
|
||
snippet.hidden = false;
|
||
snippet.textContent = installSnippet(fileUrl, det.key, isWin);
|
||
};
|
||
actions.appendChild(copy);
|
||
meta.innerHTML = "SHA-256 <code>" + art.sha256 + "</code> · " + fmtSize(art.size) + " · verify against <a href=\"" + baseUsed + "/SHA256SUMS\">SHA256SUMS</a>";
|
||
snippet.hidden = false;
|
||
snippet.textContent = installSnippet(fileUrl, det.key, isWin);
|
||
} else {
|
||
status.innerHTML = "Detected <strong>" + det.label + "</strong>. No native binary for this environment — use the <strong>WASM/JS</strong> hasher (browser/mobile).";
|
||
if (manifest.wasm) {
|
||
const wa = document.createElement("a");
|
||
wa.className = "btn";
|
||
wa.href = baseUsed + "/" + manifest.wasm.module;
|
||
wa.textContent = "Download " + manifest.wasm.module;
|
||
actions.appendChild(wa);
|
||
const ja = document.createElement("a");
|
||
ja.className = "btn secondary";
|
||
ja.href = baseUsed + "/" + manifest.wasm.js;
|
||
ja.textContent = manifest.wasm.js;
|
||
actions.appendChild(ja);
|
||
const tryBtn = document.createElement("a");
|
||
tryBtn.className = "btn secondary";
|
||
tryBtn.href = "sample.html";
|
||
tryBtn.textContent = "Open stamp sample";
|
||
// prefer multi-doc page when present
|
||
actions.appendChild(tryBtn);
|
||
meta.innerHTML = "WASM SHA-256 <code>" + manifest.wasm.sha256 + "</code>. Import <code>" + manifest.wasm.js + "</code> in a page or worker; hash bytes locally, then call MCP <code>stamp</code> with the hex digest only.";
|
||
snippet.hidden = false;
|
||
snippet.textContent =
|
||
`import init, { sha256_hex_js, Sha256Stream } from "${baseUsed}/${manifest.wasm.js}";
|
||
await init();
|
||
const digest = sha256_hex_js(new TextEncoder().encode("hello"));
|
||
// or stream a File via Sha256Stream.update(chunk) / .finish()
|
||
// then POST stamp { sha256: digest } to ${mcpUrl}`;
|
||
}
|
||
}
|
||
}
|
||
run();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|