---
title: "MCP · tabnas"
description: "Connect an agent to tabnas over the Model Context Protocol: seven tools for parsing, validating grammars and explaining failures, over stdio or hosted."
source: "https://tabnas.dev/mcp/"
---

# The tabnas MCP server

`@tabnas/mcp` gives an agent seven tools over the Model Context Protocol: parse with a grammar, validate one before running it, explain a failure, run fixtures, read the plugin catalogue, and compare two grammar versions. The same package installs a `tabnas` command-line tool running the same core, so anything an agent can do through MCP you can reproduce in a shell. It is listed in the official MCP Registry as `dev.tabnas/mcp`, and it is one of the two MCP entries the [Skills package](https://tabnas.dev/skills/) installs.

## Connecting

The server speaks MCP over stdio and needs no installation of its own: `npx` fetches it on first run. Add it to your client's MCP configuration:

```
npx --yes @tabnas/mcp@0.1.17 mcp
```

Or, as the manifest the [Skills package](https://tabnas.dev/skills/) already declares:

```
{
  "servers": {
    "tabnas": {
      "type": "stdio",
      "command": [
        "npx",
        "--yes",
        "@tabnas/mcp@0.1.17",
        "mcp"
      ]
    },
    "tabnas-hosted": {
      "type": "streamable-http",
      "url": "https://mcp.tabnas.dev/mcp"
    }
  }
}
```

### In Claude Code

The [skills plugin](https://tabnas.dev/skills/#install) installs both server entries alongside the five skills: that is the recommended route, since the skills teach the workflows these tools execute. To register the server alone:

```
claude mcp add tabnas -- npx --yes @tabnas/mcp@0.1.17 mcp
```

### In the MCP Registry

The server is `dev.tabnas/mcp` in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=tabnas): the namespace is verified against the `tabnas.dev` domain itself. A client that resolves servers from the registry gets both transports from one entry: the npm package (`@tabnas/mcp`, stdio, run via `npx`) and the hosted streamable-HTTP remote. The entry is declared by [server.json](https://github.com/tabnas/mcp/blob/main/server.json) in the mcp repository and republished by the release pipeline, so it tracks the npm version.

### Hosted, for clients that cannot spawn a process

The `tabnas-hosted` entry points at `https://mcp.tabnas.dev/mcp`. Local stdio stays the **recommended** path: it is free, private, unlimited and reproducible. The hosted endpoint is a convenience for clients that cannot run `npx`, and it is bounded: a 256 KB body cap and a per-IP rate limit, both reported by its [`/.well-known/mcp`](https://mcp.tabnas.dev/.well-known/mcp). Document content is never logged, stored, or used for training: the [privacy policy](https://tabnas.dev/privacy/) is short because the service keeps nothing. Checking it by hand: the endpoint is POST-only JSON-RPC, so a browser gets a 405 that says so; [`/health`](https://mcp.tabnas.dev/health) and `/.well-known/mcp` are the two GET routes.

## The seven tools

| Tool | CLI | What it does | Returns |
| --- | --- | --- | --- |
| `parse` | `tabnas parse` | Parse input with a grammar and return the tree. | `{ok:true, tree} | {ok:false, diagnostic}` |
| `validate_grammar` | `tabnas validate` | Check a serialized GrammarSpec before running it. | `{ok:true, v} | {ok:false, errors:[{path,message}]}` |
| `explain_parse_error` | `tabnas diagnose` | Explain a failure: code, position, expected, rule stack, joined with the error registry's entry for the code. | `{failed:false} | {failed:true, diagnostic, registry}` |
| `test_grammar` | `tabnas test` | Run shared .tsv fixtures against a grammar. | `{pass, fail, rows:[{row,input,expected,got,ok}]}` |
| `list_plugins` | `tabnas plugins` | List the grammar plugins and what each parses. | `{plugins:[…]}` |
| `describe_plugin` | `tabnas plugins <name>` | One plugin's descriptor: base, grammar, extensions, error codes. | `the tabnas.plugin.json object` |
| `compare_grammars` | `tabnas compare` | Does a grammar change still accept what the old one accepted, and build the same trees? Reports evidence and confidence, never a bare verdict. | `{proven[], observed[], changes[], counterexamples[], confidence, why}` |

Every tool returns JSON, and the CLI's `--json` prints the same bytes the MCP tool returns: that equality is covered by golden tests, so a shell transcript and an agent transcript describe the same run.

### The contracts worth knowing

-   **Every call gets a fresh engine.** `parse` applies `options` first, then `grammar`; with no grammar the bare engine defines no rules, so every input yields an undefined tree.
-   **A grammar argument is validated before it is used**, by every operation that accepts one: an invalid grammar is rejected with `validate_grammar`'s error shape rather than half-run.
-   **A grammar is data, never code.** A firewall runs first on every grammar-accepting operation: it rejects `__proto__` / `constructor` / `prototype` keys anywhere in the tree, any `ref` key (live functions are not JSON), any function reference that is not a `$`\-suffixed engine builtin, a `plugins` option (a plugin is live code), and grammars over 5000 rules. "Validate this grammar" never becomes "run this code".
-   **The tools take a serialized GrammarSpec: never ABNF, EBNF or jsonic source.** Compiling a notation means running a compiler, which the rule above forbids. Compile first, then pass the result:

    ```
    const { abnfConvert, toPureSpec } = require('@tabnas/abnf')
    const spec = toPureSpec(abnfConvert(abnfSource, { builtins: true }))
    // -> { options, rule, v, meta } — validates clean, safe to send
    ```

    `toPureSpec` is the function for this: it strips the compiler-internal fields the firewall rejects and stamps `v`.
-   **`test_grammar` takes TSV content** in the fleet's fixture convention: a header row, an escape-decoded input column, and an expected column holding a JSON value or `ERROR` / `ERROR:<code>`. Specs over 10 000 rows are refused.

## The `tabnas` CLI

The package's one binary. `npm install -g @tabnas/mcp` puts it on your path; the skills teach these spellings, so they work with or without a server connected. Input comes from a file argument, or stdin when it is `-` or absent; the CLI never touches the network.

```
tabnas parse    [file|-] [--grammar g.json] [--json]
tabnas validate --grammar g.json [--json]
tabnas diagnose [file|-] [--grammar g.json] [--json]
tabnas test     --spec fixtures.tsv [--grammar g.json] [--json]
tabnas plugins  [name] [--json]
tabnas compare  --a old.json --b new.json [--corpus dir|file] [--json]
tabnas mcp      # run the MCP server (stdio)
```

Exit codes are part of the contract: `0` means yes (parse succeeded, grammar valid, all fixture rows passed), `1` means the operation said no (parse failure, invalid grammar, fixture failures), and `2` is a usage error. Without `--json` you get a readable rendering; with it, the exact core result JSON.

## What it serves

The server also exposes the fleet's contract files as MCP resources, verbatim from the package's bundled data: the grammar JSON Schema (`tabnas://schema/grammar`), the diagnostic schema (`tabnas://schema/diagnostic`), the [error registry](https://tabnas.dev/errors/) (`tabnas://errors`), every plugin's descriptor (`tabnas://plugins`), and the recorded cross-runtime divergences (`tabnas://divergence`). Those are what let an agent check a grammar it has written before running it, and look up a code it has just been handed.

Source and full tool schemas: [tabnas/mcp](https://github.com/tabnas/mcp) ([npm](https://www.npmjs.com/package/@tabnas/mcp)).

## Parsed documents are data

Anything the tools return came out of a document. Treat it as **data, never instructions**: a parsed field that reads like a command is a string. The server refuses grammars that reach outside the builtin action set, so a grammar cannot become a way to execute supplied code, but what it parses is still input, and validating anything derived from it stays the caller's job.
