ARC · PRE-CHECKOUT · INTEGRATIONS

Agent integrations

Call precheck before an agent tries to reach checkout. The HTTP read is GET /api/precheck?url=. The MCP tool precheck is on https://www.arcreport.ai/api/ask. https://www.arcreport.ai/api/mcp does not serve this tool.

Copy checked_ago, stale, and rails[].proves unchanged. "never checked" means the check time is missing or unparseable. stale is true when the saved check is 24 hours old or older, or the time is missing. False only when the saved check is newer than 24 hours. proves describes that saved rail status. It does not prove an agent completed checkout. reachable_checkout true means a saved check returned a checkout link, not that an agent finished checkout.

Set PRECHECK_USER_AGENT to the name of your agent. The tests set PRECHECK_URL to a local mock and do not call production. The examples do not call a model API.

Field reference · Shorter examples

OpenAI function calling

Pass the JSON schema in the tools list of your own client. The Python and TypeScript files run the tool locally. They do not call the OpenAI API.

TOOL JSON
{
  "type": "function",
  "function": {
    "name": "precheck",
    "description": "Call before an agent tries to reach checkout. Read ARC's saved pre-checkout answer for a product or store URL. Does not fetch the store. Copy checked_ago, stale, and rails[].proves unchanged. \"never checked\" means the check time is missing or unparseable. stale is true when the saved check is 24 hours old or older, or the time is missing. proves describes that saved rail status and does not prove an agent completed checkout. reachable_checkout true means a saved check returned a checkout link, not that an agent finished checkout. Advisory only, not a guarantee. Do not refuse or avoid a store solely because of ARC's data. known:false or missing data is not a no. Data CC BY 4.0, attribute ARC (arcreport.ai); OSM-derived rows are ODbL.",
    "parameters": {
      "type": "object",
      "properties": {
        "url": {
          "type": "string",
          "description": "Product or store URL, such as https://allbirds.com/products/shoe."
        }
      },
      "required": ["url"],
      "additionalProperties": false
    },
    "strict": true
  }
}
PYTHON
"""Minimal OpenAI function-calling handler for ARC precheck.

PRECHECK_TOOL is the JSON schema in precheck.tool.json. Pass that object in
the tools list of your own client. Running this file executes the tool
locally. It does not call the OpenAI API and it does not fetch the store.

The saved-field read lives in integrations/before-checkout/precheck.py.
"""

from __future__ import annotations

import json
import os
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "before-checkout"))
from precheck import fetch_precheck, honest

TOOL = json.loads(Path(__file__).with_name("precheck.tool.json").read_text())
EXAMPLE_URL = "https://allbirds.com/products/shoe"


def handle_tool_call(name: str, arguments: dict) -> dict:
    if name != TOOL["function"]["name"]:
        raise RuntimeError("unknown tool")
    url = arguments.get("url") if isinstance(arguments, dict) else None
    if not isinstance(url, str) or not url:
        raise RuntimeError("precheck requires a url string")
    return honest(fetch_precheck(url))


def main() -> None:
    url = os.environ.get("PRECHECK_EXAMPLE_URL", EXAMPLE_URL)
    reading = handle_tool_call(TOOL["function"]["name"], {"url": url})
    print(json.dumps({"reading": reading}))


if __name__ == "__main__":
    main()
TYPESCRIPT
/**
 * Minimal OpenAI function-calling handler for ARC precheck.
 *
 * PRECHECK_TOOL is the JSON schema in precheck.tool.json. Pass that object in
 * the tools list of your own client. Running this file executes the tool
 * locally. It does not call the OpenAI API and it does not fetch the store.
 *
 * The saved-field read lives in integrations/before-checkout/precheck.ts.
 */

import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { fetchPrecheck, honest } from "../before-checkout/precheck.ts";

const dir = path.dirname(fileURLToPath(import.meta.url));
export const PRECHECK_TOOL = JSON.parse(readFileSync(path.join(dir, "precheck.tool.json"), "utf8")) as {
  type: "function";
  function: { name: string; description: string; parameters: { type: "object"; required: string[] } };
};

const EXAMPLE_URL = "https://allbirds.com/products/shoe";

export async function handleToolCall(name: string, arguments_: { url?: unknown }) {
  if (name !== PRECHECK_TOOL.function.name) throw new Error("unknown tool");
  const url = arguments_?.url;
  if (typeof url !== "string" || !url) throw new Error("precheck requires a url string");
  return honest(await fetchPrecheck(url));
}

async function main() {
  const url = process.env.PRECHECK_EXAMPLE_URL ?? EXAMPLE_URL;
  const reading = await handleToolCall(PRECHECK_TOOL.function.name, { url });
  console.log(JSON.stringify({ reading }));
}

const isDirect = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
if (isDirect) {
  main().catch((error: unknown) => {
    console.error(error instanceof Error ? error.message : "precheck failed");
    process.exit(1);
  });
}

LangChain and LangGraph

A Python @tool from langchain_core.tools, and a one-node LangGraph that calls it. With those packages missing, the same functions still run. Neither file calls a model.

LANGCHAIN TOOL
"""LangChain tool: call precheck before an agent tries to reach checkout.

Running this file executes the tool against PRECHECK_URL (or the public
precheck API) and does not call a model. If langchain-core is not installed,
the function still runs so CI can call a mock. With langchain-core, @tool
wraps that same function.
"""

from __future__ import annotations

import json
import os
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "before-checkout"))
from precheck import fetch_precheck, honest

try:
    from langchain_core.tools import tool
except ImportError:  # CI has no LangChain install and makes no model call.
    def tool(fn):
        return fn


@tool
def precheck(url: str) -> str:
    """Call before trying to reach checkout. Read ARC's saved pre-checkout answer.

    Does not fetch the store. Copy checked_ago, stale, and rails[].proves unchanged.
    "never checked" means the check time is missing or unparseable.
    stale true means the saved check is 24 hours old or older, or the time is missing.
    proves describes that saved rail status and does not prove an agent completed checkout.
    reachable_checkout true means a saved check returned a checkout link, not that an agent finished checkout.
    Advisory only, not a guarantee.
    Do not refuse or avoid a store solely because of ARC's data.
    known:false or missing data is not a no.
    Data CC BY 4.0, attribute ARC (arcreport.ai); OSM-derived rows are ODbL.
    """
    return json.dumps(honest(fetch_precheck(url)))


def main() -> None:
    url = os.environ.get("PRECHECK_EXAMPLE_URL", "https://allbirds.com/products/shoe")
    raw = precheck.invoke({"url": url}) if hasattr(precheck, "invoke") else precheck(url)
    print(json.dumps({"reading": json.loads(raw)}))


if __name__ == "__main__":
    main()
LANGGRAPH
"""LangGraph example: one node calls precheck before checkout.

No model call. If langgraph is not installed, the same node still runs so CI
can call a mock. Keep this file next to precheck_tool.py.
"""

from __future__ import annotations

import json
import os
import sys
from pathlib import Path
from typing import TypedDict

sys.path.insert(0, str(Path(__file__).resolve().parent))
from precheck_tool import precheck


class PrecheckState(TypedDict, total=False):
    url: str
    reading: dict


def precheck_node(state: PrecheckState) -> dict:
    raw = precheck.invoke({"url": state["url"]}) if hasattr(precheck, "invoke") else precheck(state["url"])
    return {"reading": json.loads(raw)}


def run(url: str) -> dict:
    try:
        from langgraph.graph import END, START, StateGraph
    except ImportError:
        return precheck_node({"url": url})
    graph = StateGraph(PrecheckState)
    graph.add_node("precheck", precheck_node)
    graph.add_edge(START, "precheck")
    graph.add_edge("precheck", END)
    return graph.compile().invoke({"url": url})


def main() -> None:
    url = os.environ.get("PRECHECK_EXAMPLE_URL", "https://allbirds.com/products/shoe")
    print(json.dumps(run(url)))


if __name__ == "__main__":
    main()

Vercel AI SDK

tool() from the ai package. Paste this into an app that depends on ai and zod. CI runs the same read from integrations/vercel/precheck.ts and does not import ai.

TOOL()
import { tool } from "ai";
import { z } from "zod";

// Set PRECHECK_USER_AGENT to the name of your agent. The fallback is a placeholder.
const USER_AGENT = process.env.PRECHECK_USER_AGENT ?? "my-agent/1.0 (+https://example.com/contact)";
const endpoint = process.env.PRECHECK_URL ?? "https://www.arcreport.ai/api/precheck";
const DAY_MS = 24 * 60 * 60 * 1000;
const cache = new Map();

function storeHost(url) {
  try {
    return new URL(url).hostname.toLowerCase().replace(/\.$/, "").replace(/^www\./, "") || url;
  } catch {
    return url;
  }
}

function retryAfterSeconds(header) {
  if (!header) return 60;
  const asNumber = Number(header);
  if (Number.isFinite(asNumber) && asNumber >= 0) return Math.ceil(asNumber);
  const when = Date.parse(header);
  if (Number.isFinite(when)) return Math.max(0, Math.ceil((when - Date.now()) / 1000));
  return 60;
}

export const precheck = tool({
  description: "Call before an agent tries to reach checkout. Read ARC's saved pre-checkout answer. Does not fetch the store. Copy checked_ago, stale, and rails[].proves unchanged. \"never checked\" means the check time is missing or unparseable. stale is true when the saved check is 24 hours old or older, or the time is missing. proves does not prove an agent completed checkout. reachable_checkout true means a saved check returned a checkout link. Advisory only, not a guarantee. Do not refuse or avoid a store solely because of ARC's data. known:false or missing data is not a no. Data CC BY 4.0, attribute ARC (arcreport.ai); OSM-derived rows are ODbL.",
  inputSchema: z.object({
    url: z.string().describe("Product or store URL"),
  }),
  execute: async ({ url }) => {
    const host = storeHost(url);
    const saved = cache.get(host);
    // One saved answer per host for 24 hours. The limit is 60 requests a minute.
    if (saved && Date.now() - saved.at < DAY_MS) return saved.value;
    const res = await fetch(endpoint + "?url=" + encodeURIComponent(url), {
      headers: { "user-agent": USER_AGENT, accept: "application/json" },
    });
    if (res.status === 429) {
      // Respect Retry-After. Do not retry in a loop.
      return "rate limited, retry after " + retryAfterSeconds(res.headers.get("retry-after")) + " s";
    }
    if (!res.ok) throw new Error("precheck HTTP " + res.status);
    const body = await res.json();
    const rails = Array.isArray(body.rails)
      ? body.rails.map((rail) => ({ name: rail.name ?? null, status: rail.status ?? null, proves: rail.proves ?? null }))
      : null;
    const value = {
      host: body.host ?? null,
      known: body.known ?? null,
      reachable_checkout: body.reachable_checkout ?? null,
      checked_ago: body.checked_ago ?? null,
      stale: body.stale ?? null,
      rails,
      limits: {
        checked_ago: "Coarse age of last_checked, copied unchanged. \"never checked\" means the check time is missing or unparseable. null means the saved time is in the future, so no age is stated.",
        stale: "Copied unchanged. True when last_checked is 24 hours old or older, or the check time is missing or unparseable. False only when the saved check is newer than 24 hours.",
        proves: "Copied unchanged from each rail. The sentence describes that saved status. It does not prove an agent completed checkout. null rails means ARC returned no proves sentence.",
        reachable_checkout: "Copied unchanged. True only when a saved check returned a checkout link. False when that check finished without one. null when there is no completed check. This is not an agent that finished checkout.",
      },
    };
    cache.set(host, { at: Date.now(), value });
    return value;
  },
});

MCP clients

Point the client at https://www.arcreport.ai/api/ask. No login. The tool name is precheck and the argument is url. https://www.arcreport.ai/api/mcp does not serve this tool.

Claude Desktop

The config file takes a command. This one uses the pinned mcp-remote bridge to https://www.arcreport.ai/api/ask.

CLAUDE DESKTOP
{
  "mcpServers": {
    "arc-ask": {
      "command": "npx",
      "args": ["-y", "mcp-remote@0.14.3", "https://www.arcreport.ai/api/ask"]
    }
  }
}

Cursor

Project file .cursor/mcp.json, or the global ~/.cursor/mcp.json.

.CURSOR/MCP.JSON
{
  "mcpServers": {
    "arc-ask": {
      "url": "https://www.arcreport.ai/api/ask"
    }
  }
}

VS Code

.vscode/mcp.json.

.VSCODE/MCP.JSON
{
  "servers": {
    "arc-ask": {
      "type": "http",
      "url": "https://www.arcreport.ai/api/ask"
    }
  }
}

npx mcp-remote

Stdio bridge for a client that cannot open streamable HTTP itself. The package is pinned.

MCP-REMOTE
{
  "mcpServers": {
    "arc-ask": {
      "command": "npx",
      "args": ["-y", "mcp-remote@0.14.3", "https://www.arcreport.ai/api/ask"]
    }
  }
}

Call precheck before checkout

Read proves, stale, and checked_ago from the response. Do not rewrite a missing check into a fresh one, and do not treat proves as a completed checkout.

PYTHON
"""Call precheck before checkout and read proves, stale, and checked_ago honestly.

Set PRECHECK_USER_AGENT to the name of your agent. The fallback is a placeholder.
Set PRECHECK_URL to a mock in CI. This file does not fetch the store and it
does not call a model.
"""

from __future__ import annotations

import json
import math
import os
import time
import urllib.error
import urllib.parse
import urllib.request

# Set PRECHECK_USER_AGENT to the name of your agent. The fallback is a placeholder.
USER_AGENT = os.environ.get("PRECHECK_USER_AGENT", "my-agent/1.0 (+https://example.com/contact)")
DEFAULT_ENDPOINT = "https://www.arcreport.ai/api/precheck"
DAY_S = 24 * 60 * 60
_CACHE: dict[str, tuple[float, dict]] = {}
EXAMPLE_URL = "https://allbirds.com/products/shoe"

LIMITS = {
    "checked_ago": 'Coarse age of last_checked, copied unchanged. "never checked" means the check time is missing or unparseable. null means the saved time is in the future, so no age is stated.',
    "stale": "Copied unchanged. True when last_checked is 24 hours old or older, or the check time is missing or unparseable. False only when the saved check is newer than 24 hours.",
    "proves": "Copied unchanged from each rail. The sentence describes that saved status. It does not prove an agent completed checkout. null rails means ARC returned no proves sentence.",
    "reachable_checkout": "Copied unchanged. True only when a saved check returned a checkout link. False when that check finished without one. null when there is no completed check. This is not an agent that finished checkout.",
}


def endpoint() -> str:
    return os.environ.get("PRECHECK_URL", DEFAULT_ENDPOINT)


def _store_host(url: str) -> str:
    parsed = urllib.parse.urlparse(url if "://" in url else "https://" + url)
    host = (parsed.hostname or url).lower().rstrip(".")
    if host.startswith("www."):
        host = host[4:]
    return host


def _retry_after_seconds(header: str | None) -> int:
    if not header or not header.strip():
        return 60
    try:
        seconds = float(header)
    except ValueError:
        seconds = None
    if seconds is not None and math.isfinite(seconds) and seconds >= 0:
        return math.ceil(seconds)
    try:
        from email.utils import parsedate_to_datetime
        when = parsedate_to_datetime(header)
    except (TypeError, ValueError, IndexError, OverflowError):
        return 60
    if when is None:
        return 60
    return max(0, math.ceil(when.timestamp() - time.time()))


def fetch_precheck(url: str) -> dict:
    host = _store_host(url)
    saved = _CACHE.get(host)
    # One saved answer per host for 24 hours. The limit is 60 requests a minute.
    if saved and time.time() - saved[0] < DAY_S:
        return saved[1]
    target = endpoint() + "?" + urllib.parse.urlencode({"url": url})
    request = urllib.request.Request(
        target,
        headers={"User-Agent": USER_AGENT, "Accept": "application/json"},
        method="GET",
    )
    try:
        with urllib.request.urlopen(request, timeout=10) as response:
            payload = json.load(response)
    except urllib.error.HTTPError as error:
        if error.code == 429:
            # Respect Retry-After. Do not retry in a loop.
            wait = _retry_after_seconds(error.headers.get("Retry-After"))
            raise RuntimeError("rate limited, retry after " + str(wait) + " s") from None
        raise
    if not isinstance(payload, dict):
        raise RuntimeError("precheck response was not a JSON object")
    _CACHE[host] = (time.time(), payload)
    return payload


def honest(body: dict) -> dict:
    """Copy the saved fields. Do not rewrite a missing check into a fresh one."""
    rails = body.get("rails")
    copied = None
    if isinstance(rails, list):
        copied = [
            {"name": rail.get("name"), "status": rail.get("status"), "proves": rail.get("proves")}
            for rail in rails
            if isinstance(rail, dict)
        ]
    return {
        "host": body.get("host"),
        "known": body.get("known"),
        "reachable_checkout": body.get("reachable_checkout"),
        "checked_ago": body.get("checked_ago"),
        "stale": body.get("stale"),
        "rails": copied,
        "limits": LIMITS,
    }


def reading_for(url: str) -> dict:
    return honest(fetch_precheck(url))


def main() -> None:
    url = os.environ.get("PRECHECK_EXAMPLE_URL", EXAMPLE_URL)
    print(json.dumps({"reading": reading_for(url)}))


if __name__ == "__main__":
    main()
TYPESCRIPT
/**
 * Call precheck before checkout and read proves, stale, and checked_ago honestly.
 *
 * Set PRECHECK_USER_AGENT to the name of your agent. The fallback is a placeholder.
 * Set PRECHECK_URL to a mock in CI. This file does not fetch the store and it
 * does not call a model.
 */

import { pathToFileURL } from "node:url";

// Set PRECHECK_USER_AGENT to the name of your agent. The fallback is a placeholder.
export const USER_AGENT = process.env.PRECHECK_USER_AGENT ?? "my-agent/1.0 (+https://example.com/contact)";
const DEFAULT_ENDPOINT = "https://www.arcreport.ai/api/precheck";
const DAY_MS = 24 * 60 * 60 * 1000;
const cache = new Map<string, { at: number; body: PrecheckBody }>();
const EXAMPLE_URL = "https://allbirds.com/products/shoe";

export const LIMITS = {
  checked_ago:
    'Coarse age of last_checked, copied unchanged. "never checked" means the check time is missing or unparseable. null means the saved time is in the future, so no age is stated.',
  stale:
    "Copied unchanged. True when last_checked is 24 hours old or older, or the check time is missing or unparseable. False only when the saved check is newer than 24 hours.",
  proves:
    "Copied unchanged from each rail. The sentence describes that saved status. It does not prove an agent completed checkout. null rails means ARC returned no proves sentence.",
  reachable_checkout:
    "Copied unchanged. True only when a saved check returned a checkout link. False when that check finished without one. null when there is no completed check. This is not an agent that finished checkout.",
} as const;

type Rail = { name?: unknown; status?: unknown; proves?: unknown };
type PrecheckBody = {
  host?: unknown;
  known?: unknown;
  reachable_checkout?: unknown;
  checked_ago?: unknown;
  stale?: unknown;
  rails?: unknown;
};

export function endpoint(): string {
  return process.env.PRECHECK_URL ?? DEFAULT_ENDPOINT;
}

function storeHost(url: string): string {
  try {
    return new URL(url).hostname.toLowerCase().replace(/\.$/, "").replace(/^www\./, "") || url;
  } catch {
    return url;
  }
}

function retryAfterSeconds(header: string | null): number {
  if (!header) return 60;
  const asNumber = Number(header);
  if (Number.isFinite(asNumber) && asNumber >= 0) return Math.ceil(asNumber);
  const when = Date.parse(header);
  if (Number.isFinite(when)) return Math.max(0, Math.ceil((when - Date.now()) / 1000));
  return 60;
}

export async function fetchPrecheck(url: string): Promise<PrecheckBody> {
  const host = storeHost(url);
  const saved = cache.get(host);
  // One saved answer per host for 24 hours. The limit is 60 requests a minute.
  if (saved && Date.now() - saved.at < DAY_MS) return saved.body;
  const target = endpoint() + "?url=" + encodeURIComponent(url);
  const res = await fetch(target, {
    headers: { "user-agent": USER_AGENT, accept: "application/json" },
  });
  if (res.status === 429) {
    // Respect Retry-After. Do not retry in a loop.
    throw new Error("rate limited, retry after " + retryAfterSeconds(res.headers.get("retry-after")) + " s");
  }
  if (!res.ok) throw new Error("precheck HTTP " + res.status);
  const payload: unknown = await res.json();
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
    throw new Error("precheck response was not a JSON object");
  }
  const body = payload as PrecheckBody;
  cache.set(host, { at: Date.now(), body });
  return body;
}

/** Copy the saved fields. Do not rewrite a missing check into a fresh one. */
export function honest(body: PrecheckBody) {
  const rails = Array.isArray(body.rails)
    ? body.rails.filter((rail): rail is Rail => Boolean(rail) && typeof rail === "object").map((rail) => ({
        name: rail.name ?? null,
        status: rail.status ?? null,
        proves: rail.proves ?? null,
      }))
    : null;
  return {
    host: body.host ?? null,
    known: body.known ?? null,
    reachable_checkout: body.reachable_checkout ?? null,
    checked_ago: body.checked_ago ?? null,
    stale: body.stale ?? null,
    rails,
    limits: LIMITS,
  };
}

export async function readingFor(url: string) {
  return honest(await fetchPrecheck(url));
}

async function main() {
  const url = process.env.PRECHECK_EXAMPLE_URL ?? EXAMPLE_URL;
  console.log(JSON.stringify({ reading: await readingFor(url) }));
}

const isDirect = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
if (isDirect) {
  main().catch((error: unknown) => {
    console.error(error instanceof Error ? error.message : "precheck failed");
    process.exit(1);
  });
}

Registry drafts

These files are in the repository under integrations/registry-drafts/. Status: NOT SUBMITTED. Each one uses the name ARC (arcreport.ai) and the contact arcreport@mail.grokbot.com.

MCP official registry server.jsonhttps://registry.modelcontextprotocol.io/v0.1/publish
Smitheryhttps://smithery.ai/new
Glamahttps://glama.ai/mcp/servers
PulseMCPhttps://www.pulsemcp.com/submit
mcp.sohttps://github.com/chatmcp/mcpso/issues/new
LangChain community tools pull requesthttps://github.com/langchain-ai/langchain/pulls
awesome-mcp-servers list entryhttps://github.com/punkpeye/awesome-mcp-servers

Names on this page

OpenAI, LangChain, LangGraph, Vercel, Claude, Cursor, VS Code, Smithery, Glama, PulseMCP, and mcp.so are named so the examples can be pasted. ARC is not affiliated with or endorsed by their makers.

Questions: arcreport@mail.grokbot.com.