Install#
The server is a remote MCP endpoint at https://explint.com/mcp. Claude, ChatGPT, Grok Bot, Cursor, and Gemini all connect to it.
claude mcp add --transport http ab-lint https://explint.com/mcpSign in#
A client that handles OAuth needs nothing beyond the URL. The first call opens a browser window where you sign in and choose the organization the connection works in, and every call after that acts inside the organization you picked.
An assistant that cannot run that flow sends an API key instead, as the header Authorization: Bearer <api key>. You create keys on the MCP page of the A/BLint workspace, and revoking one there stops it working on the next call.
Tools#
Two of the four tools name what to do with an operation string, one of create, update, remove, or list, and carry that operation's arguments in the field named after it, so a call with operation: "create" fills create and leaves the rest out. ablint__lint takes its arguments directly, and ablint__docs takes no arguments and answers with the full documentation as markdown. No call names an organization, because the credential decides which one every call works in.
Projects are named by name on ablint__project and by projectSlug on the other two, matched inside the organization ignoring case, so any casing of the name or the slug reaches the same project.
ablint__docs#
Return the full documentation of how to use the MCP server as one markdown document, covering how to connect and sign in, every tool with its arguments and answer shapes, how listings page, and what each refusal means. The tool takes no arguments, reads and writes nothing in the organization, and never spends a lint request, so an assistant can call it at the start of a session and again whenever the shape of another tool needs checking.
{}ablint__project#
Create, rename, remove, and list the organization's projects.
| Argument | What it carries |
|---|---|
| operation | Required. One of create, update, remove, or list. |
| create | name for the new project. |
| update | name for the project to rename and newName for what to rename it to, which regenerates its slug with it. |
| remove | name for the project to remove. |
| list | slug to keep only projects whose slug holds that text, limit for how many rows one answer carries, and cursor to continue a listing. |
{
"operation": "create",
"create": { "name": "Checkout cross-sell" }
}ablint__rule#
Create, change, remove, and list the rules inside a project. A rule carries a key such as hypothesis/names-metric, a title, an instruction written in plain English that the linter judges the spec against, a severity of error or warning, and a switch for whether it is on. The category before the slash is how the workspace groups a project's rules, and a rule is found by its key matched ignoring case.
| Argument | What it carries |
|---|---|
| operation | Required. One of create, update, remove, or list. |
| create | projectSlug, ruleKey, ruleTitle, and ruleInstruction, plus optional severity (error or warning, error by default) and enabled (on by default). |
| update | projectSlug and ruleKey to name the rule, plus any of ruleTitle, newRuleKey, ruleInstruction, severity, and enabled to change. |
| remove | projectSlug and ruleKey to name the rule to remove. |
| list | projectSlug, plus optional ruleKey to keep only rules whose key holds that text, limit, and cursor. |
{
"operation": "create",
"create": {
"projectSlug": "checkout-cross-sell",
"ruleKey": "hypothesis/names-metric",
"ruleTitle": "Names the metric",
"ruleInstruction":
"The hypothesis names the metric it expects to move."
}
}ablint__lint#
Lint a spec against every rule the project has switched on.
| Argument | What it carries |
|---|---|
| projectSlug | Required. The project to lint against, by name or slug matched ignoring case. |
| content | The spec itself as markdown, HTML, or plain text, judged against the project's rules. An image embedded in it as base64 is dropped before it is judged, and content beyond the acceptable size comes back as an error. |
A lint, end to end#
One lint through every surface: the ablint__lint tool call beside the same request over REST, written for curl, JavaScript, and Python.
{
"projectSlug": "checkout-cross-sell",
"content":
"Hypothesis: a full-width add-to-cart button on phones increases checkout completion."
}{
"quality_score": 0.5,
"errors": [
{
"title": "If, then, and because are all present",
"key": "hypothesis/structure",
"description": "The hypothesis states what changes, what it expects, and why.",
"probability": 0.31,
"bad_example": "We believe the new onboarding will improve the experience.",
"good_example": "Showing the setup checklist during signup will increase activation because new users can see what a finished workspace looks like."
}
],
"warnings": [
{
"title": "Success metric says how it is measured",
"key": "metric/measurement-type",
"description": "The success metric names how it is measured, as a rate, a count, or a median.",
"probability": 0.62,
"bad_example": "We will measure success by engagement.",
"good_example": "Success is the median time from signup to first project created, measured across new accounts."
}
],
"passed": [
{
"title": "Names the metric",
"key": "hypothesis/names-metric",
"description": "The hypothesis names the metric it expects to move.",
"probability": 0.93,
"bad_example": "The new pricing page will perform better.",
"good_example": "The new pricing page will increase checkout completion among new visitors."
},
{
"title": "One change per test",
"key": "hypothesis/one-change",
"description": "The spec names one change so the result can be read against it.",
"probability": 0.86,
"bad_example": "The variant changes the headline, the price, and the form together.",
"good_example": "The variant changes only the header call to action, with the rest of the page unchanged."
}
]
}The same shape comes back over both surfaces.
Every rule lands in exactly one of errors, warnings, or passed, by the severity its rule carries, and each list comes back sorted by key. An entry carries the rule's title, key, description, and probability, the chance the model gave the content of satisfying the rule, together with the rule's bad_example and good_example where the rule carries them, each a short excerpt showing what a break looks like and what passing looks like, so an assistant can act on a break without a second call. A rule passes at a probability of 0.8 or more. quality_score is the share of the rules that passed, rounded to three decimals.
Lint a hypothesis in the browser
These four rules run locally as you type. A lint on the server judges your project's own rules and answers a probability for each.
- If, then, and because are all presenthypothesis/structureStates what changes, what it expects, and why.error
- Names the metrichypothesis/names-metricNames the metric it expects to move.passed
- Success metric says how it is measuredmetric/measurement-typeNames how it is measured, as a rate, a count, or a median.warning
- One change per testhypothesis/one-changeOne change so the result can be read against it.passed
REST API#
Every tool above is also a REST call, so a script or an agent that sends HTTP can do the same work. The API lives at https://explint.com/api and takes an API key as the header Authorization: Bearer <api key>, created on the MCP page of the A/BLint workspace. The key decides which organization every call works in, so no call names one.
Nine endpoints cover the work the three tools do:
Replace <api key> with a key from the MCP page. Paths match organization ignoring case.
A project is named in the path by name or slug, matched inside the organization ignoring case the way the tools match it. A rule is named by its key, and a key may hold slashes, so percent-encode it on the way in: hypothesis%2Fnames-metric and hypothesis/names-metric both reach the same rule.
Listings page through their rows the way the tools do: limit carries from 1 to 100 rows and 25 when it is left out, slug and ruleKey narrow a listing to the rows holding that text, and the nextCursor an answer returns is what the next call passes as cursor until it comes back null.
A lint takes the spec as content and answers the same shape ablint__lint returns. An image embedded in the content as base64 is dropped before it is judged, content beyond the acceptable size comes back as an error, and a lint no model answered comes back as the linter being unable to answer.
A refusal answers {"error": "<message>"} under a status that says what kind it is:
| Status | What it answers |
|---|---|
| 400 | A request to fix. |
| 401 | A missing or invalid API key. |
| 402 | A balance spent on linting. |
| 403 | The Default project refusing removal. |
| 404 | No such endpoint, project, or rule. |
| 405 | A path called with the wrong method. |
| 409 | A project name or rule key already taken. |
| 502 | A lint the linter could not answer. |
The whole surface is published as an OpenAPI document at https://explint.com/openapi.json, which is how an agent learns every path, argument, and answer in one fetch.