Bring your own tool
Scaffold a member, run the member probe, and register it with the dealer's approval. The worked example is a service scheduler a dealership built in-house.
A member is any tool a dealer wants behind its gateway: a vendor's scheduler, chat desk, digital retailing or credit application, or something the dealership's own developer built. This page walks the story the demo tells: "I built a service scheduler at my dealership."
The story in four commands
From a checkout of the repository (the public repository opens with the specification repository; until then, ask for access):
# 1. Scaffold a member on the gateway's shared runtime, prefilled from the tool catalog
node packages/create-automotivemcp-server/bin/create-amcp-server.mjs my-scheduler \
--member --tools leads.create_appointment,leads.get_appointment \
--contact email --role service_scheduler --name "Service scheduler (sandbox)"
# 2. Replace the generated handlers with your own system, then run the member probe
cd packages/testkit-gateway
npm ci # once: the gateway's one dependency, the MCP SDK
node bin/amcp-testkit.mjs probe my-scheduler --out report.json
# 3. A local gateway with your member connected for the demo dealer
node bin/amcp-testkit.mjs up --connect my-scheduler
# 4. The generated test runs the probe in CI
npm testStep 1 writes members/my-scheduler/ (manifest, handlers, sample data from the published examples with no
personal data, a README), a test that runs the probe, a catalog entry in config/gateway.json, and, with
--contact, the disclosure text the shopper will agree to. The scaffold passes the probe before you change a
line. Tool names come from the catalog: the scaffolder refuses one it does not list.
With --role service_scheduler, the manifest's leads.create_appointment entry carries
appointment_types: ["service"]: that is what makes the gateway send service appointments (type service) to your
member instead of the CRM's test drives. The generated handler records an appointment; once yours lists open slots
when start_at is missing, add preview_when_missing: "start_at" so a shopper can see times before consenting.
The demo's own scheduler (members/service-scheduler) was made exactly this way. The builder replaced the two
handlers with the store's service calendar and filled in the manifest; everything else is the scaffold. A
dealer connects it from the dealer view, and then
leads.create_appointment also schedules service appointments (type service) and leads.get_appointment appears on
the gateway's tool list.
amcp-conform --member my-scheduler runs the same probe from the conformance CLI.
Six rules a member follows
1. Keep normative tool names. Your brand appears in provenance, never in a tool name. If the catalog lacks a capability, propose it at /contact rather than naming your own.
2. Accept only gateway-exchanged tokens. Verify the ES256 signature against the gateway's JWKS, that aud
is you, that act is present, that the scope covers the tool and stays within your manifest, that it names
exactly one location you serve, and that it has not expired or been revoked. The shared runtime does all of
this before your handler runs:
import { createMemberServer, MemberError } from '../_runtime/member-server.mjs';
export default function createMember(opts) {
return createMemberServer({ ...opts, tools: {
'leads.get_appointment': {
scope: 'amcp:leads:read',
handler: async (args, ctx) => { /* ctx.claims is already verified */ },
},
} });
}3. Key customers on the pairwise id. ctx.claims.sub is the shopper as your vendor alone knows them. Do
not expect an email.
4. Need contact? Only by receipt. Declare it in the manifest (identity.contact_access: via_receipt and a
consent_scope, which becomes your own checkbox on the consent page), then verify the receipt addressed to
you and ask the gateway to release the fields it covers:
import { useReceipt } from '../_runtime/consent-use.mjs';
const { release, withheld } = useReceipt(ctx, { channel: 'email' });
// release.contact holds name and email, released by reference; withheld says why not5. Need more identity? Step up. Set required_amcp_level on the tool. The runtime refuses a weaker
delegation with insufficient_scope, and the gateway runs the consent ceremony. A call that is only a
preview (open slots, say) can name the argument whose absence makes it one: preview_when_missing.
6. Return schema-valid objects, marked sandbox. Whatever a handler returns must validate against the
entity schema, and a write sets extensions["io.automotivemcp/sandbox"]: true. The gateway adds provenance.
The member manifest
Served at /members/<id>/manifest.json; schema at
/test-kit/schemas/member-manifest.json (draft 0.1-draft).
| Field | Meaning |
|---|---|
member_id | Canonical https URI, also your token audience |
display_name, role, operator | How the dealer and the trace show you, and where tickets about you go |
tools[] | Normative name, level, scopes, read_only, side_effects (none, sandbox; a sandbox gateway refuses live), required_amcp_level, returns_entity, example_arguments |
auth | Token exchange, accepted issuers, JWKS, requires_act |
identity | contact_access (none or via_receipt), receipt channels, your consent_scope |
sandbox | Enabled, no live delivery, no outbound path |
direct_endpoint | Your own endpoint, so a gateway stays optional |
What the member probe checks
| Group | Checks |
|---|---|
| Manifest | Validates against the schema; names are in the catalog; sandbox only; never an SSN or date of birth; contact only by receipt, with disclosure text |
| Surface | server/discover answers on 2026-07-28; tools/list matches the manifest |
| Refuses | No token, a bad signature, a sub without act, the wrong audience, an expired token, a wider scope, a token for another tool, a location you do not serve, a revoked parent |
| Accepts | A correct token on every tool; the section 12.8 audit record names you, the tool, the pairwise id and the token; returned entities validate |
| Identity | A tool that needs contact steps up for an anonymous shopper; writes carry the sandbox flag; no error message carries an email or phone number |
The report is signed (ES256 over its hash, public key included), like the conformance report, so a published result cannot be edited afterwards. The dealer view runs the probe before it lets a dealer approve a member, and refuses one that fails.
Connect it
- Locally:
node bin/amcp-testkit.mjs up --connect my-scheduler, or--member ./path/to/memberfor a member that lives outside the repository. Point Inspector, Claude Code or Cursor at the printed URL. - On the public sandbox, from the catalog: members it hosts appear in the dealer view's catalog.
- On the public sandbox, at your own URL: see the next section.
Run it at your own URL and add it by URL
The public gateway can reach a member at its own public https address. A dealer signs in to the dealer view
(sandbox), pastes the endpoint, reviews what it asks for and the probe result, and approves it for that store
only. The reference build is the Northshore Service Scheduler, live at
https://scheduler-demo.automotivemcp.ai/mcp; its code is packages/reference-scheduler.
-
Scaffold and build as above, or start from the reference scheduler. A member at its own URL serves
POST /mcpandGET /manifest.json, and its manifest'smember_idis its own https origin: that is the token audience. -
Run it locally:
PORT=3341 PUBLIC_URL=https://<your-public-host> node packages/reference-scheduler/server.mjs -
Expose it. Either deploy it behind https on a host you control, or open a quick tunnel for a demo:
cloudflared tunnel --url http://127.0.0.1:3341 # prints https://<random-words>.trycloudflare.com; restart the member with that as PUBLIC_URL -
Add it by URL. Open /demo/dealer, sign in as the dealer (a demo address and an on-screen code), choose Add your own tool by URL, paste
https://<your-public-host>/mcp, read the review and the probe result, tick the scopes and approve. It connects for that store only and expires after three hours.
What the gateway enforces before and during every call to a member it does not host:
- https only, the standard port, no user name or password in the URL, a domain name rather than an IP address
- the name is resolved by the gateway and every address must be public: loopback, private, link-local, CGNAT, multicast, reserved, documentation and cloud metadata ranges are refused, IPv4 and IPv6; the connection is pinned to the address that passed, and the check runs again on every call
- no redirects, short timeouts, capped response sizes, JSON only
- normative tool names and AutomotiveMCP scopes only; the member probe must pass in full
- a few outside tools at once across the sandbox, one per store, rate limits per store and per visitor
- contact only by consent receipt on a fictional sandbox persona; a member outside the gateway never receives a name, phone or email
- every add, refusal, connection and disconnection is logged on the session trace, and the dealer can disconnect at any time
Hand it to your coding agent
Read https://automotivemcp.ai/llms.txt and https://automotivemcp.ai/test-kit/bring-your-own-tool.
In this repository, scaffold an AutomotiveMCP gateway member for <your tool> with
create-automotivemcp-server --member, using only tool names from tools.json. Wire the handlers to
<your system>, keep every write sandbox, take contact only through useReceipt, and keep going until
node bin/amcp-testkit.mjs probe <id> passes.