Treatyway MCP contract

Version 1 is a draft standard a seller's MCP server can implement, so a buyer's agent knows what to call. It covers services and goods.

Published November 2026. Status: draft. Open for feedback. Charles Azam publishes this contract. Write to charles@treatyway.com.

This page is the contract. The machine-readable copy is /contract/v1.json. If they disagree, this page wins, and the disagreement is a bug. Old versions stay published. This one stays at /contract/v1.

Treatyway is optional. A buyer's agent may call a seller's MCP directly. In direct mode the conversation never passes through Treatyway. Getting a pass tells Treatyway which seller you asked a pass for, nothing else.

What is live, and what is not, is listed once at the end of this page.

Versioning

A seller declares this contract in two places. Either one is enough. Both is better.

The tool treatyway_contract takes no arguments and returns the standard tools that server implements:

{"contract":"treatyway","version":"1","tools":["describe_offer","check_availability","request_quote","get_status","message"]}

tools lists implemented standard tools only. Leave out a tool that is not implemented. place_order is left out until the seller turns it on. Custom tools are not listed here. Agents discover those with MCP tools/list.

The server may also put this line in its MCP instructions or metadata:

treatyway-contract: 1

That line is the major version. A breaking change bumps it, and the tool's version, to the next whole number. The new contract is published at /contract/v2, then /contract/v3. This page stays here.

An additive change is 1.x. New optional fields and new tools may appear. The instructions line stays treatyway-contract: 1. The tool may return "version":"1.1". A client written for version 1 must keep working: servers ignore unknown input fields, and clients ignore unknown output fields. Removing a field, changing a type, or changing what a field means is a breaking change.

This publication's version is 1.

Tools

Six tools. Each one takes JSON and returns JSON. Names:

The minimum trust level is a rule of this standard. Treatyway's hosted tools enforce it. Level 0 cannot call request_quote or place_order. check_availability and message record an information request, so level 0 may call them within today's caps. A seller's own server enforces the same minimums when the caller presents a pass.

ToolMinimum levelDefault
describe_offer0On
message0On
check_availability0On. Records an information request.
request_quote1On
get_status0On
place_order2Off. Treatyway's hosted tool returns not_supported.

Level 0 is also limited in how many requests the seller accepts. The seller chooses the numbers. Level 0 gets fewer requests than level 1. Treatyway's own caps are in agents.md. This standard does not freeze those numbers.

Every answer

Every response names who answered. answered_by.name is required. answered_by.role is optional: business, person, or agent.

Every response may include needs_human when a person still has to supply or decide something. needs_human.missing is an array of short strings saying what is missing. A shop that cannot answer is not in error. The call succeeded, and the body says what is missing.

Unknown input fields are ignored. Unknown output fields are ignored.

describe_offer

What the seller sells. Use it for a service, a good, or both.

Input fields are all optional.

FieldRequiredMeaning
kindnoservice, good, or any. Filters the list.

Output:

FieldRequiredMeaning
offersyesThe list. Empty only when needs_human is set.
offers[].nameyesShort name.
offers[].kindyesservice or good.
offers[].idnoStable id for later calls.
offers[].descriptionnoWhat the buyer gets.
offers[].areanoWhere this offer is served or shipped.
offers[].hoursnoWhen the seller takes it.
offers[].lead_timenoHow long until the work or the delivery.
offers[].how_to_ordernoHow to order this offer.
area, hours, lead_time, how_to_ordernoThe same facts for the whole shop, when they are not per offer.
answered_byyesWho answered.
needs_humannoWhat is missing.

Example, a service and a good. The prices and hours are an illustration, not a live listing.

{
  "offers": [
    {
      "id": "site-visit",
      "name": "Site visit",
      "kind": "service",
      "area": "Oakland and Berkeley",
      "hours": "Weekdays 08:00–16:00",
      "lead_time": "3 business days",
      "how_to_order": "request_quote, then place_order if the seller has enabled it"
    },
    {
      "id": "brass-rod",
      "name": "Brass rod, 10 mm",
      "kind": "good",
      "lead_time": "Ships in 2 business days",
      "how_to_order": "request_quote with a quantity"
    }
  ],
  "how_to_order": "Describe the job or the basket with request_quote.",
  "answered_by": {"name": "Example Metals", "role": "business"}
}

check_availability

A date or time for a service. A quantity in stock for a good.

FieldRequiredMeaning
kindyesservice or good.
startwhen kind is serviceISO-8601 date or date-time.
endnoEnd, for a service.
quantitywhen kind is goodWhole number, 1 or more.
offer_idnoFrom describe_offer.
skunoSeller's own stock code, for a good.

Output: available (boolean) when the seller can answer. quantity_available for a good. Echo start and end when useful. note is optional. answered_by is required. Omit available and set needs_human when the seller cannot answer.

{
  "available": true,
  "start": "2026-11-20T15:00:00-07:00",
  "answered_by": {"name": "Example Metals", "role": "business"}
}
{
  "available": true,
  "quantity_available": 40,
  "answered_by": {"name": "Example Metals", "role": "agent"}
}

request_quote

A described job, or a basket of goods. Returns quote_id and a price, or the string needs_human in place of a price.

Treatyway's hosted request_quote requires business_id, subject, message, quantity, destination, and idempotency_key. Level 0 cannot call it. On a seller's own server, request_quote requires summary. destination is optional there.

The fields below are the seller's own server.

FieldRequiredMeaning
summaryyesThe job or the basket, in plain text.
kindnoservice or good.
linesnoStructured lines. Each line needs description. quantity, offer_id, sku, and when are optional.
whennoDate or time for a service.
destinationnoWhere the work happens, or where goods should go.
currencynoISO 4217 code the buyer prefers.

Output:

FieldRequiredMeaning
quote_idyesId for get_status and place_order. Returned even when the price needs a person.
priceyesAn object with amount and currency, or the string needs_human.
price.amountwith a price objectDecimal string, such as "1380.00". Not a float.
price.currencywith a price objectISO 4217 code.
expires_atnoISO-8601 time the price stops being valid.
notenoA short condition.
answered_byyesWho answered.
needs_humannoWhat is missing. Set it when price is needs_human.
{
  "quote_id": "q_example",
  "price": {"amount": "1380.00", "currency": "USD"},
  "expires_at": "2026-11-27T00:00:00Z",
  "answered_by": {"name": "Example Metals", "role": "person"}
}
{
  "quote_id": "q_example",
  "price": "needs_human",
  "needs_human": {"missing": ["the alloy, and whether the rods are cut to length"]},
  "answered_by": {"name": "Example Metals", "role": "business"}
}

The first example is a priced job. The second is a basket the seller will not price without a person. Neither example is a live quote.

place_order

Books a service, or orders goods, against a quote. It does not take payment. Treatyway has no payment code, and this standard does not add one.

Off by default. The seller enables it by listing place_order in treatyway_contract and in MCP tools/list. Until then, agents do not call it. A call to a tool that is not listed is refused. Nothing is booked.

Requires trust level 2. Below that, refuse the call and do not book. The error text says that place_order requires trust level 2. This is a refusal, not a needs_human answer.

A direct caller with no pass has no Treatyway level. The seller must not treat a missing pass as level 2. It may refuse place_order, or use its own authentication. A call that went through Treatyway carries the trust label. Only Level 2, Owner proven, may place an order.

FieldRequiredMeaning
quote_idyesFrom request_quote.
confirmyesMust be true. The agent is committing.
notenoA short note to the seller.

Output: order_id and status when an order exists. status is accepted, rejected, or needs_human. quote_id may be echoed. answered_by is required. When nothing was booked, status and needs_human are enough, and order_id is omitted.

{
  "order_id": "o_example",
  "quote_id": "q_example",
  "status": "accepted",
  "answered_by": {"name": "Example Metals", "role": "person"}
}

get_status

Status of one quote or one order. Send quote_id, order_id, or both.

status is the seller's word. Common values are open, quoted, needs_human, accepted, in_progress, completed, cancelled, no_show, and rejected. Others are allowed. summary is optional. answered_by is required.

{
  "quote_id": "q_example",
  "order_id": "o_example",
  "status": "in_progress",
  "summary": "Cutting Thursday.",
  "answered_by": {"name": "Example Metals", "role": "business"}
}

message

Free text. text is required. quote_id and order_id are optional context.

The output may include text as the reply. answered_by is required. needs_human is optional.

{
  "text": "The visit includes measuring the doorway.",
  "answered_by": {"name": "Ada", "role": "person"}
}

Level 0 may send a message. The business is told the agent is not authenticated. The seller may cap how many.

Tools a seller adds

A seller may add tools. get_menu and configure_product are examples. Agents discover them with MCP tools/list. They are not part of this contract, and they are not listed in treatyway_contract.

Reserved names:

Those names are reserved so a later version of this contract can use them.

Identity pass

Issued when signing is configured. If it is not, get_pass returns not configured and /.well-known/jwks.json has an empty keys array. A seller must not require a pass.

The shape:

  1. The buyer's agent asks Treatyway for a pass scoped to one seller. Treatyway learns only that agent X wants to reach seller Y. Treatyway does not learn the message, the quote, or the order.
  2. The agent calls the seller's MCP directly and sends the pass as Authorization: Bearer.
  3. The seller checks the pass locally with Treatyway's public keys. There is no call back to Treatyway.

The pass is a signed JWT. Proposed claims:

ClaimMeaning
isshttps://treatyway.com.
subThe agent id.
owner{ "name", "proof" }. proof is declared, social, or domain. Level 0 cannot get a pass.
level1 or 2.
audThe seller's domain. A pass for one domain is refused at another.
exp10 minutes from issue. iat is included.
jtiA unique id. Treatyway stores the agent id, aud, jti, and the time. It does not store the conversation.

The algorithm is ES256 (ECDSA P-256, SHA-256). The public keys are at https://treatyway.com/.well-known/jwks.json. When signing is not configured that document is { "keys": [] }.

get_pass takes seller_domain and needs a level 1 or 2 workspace key. POST /api/pass/verify with { "token" } checks the signature locally and returns the claims. A seller should still verify locally and not depend on that call.

Local check:

const token = process.argv[2];
const jwks = await fetch("https://treatyway.com/.well-known/jwks.json").then((response) => response.json());
const [headerPart, payloadPart, signaturePart] = token.split(".");
const header = JSON.parse(Buffer.from(headerPart, "base64url").toString());
const key = jwks.keys.find((item) => item.kid === header.kid);
const cryptoKey = await crypto.subtle.importKey("jwk", key, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
const ok = await crypto.subtle.verify(
  { name: "ECDSA", hash: "SHA-256" },
  cryptoKey,
  Buffer.from(signaturePart, "base64url"),
  new TextEncoder().encode(`${headerPart}.${payloadPart}`),
);
const claims = JSON.parse(Buffer.from(payloadPart, "base64url").toString());
if (!ok || claims.iss !== "https://treatyway.com" || claims.exp * 1000 < Date.now()) throw new Error("pass rejected");

A Bearer workspace key and the trust label still apply when the call goes through Treatyway. Direct calls use the pass, or whatever authentication the seller already has. Level 0 has no pass.

Trust levels

A request shows one of three labels, with a level number.

Agents

LevelLabelWhat the business is told
0Owner unknownThis agent is not authenticated. Treatyway does not know who operates it.
1Owner namedThe name, and that it was not checked. Treatyway's words today are "Declared by agent, not verified".
2Owner provenThat the owner controls a website or account. Level 2 proves the operator controls a website or account. It does not mean a specific person approved the request.

A level 1 name can be typed by anyone. Never present it as checked.

What each level may call is in the tools table above. place_order is level 2 only, and only after the seller enables it.

Businesses

These are the business's own levels. They are not the agent levels above.

LevelMeaning
Business level 1The business gives its website. Declared, not checked. Treatyway has no declared-only state. Starting a claim checks the domain immediately.
Business level 2The business proves its website. A claim proves control of the listed website, by a DNS TXT record or a file on that site. A listing with no website cannot reach Business level 2. The company name is not proved. A separate registry match exists for some San Francisco listings. That match is not Business level 2.

An unclaimed listing is neither Business level. Email to that listing is not live.

Direct, or through Treatyway

Both are allowed. Treatyway is not a mandatory middleman.

In direct mode the conversation never passes through Treatyway. Getting a pass tells Treatyway which seller you asked a pass for, nothing else. A direct call does not appear on a Treatyway dashboard, and it does not feed reputation.

Going through Treatyway is the path for a seller that has no MCP of its own. It is also a path a seller with an MCP may choose. Treatyway records the request. When a channel is connected, it can deliver a signed webhook. Email to a business is not live.

Reputation, when it exists, counts only what Treatyway can see or what a party confirms. A direct deal counts only after an outcome report. A fast reply counts toward ranking only when the reply went through Treatyway. The reports are stored. The ranking rule is not in force.

Outcome reports

POST /api/outcome and the MCP tool report_outcome. Send pass_jti or request_id, and outcome of completed, no_show, or cancelled. No message, no price, and no description. A workspace key reports as the buyer. A business key reports as the seller. One report per party per deal. The same outcome again returns the stored row. A different outcome is rejected. The row is kept for a future reputation score. It does not change ranking today.

Data

Treatyway is free. There are no payments. Treatyway does not sell data.

A call through Treatyway can store the workspace names the agent typed, the website, the level, and the proof details. The key is stored as a hash. A request stores the business id, the category, the subject, the message, and the thread. The public privacy notice is /privacy. It is the list of what is stored. This page does not add to it.

A direct call stores nothing at Treatyway. Asking for a pass stores the agent id, the seller domain, the pass id, and the time. It does not store what the agent later says to the seller.

Where the database sits is not stated in this contract.

Feedback

This is a draft. Send corrections to charles@treatyway.com. Say which section, and whether you are implementing it or calling it.

Status

Live

Not live yet