namestack-uspto
by ricky9w
README.md
# namestack-uspto
English | [简体中文](README.zh.md)
Screen a brand name against US federal trademarks before you commit to it. Read-only: it finds identical and similar marks, and never files, opposes, or registers anything.
One command serves both audiences over the same query engine: a CLI for people, and `namestack-uspto mcp`, an MCP server for agents.
Results are a knockout screen, not a clearance opinion. They cover federal US trademarks only, and say nothing about state, common-law, or non-US rights.
## Quick start
### Installation
Requires Node.js 22.18 or newer. There is no account, API key, or login.
```sh
npm install --global @namestack/uspto # installs namestack-uspto
npx -y @namestack/uspto check --name=yourbrand # or run it without installing
```
To build and install from a clone instead:
```sh
bun install --frozen-lockfile
bun run build
npm pack
npm install --global ./namestack-uspto-*.tgz
```
The installed command needs Node, not Bun. Bun is only used to build.
### Usage
Confirm live access, find the class your goods or services fall in, then screen the name against it:
```sh
namestack-uspto doctor --online
namestack-uspto classes --class=025
namestack-uspto check --name=aurora --classes=025 --related --limit=3
```
```text
aurora
identical 34 contains 168 spelling 85 fuzzy 43 sound 476
resultId 6cb6299e-fd40-4c29-8408-4f2b33a4fe9c (fetched 2026-09-18T08:12:13.572Z)
Read a tier: namestack-uspto results --id=6cb6299e-fd40-4c29-8408-4f2b33a4fe9c --tier=sound
identical
AURORA live reg IC 025 same #85952176 2013-06-06 The Aurora Project, LLC (LI…
AURORA live pend IC 025 same #99825705 2026-05-15 AURORA FASHION INCORPORATED…
AURORA live reg IC 009,042 coordinated #78192660 2002-12-09 EPIS, Inc. (CORPORATION; OR…
```
The second line counts matches per tier. The marks below are the strongest `--limit` of them, grouped by tier. `--related` widens class 025 to the classes USPTO coordinates with it, which is why a mark in class 042 appears as `coordinated`. `doctor --online` runs one small search; without `--online` it only reports local setup.
Screen up to ten candidates in one command. Each name costs one upstream request, unless the same query ran in the last 24 hours:
```sh
namestack-uspto check --name=aurora,northwind,lumenpath --classes=009,042 --related
```
To query from Claude Code or another MCP host instead, see [MCP server](#mcp-server).
## Command reference
Put the complete command path first, then its options. Every command supports `--help`.
| Command | What it does | How to use it |
| --- | --- | --- |
| `check` | Screens 1–10 names for identical and similar marks, grouped by match tier. | `--name=aurora,northwind --classes=009,042 --related`. `--refresh` bypasses the cache. |
| `results` | Pages one tier of a saved screening snapshot, without network access. | `--id=RESULT_ID --tier=sound`, then pass the returned cursor as `--cursor="NEXT"`. |
| `search` | Runs a USPTO field tag query, for refining a screen. | `--query="CM:aurora AND IC:025" --sort=filed`, then `--cursor="NEXT"`. |
| `mark` | Shows one record by number. | `--serial=85952176` or `--registration=NUMBER`. `--history` adds prosecution history and maintenance deadlines. |
| `classes` | Lists the 45 international classes and US classes `A`, `B`, and `200`, with their coordinated classes. | Offline. `--class=025` shows one. |
| `doctor` | Reports the version, platform, cache directory, and cache settings. | `--online` runs one small search to verify live access. |
| `schema` | Prints JSON Schema for every input and result. | Offline. |
| `mcp` | Serves the query tools to an MCP host over stdio. | The host starts it; see [MCP server](#mcp-server). |
`check` and `search` share three filters. `--classes` takes up to 48 comma-separated classes, each three digits or `A` or `B`, and defaults to every class. `--related` also matches the classes USPTO coordinates with them, and requires `--classes`. `--status` is `live` by default, or `all` to include dead records.
`--limit` takes 1–100 and defaults to 25: marks per name for `check`, records per page for `search` and `results`. A `check` name is 1–80 characters. A `search` query is up to 1,000 characters, sorts by `relevance`, `filed`, `registered`, `mark`, or `serial`, and cannot page past 10,000 records. `mark` takes a 7–8 digit serial number or a registration number of up to 8 digits.
### Common options and defaults
| Option | What it controls |
| --- | --- |
| `--format=auto\|json\|human` | `auto` prints human output to a terminal outside CI, and JSON otherwise. |
| `--no-input` | Disables the progress spinner. The tool never prompts, and JSON mode never shows a spinner. |
| `--quiet`, `--no-color` | Suppress progress, disable color. A nonempty `NO_COLOR` and `TERM=dumb` also disable color. |
| `--timeout=SECONDS` | Total deadline, 1–600, default 60. Screening paces its own requests, so it needs more than one query does. |
| `--env-file=PATH` | Loads a dotenv file for this call. Existing environment values win. |
Exit codes: `0` success, including names with no matches; `1` operational failure, including `PARTIAL_FAILURE`; `2` invalid usage, configuration, or query; `124` timeout; `130` and `143` interrupted.
## Read the results
Each mark carries `matchedBy`, naming the tier it matched. Tiers are exclusive and run strongest first, so a mark appears once, under the strongest tier that found it:
| Tier | Matches |
| --- | --- |
| `identical` | The same letters, ignoring case, spacing, and punctuation: `AURORA`, `AU-RORA`, `AURORA!`. |
| `contains` | The name inside a longer mark or its pseudo mark: `LA AURORA`. |
| `spelling` | Alternate spellings of the same sounds: `ARORA`, `ORORA`. |
| `fuzzy` | Near spellings within a small edit distance: `AUROVA`. For a multi-word name, any one word within that distance. |
| `sound` | Similar pronunciation, from USPTO's phonetic index. Matches broadly. |
| `meaning` | The name in a mark's translation statement. |
`identical`, `contains`, `fuzzy`, `sound`, and `meaning` use USPTO index features as documented. `spelling` is this tool's own composition of the sound-alike regex pieces USPTO publishes; USPTO publishes the pieces, not this procedure, and its precision has not been measured.
- `live` and `classRelation` matter most together. A live mark in the `same` or a `coordinated` class is the conflict worth acting on; a dead mark is history, not an obstacle.
- Within a tier, marks rank live first, then by class relation (`same`, `coordinated`, `other`), then registrations before pending applications, then by earliest filing, since earlier rights are senior, and finally by serial number. A tier's sample is the top of this ranking over the whole tier, so a truncated tier leaves out only lower-ranked marks, and repeating a query against the same index returns the same sample.
- Read each `tiers[]` entry's `status` before its count. `searched` carries the tier's upstream `total`, the marks `returned` in this report, and `truncated: true` when the tier holds more. `skipped` carries `total: null` and a `reason`; it does not mean zero matches.
- Names longer than 40 characters or 6 words after normalization skip `identical`, `contains`, and `spelling` to bound query cost. Shorten the name or refine with `search` when those tiers matter.
- `pseudoMarks` are USPTO's alternate spellings of a mark, so a hit there is a real signal even when the printed text differs. `classes` flags each class cancelled for that mark; a cancelled class does not count toward `classRelation`.
- `mark --history` adds `history` from USPTO's case-detail service. If that second request fails, the record still returns, with `historyError` beside `history: null`.
- `search` returns `total`, `count`, `cursor`, and `truncated`. `cursor: null` ends the pages. Every sort ends on the serial number, so tied records keep their order from page to page.
- Every result carries `operation`, `source`, `checkedAt`, `notice`, and `authoritative: false`. `PARTIAL_FAILURE` keeps the names that succeeded; a failed name carries its own `error` and empty `tiers` and `marks`, never invented counts.
The envelope stays at `schemaVersion: 1`. Operation contracts follow the package version; `schema` prints them.
### Errors
| Code | Meaning |
| --- | --- |
| `RATE_LIMITED` | The request budget is spent, or USPTO throttled the source. Wait `retryAfterSeconds`; do not retry in a loop. |
| `UPSTREAM_BLOCKED` | USPTO answered with a bot challenge or refused the request. Use [tmsearch.uspto.gov](https://tmsearch.uspto.gov) directly. |
| `UPSTREAM_INCOMPLETE` | USPTO timed out or lost search shards, even though HTTP succeeded. The partial result is discarded; narrow the query or retry later. |
| `UPSTREAM_CHANGED` | The response no longer matches [docs/tmsearch.md](docs/tmsearch.md). |
| `UPSTREAM_ERROR`, `NETWORK_ERROR`, `TIMEOUT` | The request failed in transit. `retryable` says whether a later retry can help. |
| `INVALID_QUERY` | USPTO or the local guard rejected the field tag query. Field tags and operators must be uppercase. |
| `RESULT_WINDOW_EXCEEDED` | A `search` page would read past 10,000 records. Narrow the query. |
| `NOT_FOUND` | No record carries that serial or registration number. |
| `RESULT_EXPIRED`, `RESULT_NOT_FOUND` | The snapshot is past retention or gone. Run `check` again for a new ID. |
| `CACHE_INVALID`, `CACHE_UNAVAILABLE`, `CACHE_BUSY` | Local cache data, storage, or locking failed. `check` still returns fresh data with `cacheError`; `results` fails. |
| `INVALID_USAGE`, `CONFIG_INVALID` | Arguments, environment, or cache settings are invalid. Nothing was sent. |
## Saved results
`check` saves each name's fetched tier samples as a snapshot, then trims the report to `--limit`. A saved name carries a `resultId` and `cache` metadata: `hit`, `fetchedAt`, `freshUntil`, `retainUntil`, and `stale`.
- **Reuse.** The same query within the reuse TTL, 24 hours by default, returns the snapshot without contacting USPTO. `--refresh` fetches a new one; older IDs stay readable. A batch's `checkedAt` is the oldest `fetchedAt` among its names.
- **Continuation.** `results --id=RESULT_ID --tier=sound` pages one tier of the snapshot offline. The cursor belongs to that snapshot and tier. Each tier saves its highest-ranked `max(3 × limit, 30)` records, capped at 100, which is 75 at the default limit. `cachedCount` counts that sample and `total` the upstream matches, so `cursor: null` with `truncated: true` means more matches exist than were saved. Reach them with a narrower `check` or a `search`.
- **Retention.** A snapshot stays readable for 7 days from `fetchedAt`, marked `stale: true` once past its TTL. Reading it extends neither deadline. After retention, `results` fails with `RESULT_EXPIRED` instead of switching to newer data.
| Setting | Default | Override |
| --- | --- | --- |
| Reuse TTL | 86400 seconds (24 hours) | `NAMESTACK_USPTO_CACHE_TTL_SECONDS`, or `check --cache-ttl=SECONDS`. |
| Retention | 604800 seconds (7 days) | `NAMESTACK_USPTO_CACHE_RETENTION_SECONDS`, or `check --cache-retention=SECONDS`. |
Both take decimal seconds. Retention must be positive and at least the TTL; a TTL of `0` disables reuse but still saves snapshots for `results`. Deadlines are fixed when a snapshot is created: raising a setting does not extend existing snapshots, while lowering the TTL stops reusing them sooner. Invalid values fail with `CONFIG_INVALID` before any request.
The cache key is the tool version plus the canonical upstream request. Output options do not affect it; `--limit` can, because it sets the sample depth. Class filters are normalized and deduplicated first. Simultaneous identical misses in one process share one fetch; separate processes share saved snapshots but not in-flight requests.
Only complete, successful screens are saved, genuine zero matches included; `search` and `mark` always query USPTO. When saving fails, the fresh result still returns with `cacheError`, and a name without a `resultId` cannot be continued. Corrupt cache data fails with `CACHE_INVALID` instead of being reset.
### Where results are stored
| Path | What it holds |
| --- | --- |
| `~/.cache/namestack/uspto/queries/` | Snapshots, their index, and a recovery journal for interrupted writes. |
| `~/.cache/namestack/uspto/cooldown.json` | The shared cooldown after an upstream block; see [Request budget](#request-budget). |
`XDG_CACHE_HOME` moves the whole tree, `NAMESTACK_CACHE_DIR` replaces the `namestack` level, and `NAMESTACK_USPTO_CACHE_DIR` points this tool at an absolute path.
There is no size cap and no background sweep. After a `check` or `results`, expired snapshots are deleted once the earliest retention deadline in the index has passed; an idle cache is never cleaned, but expired IDs stay unreadable. Writes are atomic and made under a cross-process lock; waiting more than two seconds for it fails with `CACHE_BUSY`.
## Where the data comes from
USPTO publishes no API for searching trademarks by name. This tool queries the interface behind [tmsearch.uspto.gov](https://tmsearch.uspto.gov), which is undocumented and carries no availability promise; it can change or stop working without notice. What the tool relies on about that interface is recorded in [docs/tmsearch.md](docs/tmsearch.md). `mark --history` reads the case-detail endpoint on the same host.
The tool identifies itself as `namestack-uspto/<version>` in its `User-Agent`, sends no credentials or cookies, and never impersonates a browser or answers a bot challenge. If the interface starts requiring one, queries fail with `UPSTREAM_BLOCKED`.
### Request budget
USPTO blocks a source that queries too often, so the tool paces itself:
- At least 2 seconds between upstream requests, and at most 40 in any 5 minutes, per process. A long-lived MCP server shares one budget across all its tool calls.
- When the next free slot is more than 60 seconds away, the call fails with `RATE_LIMITED` instead of waiting.
- After an upstream block, a cooldown is written to `cooldown.json`: the server's `Retry-After`, or 15 minutes. Other invocations on the machine back off too, and queued requests recheck it before sending. The marker is advisory; an unwritable cache directory does not stop the tool.
- Each attempt times out after 20 seconds, or 10 for history. Transient failures are retried at most twice within the total deadline.
A `check` costs one request per uncached name, `mark` one, and `mark --history` two. Cache hits, `results`, and `classes` cost nothing. `--refresh` still obeys the budget.
## Use with AI agents and scripts
### Scripts
Data commands write exactly one newline-terminated envelope to stdout, with diagnostics on stderr:
```json
{"schemaVersion":1,"ok":true,"data":{}}
{"schemaVersion":1,"ok":false,"error":{"code":"INVALID_QUERY","message":"Logical operators must be uppercase; a lowercase and, or, or not is searched as a word.","retryable":false,"hint":"Field tags and logical operators must be uppercase, for example CM:aurora AND LD:true."}}
```
Select JSON and disable interaction explicitly:
```sh
namestack-uspto check --name=yourbrand --classes=042 --format=json --no-input
namestack-uspto schema --format=json
```
### Agent skill
The bundled [skill](skills/namestack-uspto/SKILL.md) tells an agent how to choose classes, bound queries, read the tiers, and stop on rate limits, with field tag syntax in [references/query-syntax.md](skills/namestack-uspto/references/query-syntax.md). Install the CLI first; the skill does not install the executable.
### MCP server
`namestack-uspto mcp` serves the queries to an MCP host over stdio. The host starts and stops the process; you do not run it yourself. There are no credentials to configure.
#### Set up in Claude Code
1. Register the server. `--scope user` makes it available in every project:
```sh
claude mcp add namestack-uspto --scope user -- namestack-uspto mcp
```
2. Run `claude mcp list` and look for `✔ Connected`. In a session, `/mcp` lists the five tools. Add the rule `mcp__namestack-uspto` with `/permissions` to allow all five without prompts.
Other hosts take the same command in their JSON configuration. If a host cannot find the command, use the absolute path from `command -v namestack-uspto`.
```json
{
"mcpServers": {
"namestack-uspto": { "command": "namestack-uspto", "args": ["mcp"] }
}
}
```
For a team, `--scope project` writes a `.mcp.json` to commit. `npx` spares each member a global install:
```json
{
"mcpServers": {
"namestack-uspto": { "command": "npx", "args": ["-y", "@namestack/uspto", "mcp"] }
}
}
```
#### Launch command
| Command | Behavior |
| --- | --- |
| `namestack-uspto mcp` | Runs the global install and never contacts npm at startup. Upgrade with `npm install --global @namestack/uspto@latest`. |
| `npx -y @namestack/uspto mcp` | Needs no install and picks up new releases, because npx checks the registry on every start. Offline, npx retries for over a minute before using its cache, which exceeds Claude Code's 30-second startup limit. |
| `npx -y --prefer-offline @namestack/uspto mcp` | Skips the registry check, so it starts offline but does not look for new releases. |
The first npx start downloads the package. If that is too slow, raise the limit with `MCP_TIMEOUT=60000 claude`.
#### Cache settings
MCP tools take no cache options. The server reads `NAMESTACK_USPTO_CACHE_TTL_SECONDS` and `NAMESTACK_USPTO_CACHE_RETENTION_SECONDS` on every `uspto_check` call, and the cache directory variables once at startup. A host does not necessarily pass your shell environment to the server, so set them explicitly:
```sh
claude mcp add namestack-uspto --scope user \
-e NAMESTACK_USPTO_CACHE_TTL_SECONDS=3600 \
-- namestack-uspto mcp
```
#### Tools
| Tool | Input |
| --- | --- |
| `uspto_check` | Takes `names` (1–10), with optional `classes`, `related`, `status`, `limit` (1–100, default 25), and `refresh`. |
| `uspto_results` | Takes `resultId` and `tier`, with optional `limit` (1–100, default 25) and `cursor`. Reads the saved sample offline. |
| `uspto_search` | Takes `query`, with optional `classes`, `related`, `status`, `sort`, `limit`, and `cursor`. |
| `uspto_mark` | Takes `serialNumber` or `registrationNumber`, with optional `history`. |
| `uspto_classes` | Takes an optional `class`. Answers from bundled data, offline. |
All five are annotated read-only, non-destructive, and idempotent; `uspto_results` and `uspto_classes` also declare `openWorldHint: false`, because they never leave the machine. Results arrive as `structuredContent` carrying the envelope shown under [Scripts](#scripts), with the same envelope as compact JSON text. An operational failure returns `isError` with that envelope and leaves the server running.
## Known limits
- The upstream index trails the register by about a day, and `authoritative` is always `false`. Confirm anything that matters on [TSDR](https://tsdr.uspto.gov).
- `spelling` is this tool's own heuristic, with unmeasured precision. `sound` is USPTO's index and deliberately broad.
- Coordinated classes are USPTO's own grouping and do not capture every related good or service.
- A few pre-1950s records carry a US class where an international class belongs, so their `classRelation` can mislead. They are almost always dead, and `--status=live` excludes them.
- Design-only marks have no text to match. Find them with `search` by design code (`DC`).
- `search` cannot page past 10,000 records, which is the upstream limit.
## Development
```sh
bun install --frozen-lockfile
bun run dev -- check --name=aurora --classes=025 # run the CLI from source
bun run typecheck # tsc --noEmit
bun run lint # biome
bun run test # node:test, offline
bun run build # tsdown, emits dist/cli.mjs
```
Responsibilities are split by directory: `core/` owns screening, search, the snapshot cache, and the result contracts; `providers/` owns Trademark Search and case-detail requests, request pacing, and response normalization; `cli/` and `mcp/` adapt the same core operations to their transports. Keep terminal output and process exits out of `core/`, and contain upstream format changes inside `providers/` and [docs/tmsearch.md](docs/tmsearch.md).
Tests use fixtures and never contact USPTO; fixtures carry synthetic owner and attorney names. CI runs the checks on Linux and macOS under Node 22.18.0 and 24, then installs the packed tarball in a clean directory with install scripts disabled and exercises the CLI and the MCP handshake. To repeat that locally, build, run `npm pack`, and pass the tarball's absolute path to `node scripts/check-package.mjs`.
### Releasing
Pushing a `v*` tag runs [release.yml](.github/workflows/release.yml). It checks that the tag matches `package.json`, repeats the checks and the tarball test, and publishes that same tarball to npm with provenance. It authenticates through npm trusted publishing over OpenID Connect (OIDC), with no stored npm token.
1. Set an unpublished version in `package.json` and in the skill's `metadata.version`, push to `main`, and wait for CI.
2. Tag the tested commit and push the tag, for example `git tag v0.2.2 && git push origin v0.2.2`.
3. Confirm the workflow succeeded and npm shows the version with provenance.
Never reuse a published version or move a release tag. The npm trusted publisher must match user `ricky9w`, repository `namestack-uspto`, workflow `release.yml`, and no environment; update it if the repository or workflow is renamed.
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues