@shieldlabs-ai/mcp
OfficialThe provided schema exposes a read-only ShieldLabs MCP server for identification lookup, History search and aggregation, risk-score explanation, webhook signature verification, and time checks.
Read one identification by
request_id, returning risk score/band, weighted signals, detection flags, visitor/device/session/cookie/user IDs, IP/country, connection/OS/browser/device/traffic-source details, with optional ~10s polling.Search History by
request_id,user_hid,device_id,visitor_id, IPv4ip,session_id, orcookie_id, newest first and paged.Summarize recent identifications for a user, device, visitor, IP, or cookie, including distinct entities, countries, IPs, connection types, risk stats, flags, and signals.
Explain a Risk Score from a
request_idor a pasted identification/webhook/history row, with band meaning and signal-catalog context.Verify ShieldLabs webhook signatures against raw payload bytes, including multiple rotating secrets and mismatch hints.
Get current UTC time, optionally in an IANA timezone, for freshness checks.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@shieldlabs-ai/mcpCan you review the ShieldLabs request 02f1d973-4e5a-6b7c-8d9e-0f1a2b3c4d5e?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ShieldLabs MCP server
Identification, risk signals and risk scoring through hosted OAuth MCP or a local server.
How it fits
The browser SDK returns an identification ID. Your backend or AI assistant reads the result through the public History API, or receives a signed webhook. A UUID by itself does not prove that ingest succeeded.
Hosted OAuth offers 21 tools for domain setup, webhook management and History reads. Local stdio and single-tenant API-key HTTP mode remain read-only and expose seven tools when both API credentials are configured. The hosted operations reference describes every operation.
Related MCP server: verdex
Install
For guided SDK and MCP setup, give your coding assistant the installation guide. Focused skills are available in ShieldLabs-ai/skills.
Use an MCP client with a remote streamable HTTP server and OAuth:
https://mcp.shieldlabs.ai/mcpSign in or register in the browser and approve the connection. If the client does not load new tools automatically, restart that client and continue the same conversation. Keep OAuth approval in the browser that started it.
For local stdio:
{
"mcpServers": {
"shieldlabs": {
"command": "npx",
"args": ["-y", "@shieldlabs-ai/mcp"],
"env": { "SHIELDLABS_API_KEY": "sec_your_server_key" }
}
}
}Quick start
Check the hosted connection and call
shieldlabs_list_domains.Use the selected domain's public UUID for every History read. A hostname is not a History selector.
Capture an actual browser identification ID. Read it with
shieldlabs_get_identification, usingdomain,identification_idand optionallywait:true.Read the returned card's
id,domain_id,risk.state,risk.scoreandsignals.state. An unavailable risk remains unknown; it is never substituted with Score 0.
{"domain":"11111111-1111-4111-8111-111111111111","identification_id":"33333333-3333-4333-8333-333333333333","wait":true,"response_format":"json"}Guide
Review one identification
shieldlabs_get_identification returns {found:true, identification:card}. Cards use type:"identification", id and nested risk, not the legacy row format. wait:true retries reads of the same ID within a ten-second total deadline. It never starts another browser identification. Retry-After and cancellation are respected.
shieldlabs_explain_risk_score accepts a domain UUID plus identification_id, or a pasted identification. It preserves unknown risk and says when detailed signal weights are unavailable. It also accepts scored webhook payloads for offline explanation. Missing weights do not establish that nothing contributed to a nonzero score.
List History
shieldlabs_list_history lists identifications or user in a domain. It does not require a card ID:
{"domain":"11111111-1111-4111-8111-111111111111","resource":"identifications","limit":20,"response_format":"json"}The result contains data and next_page_token. Preserve the same resource, window and flat filters when passing next_page_token as page_token. Tokens are opaque. The server can invalidate them after a restart; do not silently merge a fresh traversal into an earlier snapshot.
Optional from and to are paired RFC3339 timestamps. The window includes from and excludes to. Filters are flat fields: risk_bands, hre_min_accounts (3-99), hre_min_devices (4-99), ids as one User HID string on the user list, risk_signals, source, campaign and boolean flags such as is_vpn. Nested filter, chips, offset, include_total and sort are rejected.
{"domain":"11111111-1111-4111-8111-111111111111","resource":"identifications","risk_bands":["dangerous"],"is_vpn":true,"from":"2026-10-27T00:00:00Z","to":"2026-10-28T00:00:00Z"}Visitor, device and IP resources have cards, not lists.
Read one History card
shieldlabs_get_history reads one identification, user, visitor, device or ip card:
{"domain":"11111111-1111-4111-8111-111111111111","resource":"user","resource_id":"exact-user-hid","response_format":"json"}The result is {resource, entity}. It preserves the API's identification count, risk, related counts and High-Risk Events availability. One card is not one identification, and it does not enumerate the underlying history. Card reads also accept device_id_source. Related-account/device traversal is unavailable; relation, relation_limit and relation_page_token are not accepted.
Webhooks and setup
Hosted tools can create and verify a receiver, send a signed test, read delivery status, manage the stored multiaccount setting and rotate credentials with confirmation. All enabled endpoints with a public HTTPS URL receive group events; the stored multiaccount setting does not gate delivery. A sample test does not prove real delivery. HRE confidence is separate from Risk Score; unknown metadata does not imply a negative result.
Use integrate_shieldlabs to inspect the project and choose installation stages. investigate_user and review_request require an explicit domain UUID and only mention enabled tools. Server-side integrations use the Node SDK's current entities client. Keep the public browser key separate from server credentials.
Reference
Local configuration
Variable | Purpose |
| Server-only Private API Key for entity History |
| Public History origin, default |
| Optional Management API credential |
| Registered hostname for the optional Management profile, separate from the History UUID |
| Optional Management origin |
| Optional secret for offline signature verification |
| Bearer protection for local HTTP mode |
History requests require domain in tool input. Browser code must never receive the server key or webhook secret. Run npx -y @shieldlabs-ai/mcp --help for transport and tool allowlist options.
Local tools
Tool | Purpose |
| Read an identification card by ID and domain UUID |
| List identifications or users with opaque pagination |
| Read one identification, user, visitor, device or ip card |
| Explain reported risk and weight availability |
| Read and cache the optional Management profile |
| Verify original bytes before interpreting a webhook |
| Read UTC time for freshness checks |
Local resources
The identification template is shieldlabs://domains/{domain}/identifications/{identification_id}. It reads the same entity API once. Static contract/reference resources distinguish scored webhook identifications from entity History cards.
Local prompts
integrate_shieldlabs: inspect the stack and configure the selected installation stages.investigate_user: read the supplied User HID's API aggregate card in one domain.review_request: read and assess one identification in one domain.
Security
Hosted requests use the user's OAuth grant and signed public
/mcp/v1calls. Private dashboard API routes are never exposed through MCP.Every operation applies account/domain ownership. Foreign-domain responses are rejected by the client as well.
Destructive operations require
confirm:true. One-time credentials are masked unless an installer explicitly usesinclude_secret:true; MCP transcripts can record them. Store privately and never repeat them in chat.Browser text is untrusted data. Invisible controls are escaped for display; private/debug fields are stripped, secret-shaped text is masked and output sizes are bounded.
Oversized History pages fail with a smaller-page request instead of dropping records behind an opaque cursor.
Signature checks use original webhook bytes. A valid signature is separate from the truth or availability of fields in the event.
Errors and retries
A missing identification or risk.state:"unavailable" is unverified. Do not turn it into a zero score or infer a billing cause from a missing row. Preserve the backend error code in diagnostic reports.
Read polling respects the total wait deadline, Retry-After and cancellation. A 400, 401, 403 or 410 is not healed by polling. A 404 for a fresh identification may be retried with the same ID. Never automatically retry a mutation whose result may already have been committed.
Troubleshooting
Symptom | Action |
Only offline tools are listed | Configure |
A History read rejects a hostname | Use the public UUID from the domain list |
| Update the client to the domain-scoped entity History API |
Pagination token rejected | Recheck the original query; a restart can invalidate tokens |
| Treat the read as unavailable and retry within a bounded read budget |
OAuth rejected | Reconnect using the client's OAuth flow |
A webhook test passes but no real event arrives | Check actual source generation and real signed delivery separately |
Compatibility
Node.js 20, 22 and 24 are tested in CI. Transports are stdio and streamable HTTP, with resources and prompts. The published Node SDK 1.3.2 entity client is pinned in package-lock.json and bundled into the executable. Scored webhook normalization remains separate from entity History; older row-search SDK methods do not represent the current API.
Hosted service
Use https://mcp.shieldlabs.ai/mcp for the managed OAuth service and its 21 account tools.
This repository publishes the local npm server, its seven stdio tools and reusable HTTP source.
The managed service is deployed separately. Publishing an npm tag does not deploy that service.
Development droplet
.github/workflows/deploy.yml runs on a push to development and on a
manual rerun of that ref. It installs the built server on dev-shield-admin-node-0. The process listens on
127.0.0.1:8787. nginx serves https://dev.mcp.shieldlabs.ai. The gateway key is the GitHub
Environment secret MCP_GATEWAY_KEY, not a file in git.
Production host
.github/workflows/production.yml runs on a push to main.
It builds the checksummed bundle and, in the Production environment, copies it to H3 and runs
deploy/production/install.py. The workflow does not receive the gateway key. The host must
already be prod-shield-0 with /etc/shieldlabs-mcp-production.env, the mcp.shieldlabs.ai
certificate, and an accepted account backend. A pull request only builds the artifact.
wrangler.jsonc keeps local Worker settings and has no custom domain. Do not run wrangler deploy.
npm run worker:check only bundles the Worker and does not upload it.
Domain, History and webhook tools call the public /mcp/v1 API. The connection probe uses
GET /mcp/v1/ping; reading a domain or a record is a separate verification step.
Process
The droplet starts node dist/index.js --transport http --mode public --host 127.0.0.1 --port 8787 --trust-proxy.
It speaks plain HTTP. nginx terminates TLS and is the only proxy that can reach the port.
Rate limits are kept in memory per process, and request logs go to stdout, one JSON line per
request. This repository does not push a container image.
The per-address limits use the address of each TCP connection, which behind a proxy is the
proxy's for every client. Add --trust-proxy to take the client address from CF-Connecting-IP
instead: the proxy must then set that header on every request, replacing any value the client
sent. Cloudflare sets it; with nginx, use proxy_set_header CF-Connecting-IP $remote_addr;.
Development
npm ci
# Until @shieldlabs-ai/node is published, build its tarball in a checkout of shieldlabs-node:
(cd ../shieldlabs-node && npm ci && npm pack)
npm install --no-save ../shieldlabs-node/shieldlabs-ai-node-1.0.0.tgz
npm run typecheck
npm run lint
npm test -- --coverage
npm run build
node dist/index.js --help
npm run smoke # the built server over stdio against the fake History API
npm run test:worker # the hosted mode in workerd (Node.js 22 or later)
npm run worker:check # the Worker bundle: no Node.js built-in module, size limit@shieldlabs-ai/node is bundled into dist/ at build time, so the published package depends only on
@modelcontextprotocol/sdk and zod.
Fake History API
scripts/mock-history-api.mjs serves a deterministic dataset (test/mock-data/, generated by
scripts/generate-mock-data.mjs) as a fake History API and Management API:
npm run build
node scripts/mock-history-api.mjs --port 8788
# in another terminal: the MCP Inspector CLI passes environment variables with -e
npx @modelcontextprotocol/inspector --cli node dist/index.js \
-e SHIELDLABS_API_KEY=sec_evaldata-mockdata-00000001 \
-e SHIELDLABS_API_BASE_URL=http://127.0.0.1:8788 \
--method tools/listnpm run smoke does the same without extra downloads: it starts the fake API, runs the built
server over stdio with the MCP SDK client and calls its tools, resources and prompts.
See CONTRIBUTING.md. Documentation: docs.shieldlabs.ai. Support: contact@shieldlabs.ai.
License
Available Tools
6 toolsshieldlabs_current_timeCurrent timeARead-onlyIdempotent
Return the current time of the machine running this server, in UTC and optionally in one IANA time zone.
Use it to judge freshness: for example whether an identification's observed_at is older than a 5-minute window, or how long ago an account was last seen.
Args:
timezone (string, optional): IANA time zone such as "Europe/Berlin"
response_format ('markdown' | 'json'): default 'markdown'
Returns (json): { "utc": string, "unix_seconds": number, "unix_ms": number, "timezone": string | null, "local_time": string | null, "utc_offset": string | null }
| Name | Required | Description | Default |
|---|---|---|---|
| timezone | No | IANA time zone for an additional local time, for example "Europe/Berlin" or "America/New_York" | |
| response_format | No | Output format: 'markdown' (default) for readable text, 'json' for structured data | markdown |
Output Schema
| Name | Required | Description |
|---|---|---|
| utc | Yes | Current time, RFC 3339 UTC with milliseconds |
| unix_ms | Yes | |
| timezone | Yes | |
| local_time | Yes | |
| utc_offset | Yes | |
| unix_seconds | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, non-destructive and openWorldHint=false, so the safety profile is covered. The description adds value beyond that by clarifying that the time is the server's machine clock in UTC (not the caller's local time) and that a caller-supplied timezone only adds a local rendering – a meaningful, non-obvious semantic.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in one sentence, followed by usage rationale, then args and returns in compact list form. The Returns block is largely redundant with the existing output schema, which costs a little length, but the overall structure is clean and skimmable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-required-parameter, read-only utility with an output schema present, the description covers purpose, usage motivation, and parameter intent. An agent has everything needed to call it correctly; the duplicated return block is harmless redundancy rather than a gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented with constraints and defaults. The description restates them (IANA zone example, default 'markdown') without adding syntax, fallback behavior for invalid zones, or offset semantics, so it sits at the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence names a specific verb and resource ('Return the current time of the machine running this server') and scopes it precisely to UTC plus an optional IANA time zone. No sibling tool (identification, history, summarize, risk score, webhook verification) overlaps with this, so differentiation is implicit and unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives concrete when-to-use scenarios tied to sibling concepts: judging whether an identification's observed_at is older than a 5-minute window, or how long ago an account was last seen. It lacks any when-not-to-use or named alternative, but no plausible alternative exists among the siblings, so the guidance is nearly complete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
shieldlabs_explain_risk_scoreExplain Risk ScoreBRead-onlyIdempotent
Read an identification card or explain a pasted identification. Missing signal weights are unavailable, never zero contribution. HRE confidence is separate from Risk Score.
| Name | Required | Description | Default |
|---|---|---|---|
| domain | No | Public domain UUID from shieldlabs_list_domains, not the hostname. | |
| identification | No | ||
| response_format | No | markdown | |
| identification_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| risk | No | |
| signals | Yes | |
| risk_score | No | |
| identification_id | Yes | |
| weights_available | No | |
| untrusted_data_note | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds valuable behavioral context beyond the standard annotations. Specifically, it clarifies that missing signal weights are never zero contribution and that HRE confidence is tracked separately from the final Risk Score, which an AI agent needs to understand the logic of the output.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is efficient, front-loading the primary function in the first sentence. It avoids unnecessary jargon or fluff, making it easy for an agent to parse the tool's capability quickly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the low schema coverage and the complex domain of risk scoring, the description is sufficiently complete. It addresses the scoring assumptions and input formats sufficiently for an agent to make an informed decision before invoking the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Since schema description coverage is low (25%), the qualitative information provided in the description is very high value. It explains the nature of the input (reading/pasting) and the logic behind the scoring (signal weights, HRE confidence), which the schema omits.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The title ('Explain Risk Score') and the description clearly state the core function—analyzing an identification for risk. However, the description does not explicitly state what the tool outputs or how this differs structurally from the other ShieldLabs tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance provided on when to use this tool versus alternatives (e.g., if the data is already in a domain or already an identification object). It lacks a clear 'use this when...' statement.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
shieldlabs_get_historyGet History recordBRead-onlyIdempotent
Read one identification, user, visitor, device or ip card. domain is the public UUID and resource_id is the record id. The same flat list filters apply, plus device_id_source (legacy or fp21_record). relation, relation_limit and relation_page_token are not accepted. Nested filter, chips, offset and include_total are rejected.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | ||
| ids | No | ||
| from | No | ||
| ai_bot | No | ||
| domain | Yes | Public domain UUID from shieldlabs_list_domains, not the hostname. | |
| is_tor | No | ||
| is_vpn | No | ||
| source | No | ||
| campaign | No | ||
| is_proxy | No | ||
| resource | Yes | ||
| snapshot | No | ||
| banned_ip | No | ||
| ai_browser | No | ||
| risk_bands | No | ||
| ip_mismatch | No | ||
| resource_id | Yes | ||
| is_incognito | No | ||
| latency_test | No | ||
| risk_signals | No | ||
| is_antidetect | No | ||
| is_search_bot | No | ||
| is_js_disabled | No | ||
| is_os_mismatch | No | ||
| device_spoofing | No | ||
| hre_min_devices | No | ||
| response_format | No | markdown | |
| check_incomplete | No | ||
| device_id_source | No | ||
| hre_min_accounts | No | ||
| is_privacy_relay | No | ||
| is_os_not_detected | No | ||
| is_timezone_mismatch | No | ||
| is_browser_automation | No | ||
| network_check_incomplete | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| entity | Yes | |
| resource | Yes | |
| untrusted_data_note | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint and destructiveHint=false, so the safety profile is covered. The description adds real value by enumerating rejected parameters and clarifying that `domain` is the public UUID rather than a hostname, but says nothing about not-found behavior, error shapes, or how the filter params constrain a single-record fetch.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four dense sentences, front-loaded with the read action and the two required identifiers before the filter rules. No filler, though the two rejection sentences could be merged into one.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be described. However, for a 35-parameter tool with 3% schema coverage, the description leaves the bulk of the filter surface unexplained and offloads it to a sibling tool's documentation — an agent could call this correctly only by reading shieldlabs_list_history first.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 3% across 35 parameters, so the description carries most of the burden — and it only explains `domain`, `resource_id`, `device_id_source` (whose values are already an enum) and the rejected params. The ~30 boolean/array filters (risk_bands, hre_min_devices, is_antidetect, etc.) get no explanation at all beyond the pointer to the list tool's 'flat list filters'.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource: 'Read one identification, user, visitor, device or ip card', which maps directly to the `resource` enum and distinguishes it from the list sibling by the word 'one'. It never names shieldlabs_list_history explicitly, so the agent must infer the single-vs-list split from context.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The exclusions are genuinely useful ('relation, relation_limit and relation_page_token are not accepted', 'nested filter, chips, offset and include_total are rejected'), steering the agent away from params it might otherwise guess. But 'The same flat list filters apply' is an unqualified cross-reference that only makes sense if the agent has already loaded shieldlabs_list_history, and there is no statement of when to prefer this over that sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
shieldlabs_get_identificationGet identificationARead-onlyIdempotent
Read one identification card by identification_id in a public domain UUID. wait polls missing results for at most 10 seconds; unavailable risk remains unavailable.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | ||
| from | No | ||
| wait | No | ||
| domain | Yes | Public domain UUID from shieldlabs_list_domains, not the hostname. | |
| response_format | No | markdown | |
| identification_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| found | Yes | |
| identification | Yes | |
| untrusted_data_note | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnlyHint, idempotentHint, openWorldHint, and destructiveHint=false. The description adds useful behavioral context beyond annotations by explaining that wait polls missing results for at most 10 seconds and that unavailable risk remains unavailable. It does not cover auth or rate limits, but it meaningfully supplements the annotation profile.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is short and front-loads the core purpose. Its second sentence is compact but somewhat cryptic, especially "unavailable risk remains unavailable," which could be clearer without adding much length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the essential read-by-ID behavior and the wait polling semantics, and an output schema exists so return values need not be described. However, with low schema description coverage across six parameters, it does not fully explain the optional date filters or response format, leaving clear gaps for an agent invoking the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 17%, so the description must compensate for undocumented parameters. It mentions identification_id, domain, and wait semantics, but leaves to, from, and response_format unexplained, despite those being present in the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: "Read one identification card by identification_id in a public domain UUID." It clearly identifies the key lookup inputs and is distinguishable from the listed sibling tools, which cover history, risk scores, webhook signatures, and time.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by "Read one identification card by identification_id," so an agent can infer this is for fetching a single record rather than listing or searching. However, there is no explicit guidance on when to use this versus alternatives or when not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
shieldlabs_list_historyList HistoryARead-onlyIdempotent
List identifications or users in one domain. domain is the public UUID from shieldlabs_list_domains. Repeat page_token with the same resource, window and flat filters. Filters are risk_bands, hre_min_accounts (3-99), hre_min_devices (4-99), ids (one user id on the user list), risk_signals, source, campaign and boolean flags. Dates are RFC3339 and the window is half-open. Nested filter, chips, offset, include_total and sort are rejected. visitor, device and ip have a card, not a list.
| Name | Required | Description | Default |
|---|---|---|---|
| to | No | ||
| ids | No | ||
| from | No | ||
| limit | No | ||
| ai_bot | No | ||
| domain | Yes | Public domain UUID from shieldlabs_list_domains, not the hostname. | |
| is_tor | No | ||
| is_vpn | No | ||
| source | No | ||
| campaign | No | ||
| is_proxy | No | ||
| resource | Yes | ||
| snapshot | No | ||
| banned_ip | No | ||
| ai_browser | No | ||
| page_token | No | ||
| risk_bands | No | ||
| ip_mismatch | No | ||
| is_incognito | No | ||
| latency_test | No | ||
| risk_signals | No | ||
| is_antidetect | No | ||
| is_search_bot | No | ||
| is_js_disabled | No | ||
| is_os_mismatch | No | ||
| device_spoofing | No | ||
| hre_min_devices | No | ||
| response_format | No | markdown | |
| check_incomplete | No | ||
| hre_min_accounts | No | ||
| is_privacy_relay | No | ||
| is_os_not_detected | No | ||
| is_timezone_mismatch | No | ||
| is_browser_automation | No | ||
| network_check_incomplete | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| data | Yes | |
| resource | Yes | |
| next_page_token | Yes | |
| untrusted_data_note | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, open-world), so the description mainly adds scoping rules. It does add genuine behavioral context — RFC3339 dates, half-open window, pagination requiring identical filters, and a list of rejected parameters — but says nothing about the pagination token's lifetime or result ordering.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded: the core operation, the domain provenance, and the pagination rule come first. Sentences are telegraphic yet each carries distinct information; some reads ('visitor, device and ip have a card, not a list') are cryptic out of context but still earn their place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 35-parameter tool with near-zero schema descriptions, the definition leaves meaningful gaps: the boolean flag set, the meaning of snapshot, the limit cap, and the from/to pairing rules are unaddressed. An output schema exists, so return values needn't be explained, which keeps it from being worse.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With only 3% schema description coverage across 35 parameters, the description has to carry nearly the full burden. It names several filters (risk_bands, hre_min_accounts 3-99, hre_min_devices 4-99, ids as one user id on the user list, risk_signals, source, campaign) but lumps roughly two dozen booleans into 'boolean flags', and never explains from/to, limit, snapshot, page_token or response_format semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('List') and resource ('identifications or users in one domain'), which is clearly distinct from the card-style sibling shieldlabs_get_identification. It also disambiguates the resource enum by noting that visitor, device and ip have a card, not a list. It does not, however, contrast with shieldlabs_get_history, leaving that boundary implicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete operational guidance: domain must be the public UUID from shieldlabs_list_domains, page_token must be repeated with the same resource/window/flat filters, and nested filter, chips, offset, include_total and sort are rejected. That is real when/how guidance, though it never explicitly says when to prefer this over shieldlabs_get_history or the card tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
shieldlabs_verify_webhook_signatureVerify webhook signatureARead-onlyIdempotent
Check a received ShieldLabs webhook delivery: does its X-Shield-Signature header match the raw body for the endpoint signing secret? For debugging a webhook endpoint. SHIELDLABS_WEBHOOK_SECRET is not set on this server, so pass secret.
The signature is "sha256=" plus the lowercase hex HMAC-SHA256 of the raw body, keyed with the full signing secret including its whsec_ prefix. While a secret is rotated, give several secrets separated by commas (in secret or in SHIELDLABS_WEBHOOK_SECRET): the delivery is valid when any of them matches, and matched_secret tells which. When the signature does not match a body of up to 64 KB, the tool tests common body changes (re-serialized JSON, unescaped characters, added newline) and says which one explains the mismatch. It never returns a secret or an expected signature.
Args:
payload (string): raw body exactly as received
signature_header (string): value of X-Shield-Signature
secret (string, optional): whsec_... signing secret of the endpoint, or several separated by commas
payload_encoding ('utf8' | 'base64'): default 'utf8'
response_format ('markdown' | 'json'): default 'markdown'
Returns (json): { "valid": boolean, "secret_source": "argument" | "environment", "secrets_checked": number, "matched_secret": number | null, "checks": [{ check, ok, detail }], "event": { event_type, schema_version, created_at, event_id, identification_id, risk_score, risk_band } | null, "hint": string | null }
| Name | Required | Description | Default |
|---|---|---|---|
| secret | No | Endpoint signing secret including its whsec_ prefix, or several separated by commas while you rotate. Defaults to SHIELDLABS_WEBHOOK_SECRET from the server environment. | |
| payload | Yes | The raw request body exactly as received: the bytes that were signed. Do not pretty-print, re-serialize or trim it. | |
| response_format | No | Output format: 'markdown' (default) for readable text, 'json' for structured data | markdown |
| payload_encoding | No | 'utf8' (default) when payload is the body text; 'base64' when payload is the body bytes encoded as base64 (use it when exact bytes such as a trailing newline matter) | utf8 |
| signature_header | Yes | Value of the X-Shield-Signature header, for example "sha256=ea26857...fdd8" |
Output Schema
| Name | Required | Description |
|---|---|---|
| hint | Yes | |
| event | Yes | |
| valid | Yes | |
| checks | Yes | |
| secret_source | Yes | |
| matched_secret | Yes | Position, from 1, of the secret that matches, in the order given; null when none |
| secrets_checked | Yes | How many signing secrets were checked (several while a secret is rotated) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes far beyond the readOnly/destructive hints by explaining technical constraints (64 KB body limit), the underlying cryptographic process (sha256+ hex HMAC-SHA256), and critical failure modes—specifically that it 'never returns a secret or an expected signature.' It even mentions active error mitigation (testing for re-serialized JSON changes).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is necessarily detailed due to the complex technical requirements (signature format, secret rotation, error testing), making it long but highly functional. It prioritizes the core purpose and constraints upfront.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity and the presence of a rich output schema, the description is extremely complete. It details the security mechanisms, the logic of multi-secret checking, and the error-handling capabilities of the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
While the schema coverage is 100% (baseline 3), the description adds essential operational context that the schema misses. It clarifies that the `secret` can be a comma-separated list for rotation, and it specifies required formats for `payload` (no pretty-printing).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly defines the function as checking if an X-Shield-Signature header matches the raw body, specifically for 'debugging a webhook endpoint.' This is highly specific and immediately distinguishes it from the list or get operations of sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides explicit guidance on when to use the tool (debugging) and how to handle complexity, such as providing multiple rotating secrets separated by commas. It also notes environmental constraints ('SHIELDLABS_WEBHOOK_SECRET is not set on this server').
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
7 tool updates
v1.1.0- Changed
shieldlabs_explain_risk_score25 fields changed- added
Input schema / properties / domainAdded value: +{ + "description": "Public domain UUID from shieldlabs_list_domains, not the hostname.", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", + "type": "string" +} - changed
Input schema / properties / identification / anyOfPrevious value: -[ - { - "additionalProperties": true, - "propertyNames": { - "type": "string" - }, - "type": "object" - }, - { - "maxLength": 500000, - "type": "string" - } -]New value: +[ + { + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + { + "maxLength": 500000, + "type": "string" + } +] - removed
Input schema / properties / identification / descriptionRemoved value: -"An identification to explain without an API call: a normalized identification (for example the json output of shieldlabs_get_identification), a webhook event or its data object, or a raw History API row. A JSON object or its JSON text." - added
Input schema / properties / identification_idAdded value: +{ + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", + "type": "string" +} - removed
Input schema / properties / request_idRemoved value: -{ - "description": "Request ID of the identification to explain, read from the History API (needs SHIELDLABS_API_KEY; waits up to about 10 seconds for a new identification, like shieldlabs_get_identification). Give either request_id or identification.", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", - "type": "string" -} - removed
Input schema / properties / response_format / descriptionRemoved value: -"Output format: 'markdown' (default) for readable text, 'json' for structured data" - changed
Output schema / additionalPropertiesPrevious value: -falseNew value: +{} - removed
Output schema / properties / band_meaningRemoved value: -{ - "type": "string" -} - removed
Output schema / properties / band_rangeRemoved value: -{ - "type": "string" -} - removed
Output schema / properties / connection_typeRemoved value: -{ - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "label": { - "type": "string" - }, - "meaning": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "required": [ - "value", - "label", - "meaning" - ], - "type": "object" - }, - { - "type": "null" - } - ] -} - removed
Output schema / properties / detection_flagsRemoved value: -{ - "items": { - "additionalProperties": false, - "properties": { - "flag": { - "type": "string" - }, - "label": { - "type": "string" - }, - "meaning": { - "type": "string" - }, - "scored": { - "type": "boolean" - } - }, - "required": [ - "flag", - "label", - "scored", - "meaning" - ], - "type": "object" - }, - "type": "array" -} - added
Output schema / properties / identification_idAdded value: +{ + "type": [ + "string", + "null" + ] +} - removed
Output schema / properties / notesRemoved value: -{ - "items": { - "type": "string" - }, - "type": "array" -} - removed
Output schema / properties / request_idRemoved value: -{ - "anyOf": [ - { - "description": "Request ID of the explained identification, when known", - "type": "string" - }, - { - "type": "null" - } - ] -} - added
Output schema / properties / riskAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "string" + }, + "band": { + "enum": [ + "trusted", + "suspicious", + "dangerous" + ], + "type": "string" + }, + "score": { + "maximum": 100, + "minimum": 0, + "type": "integer" + }, + "state": { + "const": "ok", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "null" + }, + "band": { + "type": "null" + }, + "state": { + "const": "unavailable", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + } + ] +} - removed
Output schema / properties / risk_bandRemoved value: -{ - "enum": [ - "trusted", - "suspicious", - "dangerous", - "rate_limited" - ], - "type": "string" -} - added
Output schema / properties / signals / anyOfAdded value: +[ + { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "type": "string" + }, + "maxItems": 100, + "type": "array" + }, + "state": { + "enum": [ + "ok", + "unavailable" + ], + "type": "string" + } + }, + "required": [ + "state", + "items" + ], + "type": "object" + }, + { + "items": { + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" + }, + "type": "array" + } +] - removed
Output schema / properties / signals / itemsRemoved value: -{ - "additionalProperties": false, - "properties": { - "in_catalog": { - "type": "boolean" - }, - "label": { - "type": "string" - }, - "meaning": { - "type": "string" - }, - "name": { - "type": "string" - }, - "reading": { - "anyOf": [ - { - "description": "How to read the risk signal next to others; null outside the catalog", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "related_flag": { - "anyOf": [ - { - "description": "Detection flag that usually comes with the risk signal", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "typical_weight": { - "anyOf": [ - { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - { - "type": "null" - } - ] - }, - "weight": { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - } - }, - "required": [ - "name", - "weight", - "label", - "meaning", - "reading", - "typical_weight", - "related_flag", - "in_catalog" - ], - "type": "object" -} - removed
Output schema / properties / signals / typeRemoved value: -"array" - removed
Output schema / properties / sourceRemoved value: -{ - "enum": [ - "history", - "webhook", - "input" - ], - "type": "string" -} - removed
Output schema / properties / truncatedRemoved value: -{ - "description": "Only present (true) when this server shortened the explanation", - "type": "boolean" -} - removed
Output schema / properties / truncation_messageRemoved value: -{ - "description": "What was shortened: risk signals left out, or long pasted values cut", - "type": "string" -} - added
Output schema / properties / untrusted_data_noteAdded value: +{ + "type": "string" +} - added
Output schema / properties / weights_availableAdded value: +{ + "type": "boolean" +} - changed
Output schema / requiredPrevious value: -[ - "request_id", - "risk_score", - "risk_band", - "band_range", - "band_meaning", - "signals", - "detection_flags", - "connection_type", - "notes", - "source" -]New value: +[ + "identification_id", + "signals", + "untrusted_data_note" +]
- Added
shieldlabs_get_history - Changed
shieldlabs_get_identification59 fields changed- added
Input schema / properties / domainAdded value: +{ + "description": "Public domain UUID from shieldlabs_list_domains, not the hostname.", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", + "type": "string" +} - added
Input schema / properties / fromAdded value: +{ + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", + "type": "string" +} - added
Input schema / properties / identification_idAdded value: +{ + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", + "type": "string" +} - removed
Input schema / properties / request_idRemoved value: -{ - "description": "Request ID of one identification: the UUID the browser agent returned for the protected action, for example \"02f1d973-84db-4156-a7f7-e799e6bf389b\"", - "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", - "type": "string" -} - removed
Input schema / properties / response_format / descriptionRemoved value: -"Output format: 'markdown' (default) for readable text, 'json' for structured data" - added
Input schema / properties / toAdded value: +{ + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", + "type": "string" +} - removed
Input schema / properties / wait / descriptionRemoved value: -"Wait for the verdict when the identification is not stored yet, polling for up to about 10 seconds in total (default true). Use false to read once, for example to read a refined verdict again." - changed
Input schema / requiredPrevious value: -[ - "request_id" -]New value: +[ + "domain", + "identification_id" +] - added
Output schema / properties / identification / properties / attributesAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "browser": { + "type": [ + "string", + "null" + ] + }, + "connection_type": { + "type": [ + "string", + "null" + ] + }, + "countries": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "maxItems": 100, + "type": "array" + }, + { + "type": "null" + } + ] + }, + "device_type": { + "type": [ + "string", + "null" + ] + }, + "local_countries": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "maxItems": 100, + "type": "array" + }, + { + "type": "null" + } + ] + }, + "os": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "countries", + "local_countries", + "browser", + "os", + "device_type", + "connection_type" + ], + "type": "object" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / identification / properties / band_splitAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "dangerous": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "suspicious": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "trusted": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "trusted", + "suspicious", + "dangerous" + ], + "type": "object" + }, + { + "type": "null" + } + ] +} - changed
Output schema / properties / identification / properties / browser / typePrevious value: -"string"New value: +[ + "string", + "null" +] - changed
Output schema / properties / identification / properties / connection_type / typePrevious value: -"string"New value: +[ + "string", + "null" +] - changed
Output schema / properties / identification / properties / cookie_id / typePrevious value: -"string"New value: +[ + "string", + "null" +] - added
Output schema / properties / identification / properties / countryAdded value: +{ + "type": [ + "string", + "null" + ] +} - added
Output schema / properties / identification / properties / created_atAdded value: +{ + "type": [ + "string", + "null" + ] +} - removed
Output schema / properties / identification / properties / detection_flagsRemoved value: -{ - "additionalProperties": false, - "properties": { - "abuser": { - "type": "boolean" - }, - "anti_detect_browser": { - "type": "boolean" - }, - "browser_automation": { - "type": "boolean" - }, - "browser_vpn_proxy": { - "type": "boolean" - }, - "check_incomplete": { - "type": "boolean" - }, - "datacenter_ip": { - "type": "boolean" - }, - "incognito": { - "type": "boolean" - }, - "ip_mismatch": { - "type": "boolean" - }, - "javascript_disabled": { - "type": "boolean" - }, - "os_mismatch": { - "type": "boolean" - }, - "os_not_detected": { - "type": "boolean" - }, - "privacy_relay": { - "type": "boolean" - }, - "proxy": { - "type": "boolean" - }, - "search_bot": { - "type": "boolean" - }, - "stun_not_checked": { - "type": "boolean" - }, - "suspicious_paid_click": { - "type": "boolean" - }, - "timezone_mismatch": { - "type": "boolean" - }, - "tor": { - "type": "boolean" - }, - "vpn": { - "type": "boolean" - } - }, - "required": [ - "vpn", - "privacy_relay", - "browser_vpn_proxy", - "tor", - "proxy", - "datacenter_ip", - "abuser", - "os_mismatch", - "os_not_detected", - "timezone_mismatch", - "anti_detect_browser", - "browser_automation", - "ip_mismatch", - "incognito", - "search_bot", - "suspicious_paid_click", - "javascript_disabled", - "stun_not_checked", - "check_incomplete" - ], - "type": "object" -} - changed
Output schema / properties / identification / properties / device_id / typePrevious value: -"string"New value: +[ + "string", + "null" +] - added
Output schema / properties / identification / properties / device_id_sourceAdded value: +{ + "type": [ + "string", + "null" + ] +} - added
Output schema / properties / identification / properties / device_identityAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "hardware_id": { + "type": [ + "string", + "null" + ] + }, + "hardware_ids": { + "items": { + "type": "string" + }, + "maxItems": 100, + "type": "array" + }, + "legacy_device_id": { + "type": [ + "string", + "null" + ] + }, + "legacy_device_ids": { + "items": { + "type": "string" + }, + "maxItems": 100, + "type": "array" + }, + "record_ids": { + "items": { + "type": "string" + }, + "maxItems": 100, + "type": "array" + }, + "state": { + "type": "string" + } + }, + "type": "object" + }, + { + "type": "null" + } + ] +} - changed
Output schema / properties / identification / properties / device_type / typePrevious value: -"string"New value: +[ + "string", + "null" +] - added
Output schema / properties / identification / properties / domain_idAdded value: +{ + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", + "type": "string" +} - removed
Output schema / properties / identification / properties / escaped_fieldsRemoved value: -{ - "description": "Only present when a value contained invisible or control characters: paths of the fields where this server replaced them with visible \\uXXXX or \\u{XXXXX} escapes. Such a value differs from the stored one: do not search by it.", - "items": { - "type": "string" - }, - "type": "array" -} - added
Output schema / properties / identification / properties / first_seenAdded value: +{ + "type": [ + "string", + "null" + ] +} - added
Output schema / properties / identification / properties / high_risk_eventsAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": [ + "string", + "null" + ] + }, + "items": { + "items": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "confidence": { + "type": "string" + } + }, + "required": [ + "code", + "confidence" + ], + "type": "object" + }, + "maxItems": 100, + "type": "array" + }, + "state": { + "type": "string" + } + }, + "required": [ + "state", + "as_of", + "items" + ], + "type": "object" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / identification / properties / idAdded value: +{ + "maxLength": 512, + "minLength": 1, + "type": "string" +} - added
Output schema / properties / identification / properties / identificationsAdded value: +{ + "anyOf": [ + { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / identification / properties / identity_riskAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "device": { + "anyOf": [ + { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "string" + }, + "band": { + "enum": [ + "trusted", + "suspicious", + "dangerous" + ], + "type": "string" + }, + "score": { + "maximum": 100, + "minimum": 0, + "type": "integer" + }, + "state": { + "const": "ok", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "null" + }, + "band": { + "type": "null" + }, + "state": { + "const": "unavailable", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + } + ] + }, + { + "type": "null" + } + ] + }, + "ip": { + "anyOf": [ + { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "string" + }, + "band": { + "enum": [ + "trusted", + "suspicious", + "dangerous" + ], + "type": "string" + }, + "score": { + "maximum": 100, + "minimum": 0, + "type": "integer" + }, + "state": { + "const": "ok", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "null" + }, + "band": { + "type": "null" + }, + "state": { + "const": "unavailable", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + } + ] + }, + { + "type": "null" + } + ] + }, + "user": { + "anyOf": [ + { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "string" + }, + "band": { + "enum": [ + "trusted", + "suspicious", + "dangerous" + ], + "type": "string" + }, + "score": { + "maximum": 100, + "minimum": 0, + "type": "integer" + }, + "state": { + "const": "ok", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "null" + }, + "band": { + "type": "null" + }, + "state": { + "const": "unavailable", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + } + ] + }, + { + "type": "null" + } + ] + }, + "visitor": { + "anyOf": [ + { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "string" + }, + "band": { + "enum": [ + "trusted", + "suspicious", + "dangerous" + ], + "type": "string" + }, + "score": { + "maximum": 100, + "minimum": 0, + "type": "integer" + }, + "state": { + "const": "ok", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "null" + }, + "band": { + "type": "null" + }, + "state": { + "const": "unavailable", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "visitor", + "device", + "user", + "ip" + ], + "type": "object" + }, + { + "type": "null" + } + ] +} - added
Output schema / properties / identification / properties / last_seenAdded value: +{ + "type": [ + "string", + "null" + ] +} - added
Output schema / properties / identification / properties / local_countryAdded value: +{ + "type": [ + "string", + "null" + ] +} - removed
Output schema / properties / identification / properties / local_ip / additionalPropertiesRemoved value: -false - removed
Output schema / properties / identification / properties / local_ip / propertiesRemoved value: -{ - "country": { - "description": "English country name (for example \"Germany\"), or \"\" when unknown", - "type": "string" - }, - "ip": { - "description": "Dotted IPv4 address, or \"\" when none is known", - "type": "string" - } -} - removed
Output schema / properties / identification / properties / local_ip / requiredRemoved value: -[ - "ip", - "country" -] - changed
Output schema / properties / identification / properties / local_ip / typePrevious value: -"object"New value: +[ + "string", + "null" +] - removed
Output schema / properties / identification / properties / observed_atRemoved value: -{ - "anyOf": [ - { - "description": "When the identification was observed, RFC 3339 UTC with milliseconds", - "type": "string" - }, - { - "type": "null" - } - ] -} - changed
Output schema / properties / identification / properties / os / typePrevious value: -"string"New value: +[ + "string", + "null" +] - removed
Output schema / properties / identification / properties / public_ip / additionalPropertiesRemoved value: -false - removed
Output schema / properties / identification / properties / public_ip / propertiesRemoved value: -{ - "country": { - "description": "English country name (for example \"Germany\"), or \"\" when unknown", - "type": "string" - }, - "ip": { - "description": "Dotted IPv4 address, or \"\" when none is known", - "type": "string" - } -} - removed
Output schema / properties / identification / properties / public_ip / requiredRemoved value: -[ - "ip", - "country" -] - changed
Output schema / properties / identification / properties / public_ip / typePrevious value: -"object"New value: +[ + "string", + "null" +] - added
Output schema / properties / identification / properties / related_countsAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "cookies": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "devices": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "public_ips": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "sessions": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "users": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + }, + "visitors": { + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "cookies", + "sessions" + ], + "type": "object" + }, + { + "type": "null" + } + ] +} - removed
Output schema / properties / identification / properties / request_idRemoved value: -{ - "type": "string" -} - added
Output schema / properties / identification / properties / riskAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "string" + }, + "band": { + "enum": [ + "trusted", + "suspicious", + "dangerous" + ], + "type": "string" + }, + "score": { + "maximum": 100, + "minimum": 0, + "type": "integer" + }, + "state": { + "const": "ok", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "as_of": { + "type": "null" + }, + "band": { + "type": "null" + }, + "state": { + "const": "unavailable", + "type": "string" + } + }, + "required": [ + "state", + "band", + "as_of" + ], + "type": "object" + } + ] +} - removed
Output schema / properties / identification / properties / risk_bandRemoved value: -{ - "enum": [ - "trusted", - "suspicious", - "dangerous", - "rate_limited" - ], - "type": "string" -} - removed
Output schema / properties / identification / properties / risk_scoreRemoved value: -{ - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" -} - changed
Output schema / properties / identification / properties / session_id / typePrevious value: -"string"New value: +[ + "string", + "null" +] - added
Output schema / properties / identification / properties / signals / additionalPropertiesAdded value: +false - removed
Output schema / properties / identification / properties / signals / itemsRemoved value: -{ - "additionalProperties": false, - "properties": { - "description": { - "anyOf": [ - { - "description": "Always null: internal score-detail text is not returned", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "name": { - "type": "string" - }, - "weight": { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - } - }, - "required": [ - "name", - "weight", - "description" - ], - "type": "object" -} - added
Output schema / properties / identification / properties / signals / propertiesAdded value: +{ + "items": { + "items": { + "type": "string" + }, + "maxItems": 100, + "type": "array" + }, + "state": { + "enum": [ + "ok", + "unavailable" + ], + "type": "string" + } +} - added
Output schema / properties / identification / properties / signals / requiredAdded value: +[ + "state", + "items" +] - changed
Output schema / properties / identification / properties / signals / typePrevious value: -"array"New value: +"object" - removed
Output schema / properties / identification / properties / sourceRemoved value: -{ - "enum": [ - "webhook", - "history" - ], - "type": "string" -} - added
Output schema / properties / identification / properties / trafficAdded value: +{ + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "channel": { + "type": [ + "string", + "null" + ] + }, + "click_id_type": { + "type": [ + "string", + "null" + ] + }, + "entry_url": { + "type": [ + "string", + "null" + ] + }, + "referrer_domain": { + "type": [ + "string", + "null" + ] + }, + "utm_campaign": { + "type": [ + "string", + "null" + ] + }, + "utm_content": { + "type": [ + "string", + "null" + ] + }, + "utm_medium": { + "type": [ + "string", + "null" + ] + }, + "utm_source": { + "type": [ + "string", + "null" + ] + }, + "utm_term": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "channel", + "referrer_domain", + "entry_url", + "click_id_type", + "utm_source", + "utm_medium", + "utm_campaign", + "utm_content", + "utm_term" + ], + "type": "object" + }, + { + "type": "null" + } + ] +} - removed
Output schema / properties / identification / properties / traffic_sourceRemoved value: -{ - "additionalProperties": false, - "properties": { - "channel": { - "type": "string" - }, - "click_id_type": { - "type": "string" - }, - "landing_url": { - "type": "string" - }, - "referrer_domain": { - "type": "string" - }, - "utm_campaign": { - "type": "string" - }, - "utm_content": { - "type": "string" - }, - "utm_medium": { - "type": "string" - }, - "utm_source": { - "type": "string" - }, - "utm_term": { - "type": "string" - } - }, - "required": [ - "channel", - "referrer_domain", - "landing_url", - "click_id_type", - "utm_source", - "utm_medium", - "utm_campaign", - "utm_content", - "utm_term" - ], - "type": "object" -} - removed
Output schema / properties / identification / properties / truncated_fieldsRemoved value: -{ - "description": "Only present when this server shortened values: paths of the fields cut to 1000 characters as serialized in JSON (100 for risk signal names), and \"signals\" when only the first 50 risk signals are listed. A shortened value is a prefix of the stored one: do not search by it.", - "items": { - "type": "string" - }, - "type": "array" -} - added
Output schema / properties / identification / properties / typeAdded value: +{ + "enum": [ + "identification", + "user", + "visitor", + "device", + "ip" + ], + "type": "string" +} - removed
Output schema / properties / identification / properties / user_hid / anyOfRemoved value: -[ - { - "description": "User HID as sent by the browser: \"anonymous\" for anonymous checks, null when it was empty", - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / identification / properties / user_hid / typeAdded value: +[ + "string", + "null" +] - changed
Output schema / properties / identification / properties / visitor_id / typePrevious value: -"string"New value: +[ + "string", + "null" +] - changed
Output schema / properties / identification / requiredPrevious value: -[ - "request_id", - "visitor_id", - "device_id", - "session_id", - "cookie_id", - "user_hid", - "domain", - "public_ip", - "local_ip", - "connection_type", - "os", - "browser", - "device_type", - "traffic_source", - "risk_score", - "risk_band", - "signals", - "detection_flags", - "observed_at", - "source" -]New value: +[ + "type", + "id", + "domain_id", + "domain", + "risk", + "signals" +]
- Added
shieldlabs_list_history - Removed
shieldlabs_search_history - Removed
shieldlabs_summarize_entity - Changed
shieldlabs_verify_webhook_signature1 field changed- changed
Output schema / properties / event / anyOfPrevious value: -[ - { - "additionalProperties": false, - "properties": { - "created_at": { - "type": "string" - }, - "event_type": { - "type": "string" - }, - "request_id": { - "anyOf": [ - { - "description": "Request ID of an identification.scored event", - "type": "string" - }, - { - "type": "null" - } - ] - }, - "risk_band": { - "anyOf": [ - { - "enum": [ - "trusted", - "suspicious", - "dangerous", - "rate_limited" - ], - "type": "string" - }, - { - "type": "null" - } - ] - }, - "risk_score": { - "anyOf": [ - { - "maximum": 9007199254740991, - "minimum": -9007199254740991, - "type": "integer" - }, - { - "type": "null" - } - ] - }, - "schema_version": { - "type": "string" - } - }, - "required": [ - "event_type", - "schema_version", - "created_at", - "request_id", - "risk_score", - "risk_band" - ], - "type": "object" - }, - { - "type": "null" - } -]New value: +[ + { + "additionalProperties": false, + "properties": { + "created_at": { + "type": "string" + }, + "event_id": { + "anyOf": [ + { + "description": "Signed event_id when the delivery includes one", + "type": "string" + }, + { + "type": "null" + } + ] + }, + "event_type": { + "type": "string" + }, + "identification_id": { + "anyOf": [ + { + "description": "Identification ID of an identification.scored event.", + "type": "string" + }, + { + "type": "null" + } + ] + }, + "risk_band": { + "anyOf": [ + { + "enum": [ + "trusted", + "suspicious", + "dangerous", + "banned_ip" + ], + "type": "string" + }, + { + "type": "null" + } + ] + }, + "risk_score": { + "anyOf": [ + { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "schema_version": { + "type": "string" + } + }, + "required": [ + "event_type", + "schema_version", + "created_at", + "event_id", + "identification_id", + "risk_score", + "risk_band" + ], + "type": "object" + }, + { + "type": "null" + } +]
6 tool updates
v1.0.0- First observed
shieldlabs_current_time - First observed
shieldlabs_explain_risk_score - First observed
shieldlabs_get_identification - First observed
shieldlabs_search_history - First observed
shieldlabs_summarize_entity - First observed
shieldlabs_verify_webhook_signature
TDQS
Scored across 6 tools
While all tools have distinct prefixes and functions, there is some overlap between list_history, get_identification, and get_history. An agent might confuse which specific getter tool to use depending on the granularity of the required record.
All tool names strictly adhere to the shieldlabs_verb_noun pattern. The use of snake_case is uniform across the entire set, making the set highly predictable and easy to parse.
The 6 tools are well-scoped and earn their place. They successfully cover the core functionalities needed for a data-heavy security/risk information retrieval service.
The tool set is critically incomplete as it lacks any mutation capabilities. The entire surface appears read-only, missing essential CRUD operations (create, update, delete) for the identified entities.
Maintenance
Related MCP Connectors
Exposes FEDLIN's public security scanners as agent-callable tools over Streamable HTTP.
Context and control for any AI. Agent identity, scoped access, and proof across tools.
Read-only, deterministic AI triage and readiness tools implementing Sophon's published rubrics.
Read-only phone intelligence for AI voice agents — line type, risk, DNC, signed receipts.
Related MCP Servers
- AlicenseAqualityAmaintenanceLets an AI agent read a Pyxis dashboard's analytics — overviews, funnels, devices, acquisition, backend requests, visits and project settings — using a read-only access token created by an admin and revocable at any time. It runs locally over stdio, holds no state, and exposes only GET-style reads of the live Pyxis API.161,507 npmMIT
- AlicenseNot gradedqualityBmaintenanceExposes read-only local analysis tools for source auditing, web traffic review, binary reverse engineering, exploit scaffolding, OSINT, and reporting via JSON-RPC over stdio, enabling LLMs to orchestrate evidence-based security workflows without executing targets or sending network requests.1MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to perform internet-facing reconnaissance and vulnerability research by looking up open ports, banners and TLS details for IPs, searching devices with Shodan query syntax, resolving DNS records in batch, and retrieving CVE/CPE intelligence with CVSS, EPSS and KEV status. Tools are read-only, credit-aware, and exposed over stdio or HTTP streaming.MIT
- FlicenseNot gradedqualityAmaintenanceEnables AI agents to query traces, sessions, exceptions, prompts, datasets, scores, and metrics for enhanced debugging and observability. Supports local stdio or HTTP deployment, selective tool loading, and read-only mode.-