Sign In

@polygraphso/mcp

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@polygraphso/mcp - npm Package Compare versions

Comparing version
0.2.0
to
0.3.0
+75
dist/tools/request.js
/**
* `request_grade` — add an ungraded MCP server to polygraph's public grading
* queue. The natural follow-up when `check_server` returns not_available.
*
* Tool description is LLM-facing — the agent reads it to decide when to
* invoke. Voice rules from brand-foundation.md apply: plain English, no
* empty intensifiers, no "AI safety" framing, no overclaim.
*
* No email: the caller is an agent, so we take no contact details. The MCP
* server passes the connected client's identity (agent_id) so the queue
* knows who asked, without prompting the user. The grade is fulfilled
* best-effort; read it later by calling `check_server` again.
*/
import { z } from "zod";
import { PolygraphApiError, postGradeRequest } from "../api.js";
export const REQUEST_TOOL_NAME = "request_grade";
export const REQUEST_TOOL_TITLE = "Request a polygraph grade for an MCP server";
export const REQUEST_TOOL_DESCRIPTION = [
"Add an MCP server to polygraph.so's public grading queue.",
"",
"Use this when `check_server` returns not_available and you want the server",
"graded. The request is free and best-effort — polygraph runs the litmus",
"test and publishes the grade, then you read it by calling `check_server`",
"again later. This does not return a grade synchronously.",
"",
"No contact details are needed. Requesting the same server twice is a no-op,",
"not a duplicate.",
"",
"Input: `server_ref` — the same registry-prefixed identifier `check_server`",
"takes (e.g. npm/@scope/name, pypi/name, github/owner/repo).",
"",
"Returns `{ status: 'queued', created, demand }` — `created` is false if it",
"was already queued, `demand` is how many requests stand behind it.",
].join("\n");
export const requestInputShape = {
server_ref: z
.string()
.min(1)
.max(512)
.describe("Registry-prefixed server identifier, e.g. 'npm/@modelcontextprotocol/server-filesystem', 'pypi/mcp-server-git', or 'github/anthropic/mcp-server-foo'."),
};
const requestInputSchema = z.object(requestInputShape);
/**
* @param agentId identity of the connected MCP client (name/version), passed
* through so the queue can attribute the request. Undefined when the client
* didn't announce itself.
*/
export async function handleRequestGrade(input, agentId) {
try {
const body = await postGradeRequest(input.server_ref, agentId);
const text = body.created
? `Queued ${input.server_ref} for grading (${body.demand} request(s) behind it). It'll be graded best-effort — call check_server again later to read the result.`
: `${input.server_ref} was already in the queue (${body.demand} request(s) behind it). Call check_server again later to read the result.`;
return {
content: [{ type: "text", text }],
structuredContent: body,
};
}
catch (err) {
return errorResult(err);
}
}
function errorResult(err) {
if (err instanceof PolygraphApiError) {
return {
content: [{ type: "text", text: err.message }],
isError: true,
};
}
const msg = err instanceof Error ? err.message : String(err);
return {
content: [{ type: "text", text: `polygraph.so request failed: ${msg}` }],
isError: true,
};
}
+39
-2

@@ -9,4 +9,5 @@ /**

* Endpoints (already shipped; see web/app/api/cli/):
* POST /api/cli/check → { server_ref } → graded | not_available
* GET /api/cli/list → { servers, total }
* POST /api/cli/check → { server_ref } → graded | not_available
* GET /api/cli/list → { servers, total }
* POST /api/cli/grade-request → { server_ref, source, agent_id? } → queued
*

@@ -73,2 +74,38 @@ * Network failures throw `PolygraphApiError` with a stable `kind` so the

}
export async function postGradeRequest(serverRef, agentId) {
const url = `${apiBaseUrl()}/api/cli/grade-request`;
const body = {
server_ref: serverRef,
source: "mcp",
};
if (agentId)
body.agent_id = agentId;
let res;
try {
res = await fetch(url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
throw new PolygraphApiError("network", `couldn't reach polygraph.so (${msg}).`);
}
// 400 → the ref was rejected; propagate the server's message.
if (res.status === 400) {
let errBody;
try {
errBody = (await res.json());
}
catch {
errBody = {};
}
throw new PolygraphApiError("http", errBody.error ?? "polygraph.so rejected the server_ref as malformed.", 400);
}
if (!res.ok) {
throw new PolygraphApiError("http", `polygraph.so returned ${res.status}.`, res.status);
}
return readJson(res);
}
export async function getList() {

@@ -75,0 +112,0 @@ const url = `${apiBaseUrl()}/api/cli/list`;

@@ -10,8 +10,11 @@ #!/usr/bin/env node

*
* Tools registered for v0.1.0:
* - check_server → POST /api/cli/check
* - list_servers → GET /api/cli/list
* Tools registered:
* - check_server → POST /api/cli/check (read)
* - list_servers → GET /api/cli/list (read)
* - request_grade → POST /api/cli/grade-request (write: queue an ungraded
* server; the natural follow-up to a not_available check)
*
* `notify_about` is deferred to v0.2 because POST /api/notify hasn't shipped
* yet — see packages/mcp/README.md "Roadmap".
* Email notification stays a web-only funnel (/notify) — an agent has no
* inbox, so request_grade takes no contact details and instead attributes
* the request to the connected client (agent_id).
*/

@@ -25,2 +28,14 @@ import { readFileSync } from "node:fs";

import { LIST_TOOL_DESCRIPTION, LIST_TOOL_NAME, LIST_TOOL_TITLE, handleList, } from "./tools/list.js";
import { REQUEST_TOOL_DESCRIPTION, REQUEST_TOOL_NAME, REQUEST_TOOL_TITLE, handleRequestGrade, requestInputShape, } from "./tools/request.js";
/**
* Identity of the connected MCP client (the "agent"), from the initialize
* handshake — e.g. "claude-ai/1.2.0". Undefined before initialize or when the
* client didn't announce itself. Used to attribute grade requests.
*/
function clientAgentId(server) {
const client = server.server.getClientVersion();
if (!client?.name)
return undefined;
return client.version ? `${client.name}/${client.version}` : client.name;
}
function readVersion() {

@@ -68,2 +83,14 @@ const here = dirname(fileURLToPath(import.meta.url));

}, handleList);
server.registerTool(REQUEST_TOOL_NAME, {
title: REQUEST_TOOL_TITLE,
description: REQUEST_TOOL_DESCRIPTION,
inputSchema: requestInputShape,
annotations: {
title: REQUEST_TOOL_TITLE,
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
}, (args) => handleRequestGrade(args, clientAgentId(server)));
return server;

@@ -70,0 +97,0 @@ }

+11
-8

@@ -28,12 +28,15 @@ /**

" github/<owner>/<repo> (e.g. github/anthropic/mcp-server-foo)",
"An optional `@<version>` suffix is accepted but ignored — the polygraph is",
"looked up by the versionless server identity.",
"An optional `@<version>` suffix looks up the grade for that EXACT version; a",
"bare ref returns the latest graded version. A grade for one version never",
"applies to another.",
"",
"Returns one of:",
" - graded: `polygraph` is the published grade ('A'|'B'|'D'|'F', no C), and",
" `polygraph_detail` carries the per-check results (C-01/C-02/C-03),",
" tool-surface fingerprint, and methodology version.",
" - not_available: this server hasn't been graded yet. The response includes",
" a notify URL the user can subscribe to. Treat this as 'no data' —",
" neither safe nor unsafe.",
" `polygraph_detail` carries the per-check results (C-01/C-02/C-03), the",
" tool-surface fingerprint, the methodology version, and `resolved_version`",
" (the version the grade was run against).",
" - not_available: this server (or the requested version) hasn't been graded",
" yet. Treat this as 'no data' — neither safe nor unsafe. The response's",
" `message` explains next steps: call `request_grade` to add it to the",
" public grading queue, or run the `self_grade` command to grade it now.",
].join("\n");

@@ -45,3 +48,3 @@ export const checkInputShape = {

.max(512)
.describe("Registry-prefixed server identifier, e.g. 'npm/@modelcontextprotocol/server-filesystem', 'pypi/mcp-server-git', or 'github/anthropic/mcp-server-foo'. Optional '@<version>' suffix is accepted but ignored."),
.describe("Registry-prefixed server identifier, e.g. 'npm/@modelcontextprotocol/server-filesystem', 'pypi/mcp-server-git', or 'github/anthropic/mcp-server-foo'. An optional '@<version>' suffix looks up that exact version; a bare ref returns the latest graded version."),
};

@@ -48,0 +51,0 @@ const checkInputSchema = z.object(checkInputShape);

{
"name": "@polygraphso/mcp",
"version": "0.2.0",
"version": "0.3.0",
"description": "MCP server for polygraph.so — independent, lab-evaluated trust grades for MCP servers. Lets AI agents check the polygraph of a server before recommending or installing it.",

@@ -30,9 +30,2 @@ "license": "Apache-2.0",

],
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"start": "node dist/index.js"
},
"engines": {

@@ -52,3 +45,9 @@ "node": ">=18"

"access": "public"
},
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"start": "node dist/index.js"
}
}
}
+28
-16

@@ -5,11 +5,10 @@ # @polygraphso/mcp

A polygraph is the result polygraph.so issues for an MCP server: an adoption tier (Top 10 / 25 / 50 / 100), and — once behavioral evaluation has run — an A–F grade plus an evidence URL. This package lets an agent check the polygraph for a server before recommending or installing it.
A polygraph is the behavioral trust grade polygraph.so issues for an MCP server: a letter grade (A/B/D/F) from an adversarial litmus test, backed by per-check results, a tool-surface fingerprint, and an evidence URL anyone can re-run. This package lets an agent check the polygraph for a server before recommending or installing it.
## Tools
- **`check_server`** — look up the polygraph for a specific MCP server (`server_ref` like `npm/@modelcontextprotocol/server-filesystem`).
- **`list_servers`** — enumerate every server polygraph tracks, tier-sorted.
- **`check_server`** — look up the published polygraph for a specific MCP server (`server_ref` like `npm/@modelcontextprotocol/server-filesystem`). An optional `@<version>` suffix looks up that exact version.
- **`list_servers`** — enumerate every server polygraph has graded, sorted by grade (A first).
- **`request_grade`** — add an ungraded server to polygraph's public grading queue. The natural follow-up when `check_server` returns `not_available`. Free and best-effort: polygraph runs the litmus test and publishes the grade, which you read later by calling `check_server` again. No contact details are asked of the agent.
`notify_about` (request a polygraph for an untracked server) lands in **v0.2** — the underlying endpoint isn't published yet.
## Install in Claude Desktop

@@ -70,18 +69,32 @@

`check_server({ server_ref: "npm/lodash" })`:
`check_server({ server_ref: "npm/@modelcontextprotocol/server-filesystem" })` — a graded server:
```json
{
"status": "tracked",
"adoption_tier": "top10",
"polygraph": null,
"notify_url": "https://polygraph.so/notify?for=npm/lodash"
"status": "graded",
"polygraph": "A",
"polygraph_detail": {
"methodology_version": "litmus-v11",
"resolved_version": "2.1.0",
"evidence_url": "https://polygraph.so/mcp/npm/@modelcontextprotocol/server-filesystem"
}
}
```
- `status` is `"tracked"` if polygraph is evaluating the server, `"not_available"` otherwise.
- `adoption_tier` is `top10` / `top25` / `top50` / `top100`, or `null` if the server is tracked but unranked.
- `polygraph` is `null` until the behavioral evaluation lands (v0 has the adoption side; the litmus harness is shipping next).
- `notify_url` is where a user can subscribe to be notified when the polygraph is published.
An ungraded server:
```json
{
"status": "not_available",
"notify_url": "https://polygraph.so/notify?for=npm/obscure-mcp-server",
"message": "No published polygraph for npm/obscure-mcp-server yet — treat it as unevaluated. Call request_grade to add it to the public queue, or grade it yourself with the self_grade command.",
"self_grade": "npx -y -p @polygraphso/litmus polygraphso-litmus litmus npm/obscure-mcp-server"
}
```
- `status` is `"graded"` when a published grade exists, `"not_available"` otherwise.
- `polygraph` is the published grade — `"A" | "B" | "D" | "F"` (no C).
- `polygraph_detail` carries the per-check results (C-01/C-02/C-03), the tool-surface fingerprint, the methodology version, and `resolved_version` (the version the grade was run against).
- On `not_available`: `message` explains the next steps, `self_grade` is a one-shot command to grade the server yourself, and `notify_url` is where a user can subscribe to be notified when the polygraph is published. Call `request_grade` to add the server to the public queue.
`list_servers()`:

@@ -94,4 +107,3 @@

"server_ref": "npm/@modelcontextprotocol/server-filesystem",
"adoption_tier": "top10",
"polygraph": null
"polygraph": "A"
}

@@ -98,0 +110,0 @@ ],