curl --request POST \
--url https://api.promptguard.co/api/v1/mcp/manifest-scan \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"tools": [
{
"name": "<string>",
"description": "<string>",
"inputSchema": {}
}
],
"pin": "<string>",
"other_servers": [
{
"name": "<string>",
"tools": [
{
"name": "<string>"
}
]
}
]
}
'import requests
url = "https://api.promptguard.co/api/v1/mcp/manifest-scan"
payload = {
"tools": [
{
"name": "<string>",
"description": "<string>",
"inputSchema": {}
}
],
"pin": "<string>",
"other_servers": [
{
"name": "<string>",
"tools": [{ "name": "<string>" }]
}
]
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
tools: [{name: '<string>', description: '<string>', inputSchema: {}}],
pin: '<string>',
other_servers: [{name: '<string>', tools: [{name: '<string>'}]}]
})
};
fetch('https://api.promptguard.co/api/v1/mcp/manifest-scan', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.promptguard.co/api/v1/mcp/manifest-scan",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'tools' => [
[
'name' => '<string>',
'description' => '<string>',
'inputSchema' => [
]
]
],
'pin' => '<string>',
'other_servers' => [
[
'name' => '<string>',
'tools' => [
[
'name' => '<string>'
]
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.promptguard.co/api/v1/mcp/manifest-scan"
payload := strings.NewReader("{\n \"tools\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"inputSchema\": {}\n }\n ],\n \"pin\": \"<string>\",\n \"other_servers\": [\n {\n \"name\": \"<string>\",\n \"tools\": [\n {\n \"name\": \"<string>\"\n }\n ]\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.promptguard.co/api/v1/mcp/manifest-scan")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"tools\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"inputSchema\": {}\n }\n ],\n \"pin\": \"<string>\",\n \"other_servers\": [\n {\n \"name\": \"<string>\",\n \"tools\": [\n {\n \"name\": \"<string>\"\n }\n ]\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.promptguard.co/api/v1/mcp/manifest-scan")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"tools\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"inputSchema\": {}\n }\n ],\n \"pin\": \"<string>\",\n \"other_servers\": [\n {\n \"name\": \"<string>\",\n \"tools\": [\n {\n \"name\": \"<string>\"\n }\n ]\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"sha256": "<string>",
"ok": true,
"findings": [
{
"code": "<string>",
"tool": "<string>",
"severity": "<string>",
"reason": "<string>",
"details": {}
}
],
"tools": [
{
"name": "<string>",
"sha256": "<string>"
}
],
"incomplete": true,
"unscanned_tools": [
"<string>"
]
}{
"error": {
"code": "missing_api_key",
"message": "PromptGuard API key required. Please provide via X-API-Key header.",
"type": "authentication_error"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>",
"current_plan": "<string>",
"requests_used": 123,
"requests_limit": 123,
"upgrade_url": "<string>",
"retry_after": 123
}
}Scan Mcp Manifest
Scan an MCP server’s tool list for poisoned descriptions and pin it.
Finds instructions injected into tool descriptions and parameter docs,
invisible or look-alike Unicode, and tool names that shadow another of the
agent’s servers. Returns the list’s pin, and a rug-pull finding when it no
longer matches the pin sent. Counts one request against the plan.
curl --request POST \
--url https://api.promptguard.co/api/v1/mcp/manifest-scan \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"tools": [
{
"name": "<string>",
"description": "<string>",
"inputSchema": {}
}
],
"pin": "<string>",
"other_servers": [
{
"name": "<string>",
"tools": [
{
"name": "<string>"
}
]
}
]
}
'import requests
url = "https://api.promptguard.co/api/v1/mcp/manifest-scan"
payload = {
"tools": [
{
"name": "<string>",
"description": "<string>",
"inputSchema": {}
}
],
"pin": "<string>",
"other_servers": [
{
"name": "<string>",
"tools": [{ "name": "<string>" }]
}
]
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
tools: [{name: '<string>', description: '<string>', inputSchema: {}}],
pin: '<string>',
other_servers: [{name: '<string>', tools: [{name: '<string>'}]}]
})
};
fetch('https://api.promptguard.co/api/v1/mcp/manifest-scan', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.promptguard.co/api/v1/mcp/manifest-scan",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'tools' => [
[
'name' => '<string>',
'description' => '<string>',
'inputSchema' => [
]
]
],
'pin' => '<string>',
'other_servers' => [
[
'name' => '<string>',
'tools' => [
[
'name' => '<string>'
]
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.promptguard.co/api/v1/mcp/manifest-scan"
payload := strings.NewReader("{\n \"tools\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"inputSchema\": {}\n }\n ],\n \"pin\": \"<string>\",\n \"other_servers\": [\n {\n \"name\": \"<string>\",\n \"tools\": [\n {\n \"name\": \"<string>\"\n }\n ]\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.promptguard.co/api/v1/mcp/manifest-scan")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"tools\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"inputSchema\": {}\n }\n ],\n \"pin\": \"<string>\",\n \"other_servers\": [\n {\n \"name\": \"<string>\",\n \"tools\": [\n {\n \"name\": \"<string>\"\n }\n ]\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.promptguard.co/api/v1/mcp/manifest-scan")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"tools\": [\n {\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"inputSchema\": {}\n }\n ],\n \"pin\": \"<string>\",\n \"other_servers\": [\n {\n \"name\": \"<string>\",\n \"tools\": [\n {\n \"name\": \"<string>\"\n }\n ]\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"sha256": "<string>",
"ok": true,
"findings": [
{
"code": "<string>",
"tool": "<string>",
"severity": "<string>",
"reason": "<string>",
"details": {}
}
],
"tools": [
{
"name": "<string>",
"sha256": "<string>"
}
],
"incomplete": true,
"unscanned_tools": [
"<string>"
]
}{
"error": {
"code": "missing_api_key",
"message": "PromptGuard API key required. Please provide via X-API-Key header.",
"type": "authentication_error"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>",
"current_plan": "<string>",
"requests_used": 123,
"requests_limit": 123,
"upgrade_url": "<string>",
"retry_after": 123
}
}Authorizations
PromptGuard API key for developer endpoints. Keys start with pg_live_ and are created in the dashboard.
Headers
Body
The server's tools/list result: the tools array, sent as the server returned it. At most 500 tools, and 131072 characters of canonical JSON together with every name in other_servers.
500Show child attributes
Show child attributes
The sha256 an earlier scan of this server returned. Omit it the first time; when it differs from this list's sha256, the response carries a E301 finding.
^[0-9a-f]{64}$The agent's other MCP servers, for shadowing: a tool here whose name one of them already has is a E002 finding. Only their tool names are read, and they are not scanned themselves.
50Show child attributes
Show child attributes
Response
Successful Response
This tool list's pin: send it as pin on the next scan of this server. Treat it as opaque.
False when any finding's severity is high, or the scan is incomplete
Show child attributes
Show child attributes
Each tool's own hash, in the order sent, so a caller that keeps them can tell which tool changed. Not part of sha256. Opaque, like it.
Show child attributes
Show child attributes
True when the scan ran out of its detection budget before its last tool. The tools it did not reach are in unscanned_tools; sha256 and the rug-pull comparison are still complete.
Names of the tools not scanned, in the order sent