Look twice.Find the gem.

AI agents and MCP servers, each published with its source and what the checks found.

Marketplace

  • Everything
  • AI agents
  • Apps
  • MCP servers
  • Templates
  • What people want
  • What changed this week
  • The verification standard
  • The ooruby Index
  • Servers that publish no source
  • Reliability guides
  • What the catalogue holds
  • Sell here

Our library

  • Everything, in one place
  • Guides
  • Glossary
  • Calculators
  • Checklists and cheat sheets
  • Community

ooruby

  • Home
  • For teams
  • Site status
  • Company projects
  • RSS feed

Verification records what our published tests found on a specific version at a specific date. It is not a warranty, and it does not certify that software is free of defects.

Rubricv1.0
AI agentsAppsMCP serversTemplatesWantedCommunityOur library
Sign inSell
How verification works
All guides
How verification worksAdvanced· 14 min read

How to verify an MCP server yourself

The one thing to remember

Nearly everything that decides whether to install an MCP server can be read without running it, and what cannot be read belongs on a list of what was not checked.

The question

Check an MCP server you are about to install with the same checks this site publishes, by hand, reading its published package and never running it.

Figure

How to verify an MCP server yourself works, in one picture

1Pin one version, and fetch its tarball without installing it2Check that the file is the one the registry published3Unpack it, and read the scripts that run on install4Find the tool descriptions, and read each one against the pa...5Search for credential-shaped strings, and publish nothing yo...6Check the advisories for this version, and for what it pulls...

The same argument as the text, as a chain. Each step is what makes the next one possible.

  1. 1

    Pin one version, and fetch its tarball without installing it

    Everything below is a statement about one published version, so choose it first. The version the registry serves as latest today can be a different one tomorrow, and a check of a moving target checks nothing. The commands assume macOS or Linux with Node installed; anything in angle brackets is yours to fill in.

    npm pack, given a name and a version from the registry, fetches that version's published tarball into npm's cache and copies it into the current folder. It does not install it. The scripts npm lists for pack, prepack, prepare and postpack, belong to packing a folder, and install scripts belong to npm install. Add --ignore-scripts anyway: the pack documentation lists it, and it turns a reasonable expectation into a stated one.

    Commands
    mkdir mcp-check && cd mcp-check
    npm view <name> version
    npm pack <name>@<version> --ignore-scripts

    Sources: npm Docs: npm view; npm Docs: npm pack; npm Docs: scripts, life cycle operation order

    Pack only a name and version from the registry, never a folder or a git address. npm runs prepare when it packs a folder and when it installs from git, which is the maker's code running before you have read a line of it.

  2. 2

    Check that the file is the one the registry published

    The registry records an integrity string for every version: sha512, a hyphen, then the base64 SHA-512 digest of the tarball, in the Standard Subresource Integrity form npm's own lockfile uses. Compute the same digest of the file npm pack wrote, which is the name on its last line of output, and compare the two character for character.

    A match means you hold the bytes npm serves for that version, and it is the same form of digest this site records beside its npm scans. It does not say who built those bytes or from which source; that is what a signed provenance attestation adds, where a package publishes one.

    Commands
    npm view <name>@<version> dist.integrity
    openssl dgst -sha512 -binary <file>.tgz | openssl enc -base64 -A

    Sources: npm Docs: package-lock.json, the integrity field; npm Docs: npm view; OpenSSL: openssl-dgst; OpenSSL: openssl-enc, the -base64 and -A options

    The second command prints only the part after sha512-. If the two differ anywhere, stop: what you downloaded is not what the registry serves for that version.

  3. 3

    Unpack it, and read the scripts that run on install

    An npm tarball keeps everything under a folder called package. Unpacking it with tar writes files and runs nothing.

    Open package/package.json. Under scripts, preinstall, install and postinstall are what npm runs when anybody installs this, before a single tool is called; npm's documentation lists them in that order. A binding.gyp at the root, with no install or preinstall script of its own, means npm compiles native code on install, and that is code running too.

    Then read bin, which names the command npx runs, and main or exports, which is what another program loads when it imports the package. Those files are where the next steps look.

    Commands
    tar -xzf <file>.tgz
    ls -la package
    cat package/package.json
    ls package/binding.gyp

    Sources: npm Docs: scripts, life cycle operation order; npm Docs: package.json

    An install script that downloads something is downloading code you have not read. Read that too, or stop there.

  4. 4

    Find the tool descriptions, and read each one against the pattern set

    An MCP server describes each of its tools to the model, and the model reads those descriptions as instructions. The person running the agent never sees them. That is where tool poisoning lives: a sentence telling the model to read a key file, to keep a step from the user, or to use this tool in place of another one.

    Most servers write their descriptions as string literals, so a search finds them: server.tool and registerTool calls, and description fields. Open each match and read the whole description, including any parts joined with a plus sign on the lines below it.

    Read them against pattern set 1.0, the rules this site runs. Each rule shows a sentence it catches and the honest sentence nearest to it that it leaves alone, which is exactly the difference you are reading for. The second and third searches look for what a reader cannot see: characters that do not render, and the escape sequences that write them.

    If the first search finds nothing, you have not read the descriptions: minified code and descriptions built at run time hide from a text search. Write that down as not checked. The complete answer comes from asking a running server for its tool list, which means running it.

    Commands
    grep -rnE --include='*.js' --include='*.mjs' --include='*.cjs' '\.(tool|registerTool)\(|description:' package/
    find package -type f -exec perl -CSD -ne 'print "$ARGV:$.\n" if /[\x{200B}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{2069}\x{FEFF}\x{E0000}-\x{E007F}]/; close ARGV if eof' {} +
    grep -rniE '\\u(200[b-f]|202[a-e]|206[0-9]|feff)|\\u001b|\\x1b' package/

    Sources: Model Context Protocol specification: Tools; This site's tool description pattern set, v1.0; Invariant Labs: tool poisoning attacks; ooruby-scan, the command line scanner that runs these rules (not published yet); GNU grep manual

    Do not paste a suspicious description into a chat with a model to ask what it means. It was written to be obeyed by one.

  5. 5

    Search for credential-shaped strings, and publish nothing you find

    Several issuers give their keys a fixed prefix, which makes a real key findable by its shape: AWS access key IDs begin AKIA, GitHub tokens begin ghp_, gho_, ghu_, ghs_, ghr_ or github_pat_, Stripe live secret keys begin sk_live_, and a private key in a file begins with a BEGIN PRIVATE KEY line.

    Most matches are not leaks. Test values, documentation examples and other scanners' detection rules all have the shape. Read each one where it sits before calling it anything.

    A real one belongs to everybody who has installed that version, and removing it from the next release does not remove it from the ones already published. Tell the maker privately so they can revoke it. Do not post the value, the file or the line anywhere public, an issue included.

    Command
    grep -rnE 'AKIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{30,}|sk_live_[0-9A-Za-z]{20,}|BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY' package/

    Sources: AWS IAM User Guide: unique ID prefixes; GitHub Docs: GitHub's token formats; Stripe Docs: API keys; RFC 7468: textual encodings, the PRIVATE KEY label; GNU grep manual

  6. 6

    Check the advisories for this version, and for what it pulls in

    OSV, the open vulnerability database, answers for an exact version of an npm package. Ask it about the package itself first.

    Most of what runs is somebody else's code, so ask about the dependency tree as well. In a fresh folder, npm can resolve the tree into a lockfile without installing anything, and npm audit reads the lockfile.

    An advisory says a version has a published defect. It does not say whether this server ever reaches the vulnerable code, which is the question to ask next.

    Commands
    curl -d '{"package": {"name": "<name>", "ecosystem": "npm"}, "version": "<version>"}' https://api.osv.dev/v1/query
    mkdir ../deps && cd ../deps && npm init -y
    npm install <name>@<version> --package-lock-only --ignore-scripts
    npm audit --package-lock-only

    Sources: OSV: POST /v1/query; npm Docs: npm install, package-lock-only and ignore-scripts; npm Docs: npm audit

    Type both flags. Without --package-lock-only, npm install downloads and unpacks the whole tree into node_modules, which is exactly what this guide avoids.

  7. 7

    Check that the licence is really there

    The license field in package.json should be an SPDX identifier such as MIT or Apache-2.0, and the tarball should carry the licence text itself. A field that says SEE LICENSE IN a file means that file has to be in the tarball, so check that it is.

    No licence at all means nobody has been given permission to use, copy or change the code, however public it is.

    Commands
    grep -n '"license"' package/package.json
    ls package | grep -iE '^(licen[cs]e|copying)'

    Sources: npm Docs: package.json; SPDX License List; Choose a License: no licence

  8. 8

    List where it sends data

    Every address written in the code is a place it might send something. List them, then compare the list with what the README or the listing says the server contacts. An unexplained host is a question for the maker, and so is an expected one that is missing.

    Then find the calls that make requests, and read what goes into them: which arguments, which files, which environment variables.

    A host written in the code is not proof the server calls it, and a host missing from the code is not proof it does not, because an address can be built at run time. This reads what was written down, and says so.

    Commands
    grep -rhoE 'https?://[A-Za-z0-9.-]+' package/ | sort | uniq -c | sort -rn
    grep -rnE --include='*.js' --include='*.mjs' --include='*.cjs' 'fetch\(|axios|https?\.request\(|new WebSocket\(' package/

    Source: GNU grep manual

  9. 9

    For a remote server, check that it refuses you without credentials

    A server you connect to over HTTP runs code you cannot read, so the check that remains is whether it lets strangers in. The protocol's authorization rules say a server that requires authorization answers a request with no token with 401 Unauthorized and a WWW-Authenticate header naming where its protected resource metadata lives.

    Send one initialize request with no credentials, as a client would, and read the status and the headers. Then fetch the metadata address the header names, and check that it names an authorization server.

    If it answers 200 and goes on to list its tools for anybody, it is open. That can be right for a server that only reads public data, and it is never right for one that can act on an account. Do not call a tool to find out more, and send requests only to the endpoint the maker publishes.

    Commands
    curl -si -X POST <endpoint> -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual-check","version":"1.0.0"}}}'
    curl -s <the resource_metadata address from the WWW-Authenticate header>

    Sources: Model Context Protocol specification: Authorization; Model Context Protocol specification: Transports; RFC 9728: OAuth 2.0 Protected Resource Metadata

    Authorization is optional in the specification, so a server without it is not breaking a rule. It is telling you that anybody who finds the address can use it.

  10. 10

    Write down what you did not check

    None of the above ran the server, so none of it says how the server behaves: what it reads when a tool is called, what it sends, what it does with a URL it is handed. The SSRF guide covers the one behavioural test worth running yourself, locally, before anything real is connected.

    For a remote server, the code you read, if there was any, is not proof of what the endpoint runs. And a repository's history, where a deleted key still lives, is not in the tarball at all.

    Keep that list beside the result. A check that says what it did not cover can be relied on for what it did.

You have got it when

You have one version pinned with a matching digest, its install scripts and tool descriptions read, the hosts it names, the advisories for it and its tree, its licence, and for a remote server the status it gives without credentials, with what you did not check written beside them.

Read next

Before you buy
How to check an MCP server for SSRF yourself
How verification works
What a Verified badge actually means
How verification works
What we do not check
Glossary
Tool poisoning
Glossary
Lifecycle script
Glossary
Artifact digest
The bottom line

Nearly everything that decides whether to install an MCP server can be read without running it, and what cannot be read belongs on a list of what was not checked.

See the 30 live screens

Every one shows its exact method, and the circumstances in which it is wrong. Free, and no account to look.