mcp server
suss
Reads your code and checks what it does at every boundary, a route, a table or a queue, against the clients, specs and infrastructure on the other side. TypeScript, Python and Ruby.
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- Node.js 20 or newer
{
"mcpServers": {
"nimbuscloud-ai-suss": {
"command": "npx",
"args": [
"-y",
"@suss/mcp"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 14, 2026suss
suss reads your code and checks what it does at every boundary, such as a route or a table, against the clients, specs and infrastructure on the other side. It works on TypeScript, Python and Ruby.
Code is written faster than anyone can read it. Large pull requests land all day, and no reviewer can keep one of them in their head. A diff shows you which lines of text changed, and you still have to work out what the service does now. Tests written alongside the change only check what their author meant. suss reads the code and writes down what it does on every path, from the request or message that comes in to the table or queue it touches, so you can read that instead. There is no model in it, so the same code gives you the same description every time.
Read one service
npx @suss/cli inspect
inspect reads the project you run it in. It works out which packs the project needs from its dependencies, the same way suss init does, and prints the ones it picked. suss init also saves that choice to suss.json, so every later command uses the same packs.
src/api.ts
├─ GET /users/{id} (hono handler | line 5)
│ if !findUser()
│ -> 404 { error }
│ elif findUser().deletedAt
│ -> 410 { error }
│ else
│ -> 200 { id, name, email }
│
└─ POST /users (hono handler | line 19)
if !c.req.json().name
-> 400 "name is required"
else
-> 201 { id, name }
Each handler is listed with every path it can take, and each path shows the status and the body fields it produces. When suss cannot follow a call, it lists that call under the handler, so a path it could not read still shows up.
The same summaries are available to a coding agent over MCP, so it can ask what a route reaches or what writes a table before it edits either:
{
"mcpServers": {
"suss": { "command": "npx", "args": ["-y", "@suss/mcp", "/path/to/project"] }
}
}
suss init writes the suss.json the server reads. You can ask the same questions from the shell with suss ask 'what writes postgresql:Article'.
Read what a change did
inspect --diff compares two summary files. Run it on the base and the head of a pull request and it reports what the change did to each unit, whichever lines the diff touched:
1 boundary changed: 3 outcomes.
~ serves GET /users/{id} src/routes/users.ts::get (3 outcomes)
outcomes
+ responds 200 { id, status } when findUser() && findUser().deletedAt
- responds 410 { error } when findUser() && findUser().deletedAt
~ responds 200 { id, name, -email } otherwise
Changes by file
src/routes/users.ts
~ get
A deleted account used to get a 410 and now gets a 200 with status: "deleted", and email is no longer in the response. Both versions compile and the types still line up. Every caller that treats a 200 as a usable account is now wrong. On a large pull request, read this output first.
What you use it for
- A service you did not write, or one an agent changed an hour ago.
inspectprints what each handler, consumer and query does on every path, in a page, without reading the source. - A pull request too large to read.
inspect --diffon the base and the head tells you which units changed behavior and how. - An agent about to edit something. Over MCP it asks what a route reaches, what writes a table, or what calls a function, and gets file and line for each.
- A spec that drifted.
suss contractreads an OpenAPI document, a Prisma schema or a CloudFormation template into the same form, andcheckreports where the code and the document disagree. - A field you want to remove.
checkpairs every handler with the clients that call it and tells you which client reads the field, or never handles a status the handler produces.
Here is what a finding looks like, taken from the runnable example in this repo:
[WARNING] unhandledProviderCase
Provider produces status 404 but no consumer branch handles it
provider: openapi:openapi.json::getPetById (openapi:openapi.json:0)
consumer: src/petstore-client.ts::describePet (src/petstore-client.ts:66)
boundary: openapi (http) GET /api/v3/pet/{petId}
Adopting it
Each step takes a little more work than the one before, and needs a little more from the codebase. You can stop at any step that already gives you enough. The adoption guide gives the command for each step, what it reports, and what a false positive looks like at that step.
- Read one service with
extractandinspect. There is nothing to triage yet. - Question it with
suss askor the MCP server. - Compare it against a document you already keep with
suss contractandcheck.suss initsets this up. - Add the consumer side, so a finding points at the caller that breaks.
- Gate on it:
check --fail-on errorin CI,inspect --diffon every pull request. - Reuse the summaries as agent context, as endpoint docs, or as the list of paths a test suite should cover.
What it reads
TypeScript is the furthest along: Express, Fastify, Hono, NestJS, Next.js and ts-rest on the server, fetch, axios and Apollo on the client, Prisma, Drizzle, Mongoose, node-postgres and BigQuery for storage, Lambda handlers, and the AWS clients for SQS, SNS, EventBridge, DynamoDB and S3. In Python, suss reads FastAPI and flask-restx routes and SQLAlchemy queries. In Ruby, it reads Rails controllers, graphql-ruby schemas and ActiveRecord. The full list is in Packs below. One run reads one repository. To check across two repositories, you publish one side's summaries and hand them to the other side's run; see Work across services.
Install
suss ships as @suss/cli, with every pack inside it, so there is one install:
npm install --save-dev @suss/cli
You pick packs by name, as in suss extract -f ts-rest -f axios, and read a declared artifact with suss contract --from openapi. Without -f, extract reads the packs from suss.json, and when there is no such file it picks the ones init would have. A Python or Ruby project is read with --dir instead of a tsconfig; see Read Python or Ruby.
suss init reads your project, works out which packs it needs, writes them to suss.json, and offers to set them up:
┌ suss init
│
◇ Found ─────────────────────────────╮
│ │
│ hono hono in dependencies │
│ apollo-client @apollo/client in .. │
│ cloudformation a SAM template at.. │
│ │
├─────────────────────────────────────╯
│
◇ Install 4 packages as devDependencies?
│ ● Yes / ○ No
│
◇ Installed 4 packages
│
◇ Read the code now and compare what it finds?
│ ● Yes / ○ No
│
◇ suss extract -f hono -f apollo-client -o summaries/code.json
◇ suss contract --from cloudformation template.yaml -o summaries/cloudformation.json
│
◆ Add a .sussignore for findings you decide to accept?
│ ○ Yes / ● No
│
└ Done. Re-run `suss check --dir summaries/` whenever code changes.
Nothing is written or installed unless you say yes. When its output is piped, or when it runs in CI, it prints the commands instead of asking, so suss init --plain works in a script.
The four commands
All four work on the same BehavioralSummary[]:
suss extractderives summaries from source.suss contractderives summaries from a declared artifact: OpenAPI, CloudFormation and SAM, Serverless Framework, Terraform, AppSync, GraphQL SDL and committed.graphqloperation documents, Prisma schema, Storybook, Wrangler.suss checkpairs providers with consumers and emits findings where they disagree. It exits nonzero when a finding crosses the--fail-on error|warning|info|nonethreshold.suss inspectrenders summaries as text.--diff BEFORE AFTERreports what a change added, removed, or altered.--flow "GET https://..."shows who serves a request, hop by hop.
extract and contract produce the same format, so a TypeScript handler compares directly against the OpenAPI document for it, a CloudFormation template against the Lambda code it deploys, or a Storybook file against the React component it documents.
suss writes a BehavioralSummary for every function reachable from a recognized entry point. The summary lists the transitions the function produces, one per execution path. Each transition records the predicates that guard it, its output, and the side effects along the way. @suss/checker and any other downstream tool read that JSON, and inspect is one way of printing it.
A complete example
examples/petstore-axios-openapi/ pairs a TypeScript axios consumer against the Petstore OpenAPI spec. make all runs extract, contract and check over it.
Docs
The documentation site is at suss.sh.
- Quickstart: the smallest end-to-end example.
- Adopt it step by step: the steps above, one at a time.
- AGENTS.md: driving suss from a coding agent, and which docs answer what.
@suss/mcpputs the same questions in front of a model as MCP tools.npm install @suss/cliships the same file atnode_modules/@suss/cli/AGENTS.md. Runsuss askwith no question and it prints the questions it takes. - The problem: the problem, and why existing tools miss it.
- Compared to other tools: what your linter, types, OpenAPI, tests and tracing each tell you, and what suss adds.
- Glossary: one canonical definition per term.
- FAQ: what suss reads, what a boundary is, and what it leaves out.
- Kinds of contract: the kinds of contract, how much each one can tell you, and how that decides what a finding means. Intent docs your team writes get their own guide.
- Cross-boundary checking: how the pairwise checker works.
- Accept a finding: the
.sussignorefile format.
Reference and theory: Summary format, IR types, Architecture, What a pack is, Contract sources.
Packs
The behavioral summary format and the IR types in @suss/behavioral-ir are stable. The extraction pipeline and the cross-boundary checker are still in active development, and the set of packs keeps growing.
Fifty-six packs read code today. You pick them by name with -f:
| What it reads | Packs |
|---|---|
| HTTP frameworks, TypeScript | express fastify hono nextjs nestjs-rest ts-rest |
| HTTP frameworks, Python | fastapi flask-restx |
| HTTP frameworks, Ruby | rails |
| GraphQL servers | apollo nestjs-graphql graphql-ruby |
| Serverless and edge | aws-lambda cloudflare-workers |
| UI | react react-router react-query |
| HTTP and GraphQL clients | fetch axios apollo-client requests httpx aiohttp faraday net-http |
| Databases and ORMs | prisma drizzle mongoose typeorm pg pg-ruby sqlalchemy sqlmodel activerecord redis |
| Warehouses | bigquery bigquery-python bigquery-ruby |
| Client state | zustand |
| Object and key-value storage | aws-s3 gcs aws-dynamodb |
| Secrets and parameters | aws-secrets-manager aws-ssm |
| Messaging | aws-sqs aws-sns aws-eventbridge nestjs-microservices |
| Runtime surface | node, which includes process.env |
| In-process, between workspace packages | package-exports |
| Tests a PRD scenario lists as covering it | vitest pytest rspec minitest, with factory-bot and fabrication for what a Ruby test builds |
Ten contract readers turn a declared artifact into the same format, reached with --from: openapi, graphql (SDL), graphql-documents (committed .graphql operations), cloudformation (including SAM, and the API Gateway resources in it), serverless, appsync, storybook, prisma, terraform, wrangler. The intent docs your team writes are read separately, by suss check --intent.