Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
084067f
release: v1.0.1 with Docker marketplace support (#70)
DorAshkenaziHuman Sep 8, 2025
7fef464
release: v1.0.2 with correct marketplace service name
DorAshkenaziHuman Sep 8, 2025
d4e0c34
update package-lock.json for version 1.0.2
DorAshkenaziHuman Sep 8, 2025
a15b86f
Merge branch 'main' into dev
DorAshkenaziHuman Sep 8, 2025
33fa196
Changed docker image registry location (#73)
DorAshkenaziHuman Sep 9, 2025
2cb636d
Merge branch 'main' into dev
DorAshkenaziHuman Sep 9, 2025
23d9d53
bump version to 1.0.4 to resolve NPM publish conflict
DorAshkenaziHuman Sep 9, 2025
f3ac780
Merge branch 'main' into dev
DorAshkenaziHuman Sep 9, 2025
0fca149
Bump @typescript-eslint/parser from 8.42.0 to 8.43.0 (#76)
dependabot[bot] Sep 21, 2025
134522e
Bump @typescript-eslint/eslint-plugin from 8.42.0 to 8.43.0 (#81)
dependabot[bot] Sep 21, 2025
b0f0faf
Bump actions/setup-node from 4 to 5 (#64)
dependabot[bot] Sep 21, 2025
e5defca
Bump lint-staged from 16.1.6 to 16.2.0 (#84)
dependabot[bot] Sep 24, 2025
590eae3
Bump @modelcontextprotocol/sdk from 1.17.5 to 1.18.1 (#82)
dependabot[bot] Sep 24, 2025
e73e3e1
Bump chai from 5.3.3 to 6.2.0 (#87)
dependabot[bot] Oct 8, 2025
8a8438f
Bump @typescript-eslint/eslint-plugin from 8.44.0 to 8.45.0 (#90)
dependabot[bot] Oct 8, 2025
32de4a6
fix: README updates (#95)
ori-gold-px Oct 20, 2025
298c62c
updated cd.yaml (#103)
chen-zimmer-px Oct 27, 2025
ff081a0
fix: setup node 22 in all github action workflows (#100)
ori-gold-px Oct 29, 2025
19371ed
Upgrade Node.js version and update npm (#104)
chen-zimmer-px Nov 3, 2025
87fac41
Bump actions/checkout from 5 to 6 (#105)
dependabot[bot] Dec 11, 2025
b8ea438
Bump @modelcontextprotocol/sdk from 1.18.1 to 1.20.2 (#102)
dependabot[bot] Dec 11, 2025
7931481
adjust to new routes (#124)
asafcohenpx Jul 1, 2026
7a39a34
bump version to 1.0.5 (#125)
asafcohenpx Jul 1, 2026
0873bb2
version (#127)
asafcohenpx Jul 2, 2026
9c2b883
raw activities capablities and query search (#130)
asafcohenpx Jul 5, 2026
15a4463
Merge branch 'dev' into resolve-conflicts-05-07-26
asafcohenpx Jul 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [1.2.0] - 2026-07-02

### Added

* New `human_get_raw_activities` tool: given a Block ID / Reference ID or an IP address and a short time window (max 4 hours), returns matching raw activity records, the total count, and aggregated traffic metrics to help analyze why traffic is being blocked. Supports `limit` (1-100, default 20) and `offset` for paginating through matching records (sorted newest-first). Key analysis fields surfaced: `filterOriginReason`, `ruleName`, `displayScore`, `incidentTypes`, `trafficTags`, and `blockReference`.
* `searchQuery` support across all traffic data endpoints (`/overtime`, `/metrics`, `/tops/*`) for field-level filtering with boolean logic (AND, OR, NOT, parentheses). Available filter fields include: `socketIp`, `blockReference`, `displayScore`, `domain`, `path`, `filterOriginReason`, `ruleName`, `uaServer`, `knownBot`, `userEmail`, `httpMethod`, `httpStatusCode`, and more.

### Changed

* `human_get_traffic_data` tool description updated to document the new `searchQuery` capability, all available filter field keys, and a pointer to `human_get_raw_activities` for request-level records.

## [1.1.1] - 2026-07-02

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@humansecurity/human-mcp-server",
"description": "Model Context Protocol (MCP) server providing comprehensive cybersecurity intelligence from HUMAN Security. Offers real-time attack monitoring, threat detection, fraud prevention, PCI DSS compliance validation, and supply chain security for AI-powered applications.",
"version": "1.1.1",
"version": "1.2.0",
"type": "module",
"main": "./dist/index.cjs",
"bin": "./dist/index.cjs",
Expand Down
81 changes: 81 additions & 0 deletions src/services/cyberfraudService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
CyberfraudCustomRulesResponse,
TrafficDataInput,
TrafficDataResponse,
RawActivitiesInput,
} from '../types/cyberfraud';

const API_BASE = `${HUMAN_API_BASE}/cyberfraud`;
Expand Down Expand Up @@ -49,9 +50,26 @@ function buildBaseBody(params: TrafficDataInput) {
if (params.filters) {
body.filters = params.filters;
}
if (params.searchQuery && params.searchQuery.length > 0) {
body.searchQuery = params.searchQuery;
}
return body;
}

const MAX_INVESTIGATE_RANGE_MS = 4 * 60 * 60 * 1000; // 4 hours

function enforceInvestigateTimeRange(startTime: string, endTime: string): void {
const start = new Date(startTime).getTime();
const end = new Date(endTime).getTime();
const diffMs = end - start;
if (diffMs > MAX_INVESTIGATE_RANGE_MS) {
throw new Error(
`The investigation time range must not exceed 4 hours (raw activity queries are expensive). ` +
`Provided range: ${Math.round(diffMs / 60000)} minutes. Please narrow the window to at most 240 minutes.`,
);
}
}

async function parseApiResponse<T>(res: { json: () => Promise<unknown> }): Promise<T> {
const json = (await res.json()) as ApiEnvelope<T>;
if (!json.result) {
Expand Down Expand Up @@ -159,4 +177,67 @@ export class CyberfraudService {

return response;
}

async getRawActivities(
params: Pick<RawActivitiesInput, 'startTime' | 'endTime' | 'trafficSource' | 'filters' | 'searchQuery'> & {
limit?: number;
offset?: number;
},
): Promise<Record<string, unknown>[]> {
const clamped = clampAttackReportingTimes(params.startTime, params.endTime);
const from = Math.floor(new Date(clamped.startTime).getTime() / 1000);
const to = Math.floor(new Date(clamped.endTime).getTime() / 1000);
const url = buildTrafficDataUrl('/activities', from, to);
const body: Record<string, unknown> = {
trafficSource:
params.trafficSource && params.trafficSource.length > 0 ? params.trafficSource : ['web', 'mobile'],
};
if (params.filters) body.filters = params.filters;
body.searchQuery = params.searchQuery;
if (params.limit !== undefined) body.limit = params.limit;
if (params.offset !== undefined) body.offset = params.offset;
const res = await this.http.request(url, { method: 'POST', body });
const content = await parseApiResponse<{ results: Record<string, unknown>[] }>(res);
return content.results;
}

async getRawActivitiesCount(
params: Pick<RawActivitiesInput, 'startTime' | 'endTime' | 'trafficSource' | 'filters' | 'searchQuery'>,
): Promise<number> {
const clamped = clampAttackReportingTimes(params.startTime, params.endTime);
const from = Math.floor(new Date(clamped.startTime).getTime() / 1000);
const to = Math.floor(new Date(clamped.endTime).getTime() / 1000);
const url = buildTrafficDataUrl('/activities/count', from, to);
const body: Record<string, unknown> = {
trafficSource:
params.trafficSource && params.trafficSource.length > 0 ? params.trafficSource : ['web', 'mobile'],
};
if (params.filters) body.filters = params.filters;
body.searchQuery = params.searchQuery;
const res = await this.http.request(url, { method: 'POST', body });
const content = await parseApiResponse<{ total: number }>(res);
return content.total;
}

async fetchRawActivities(
params: RawActivitiesInput,
): Promise<{ count: number; activities: Record<string, unknown>[] }> {
const clamped = clampAttackReportingTimes(params.startTime, params.endTime);
enforceInvestigateTimeRange(clamped.startTime, clamped.endTime);

const baseParams = {
startTime: clamped.startTime,
endTime: clamped.endTime,
trafficSource: params.trafficSource,
filters: params.filters,
searchQuery: params.searchQuery,
};

const [activities, count] = await Promise.all([
this.getRawActivities({ ...baseParams, limit: params.limit ?? 20, offset: params.offset ?? 0 }),
this.getRawActivitiesCount(baseParams),
]);

return { count, activities };
}
}
92 changes: 92 additions & 0 deletions src/tools/getRawActivities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { RawActivitiesInputBaseSchema, RawActivitiesOutputSchema } from '../types/cyberfraud';
import { mcpToolHandler } from '../utils/mcpToolHandler';
import { makeStructuredResponseSchema } from '../utils/makeStructuredResponseSchema';
import type { CyberfraudService } from '../services/cyberfraudService';
import type { RawActivitiesInput } from '../types/cyberfraud';
import { DATE_FORMAT_EXAMPLE_END, DATE_FORMAT_EXAMPLE_START } from '../utils/constants';

export function registerGetRawActivities(server: McpServer, cyberfraudService: CyberfraudService) {
server.registerTool(
'human_get_raw_activities',
{
description: `Fetches raw activity records (individual request logs) and total count from HUMAN Security for a given search criteria and time window.

Use this tool to drill into specific traffic at the request level — whether investigating a Block ID, an IP, a user email, a domain, or any other supported field.

⚠️ TIME RANGE CONSTRAINT: Raw activity queries are expensive. The time window MUST be at most 4 hours.
Use short windows like the last 30, 60, or 240 minutes. If the user does not specify, default to the last 1 hour.

🎯 WHEN TO USE THIS TOOL:
• User wants to investigate a specific Block ID / Reference ID
• User wants to investigate traffic from a specific IP or CIDR
• User wants to find raw activity records for a user email, domain, path, VID, or any other field
• User wants to drill into specific incidents at the request level
• User asks for raw request logs matching a condition

📋 SEARCH QUERY (required, same syntax as human_get_traffic_data):
Use searchQuery for field-level filtering with boolean logic.
Field expression: { type: "field", key: "<fieldKey>", operator: "<op>", value: "<val>" }
Logical operator: { type: "operator", operator: "AND" | "OR" | "NOT" | "(" | ")" }

Supported field keys include: blockReference, socketIp, userEmail, domain, path, displayScore, vid, uaServer, knownBot,
incidentTypes, filterOriginReason, httpMethod, httpStatusCode, customRule,
accessTokenName, headerReferer, graphqlOperationName, graphqlOperationType,
customParam1–customParam10, socketIpOrgName, agent, and more.

🔍 KEY RESPONSE FIELDS FOR ANALYSIS (in activities[]):
• filterOriginReason — the specific reason the request was filtered/blocked
• ruleName — which rule triggered the block
• displayScore — risk score (0–100); higher = more suspicious
• incidentTypes — detected threat signals: "Bot Behavior", "Automation Tool", "Spoof", "Behavioral Anomalies", "UI Anomaly", "Bad Reputation", "Volumetric Rule", "Volumetric Anomaly", "Missing Sensor Data", "Cloud Service", "Anonymizing Service", "Denylisted Service", "Custom Denylist", "Captcha Solving Attack"
• trafficTags — traffic classification (e.g. "Blocked Requests", "Simulated Block")
• blockReference — the Block ID associated with this request
• socketIp, domain, path, userEmail, vid, httpMethod, httpStatusCode, country, browserDisplay, osFamily

📊 RESPONSE STRUCTURE:
• count: total number of matching records in the time window (always the full total, regardless of limit/offset)
• activities: matching records, newest first — up to 'limit' (default 20, max 100) starting at 'offset'

📄 PAGINATION:
• limit: how many raw records to return (1-100, default 20).
• offset: how many to skip (records are sorted newest-first). Use count + offset to page through results.

✅ USAGE PATTERNS:

1. INVESTIGATE BY BLOCK ID:
{"searchQuery": [{"type": "field", "key": "blockReference", "operator": "=", "value": "b5e0-b1d1-a54de"}], "startTime": "${DATE_FORMAT_EXAMPLE_START}", "endTime": "${DATE_FORMAT_EXAMPLE_END}"}

2. INVESTIGATE BY IP:
{"searchQuery": [{"type": "field", "key": "socketIp", "operator": "=", "value": "203.0.113.10"}], "startTime": "${DATE_FORMAT_EXAMPLE_START}", "endTime": "${DATE_FORMAT_EXAMPLE_END}"}

3. INVESTIGATE BY USER EMAIL:
{"searchQuery": [{"type": "field", "key": "userEmail", "operator": "=", "value": "user@example.com"}], "startTime": "${DATE_FORMAT_EXAMPLE_START}", "endTime": "${DATE_FORMAT_EXAMPLE_END}"}

4. INVESTIGATE BY DOMAIN + HIGH RISK:
{"searchQuery": [{"type": "field", "key": "domain", "operator": "contains", "value": "example.com"}, {"type": "operator", "operator": "AND"}, {"type": "field", "key": "displayScore", "operator": ">=", "value": 80}], "startTime": "${DATE_FORMAT_EXAMPLE_START}", "endTime": "${DATE_FORMAT_EXAMPLE_END}"}

5. INVESTIGATE BY VID:
{"searchQuery": [{"type": "field", "key": "vid", "operator": "=", "value": "abc-123-def"}], "startTime": "${DATE_FORMAT_EXAMPLE_START}", "endTime": "${DATE_FORMAT_EXAMPLE_END}"}

6. IP WITH AUTOMATION INCIDENT:
{"searchQuery": [{"type": "field", "key": "socketIp", "operator": "=", "value": "203.0.113.10"}, {"type": "operator", "operator": "AND"}, {"type": "field", "key": "incidentTypes", "operator": "=", "value": "Automation Tool"}], "startTime": "${DATE_FORMAT_EXAMPLE_START}", "endTime": "${DATE_FORMAT_EXAMPLE_END}"}

7. NARROW TO BLOCKED TRAFFIC ONLY (via filters):
{"searchQuery": [{"type": "field", "key": "socketIp", "operator": "=", "value": "203.0.113.10"}], "startTime": "${DATE_FORMAT_EXAMPLE_START}", "endTime": "${DATE_FORMAT_EXAMPLE_END}", "filters": {"trafficTags": ["blocked", "potentialBlock"]}}

⚠️ NOTES:
• searchQuery is required and must contain at least one expression.
• trafficSource defaults to ["web", "mobile"] when omitted.
• Use limit/offset to page through more than the default 20 records when count is large.
• For broader traffic analytics (overtime, metrics, tops) without raw records, use human_get_traffic_data instead.`,
inputSchema: RawActivitiesInputBaseSchema.shape,
outputSchema: makeStructuredResponseSchema(RawActivitiesOutputSchema).shape,
annotations: {
title: 'HUMAN Get Raw Activities',
readOnlyHint: true,
openWorldHint: true,
},
},
async (params: RawActivitiesInput) => mcpToolHandler(async () => cyberfraudService.fetchRawActivities(params)),
);
}
42 changes: 40 additions & 2 deletions src/tools/getTrafficData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export function registerCyberfraudGetTrafficData(server: McpServer, cyberfraudSe
├── Need URL path breakdown? → tops: ["path"]
├── Need top blocked IP orgs? → tops: ["socketIpOrgName"]
├── Security focus only? → filters: { trafficTags: ["blocked", "potentialBlock"] }
└── Multi-platform analysis? → trafficSource: ["web", "mobile"]
├── Multi-platform analysis? → trafficSource: ["web", "mobile"]
└── Need raw request-level records? → use human_get_raw_activities instead

✅ REQUEST MODES (combinable in one call):
• overtime: Per-minute time-series counts. Use seriesFields to break out by knownBot, customRule, or accessTokenName.
Expand All @@ -30,6 +31,39 @@ export function registerCyberfraudGetTrafficData(server: McpServer, cyberfraudSe
• filters.pageType: User journey (login, checkout, carding_attempt, etc.)
• filters.browserFamily / osFamily / country: Device and geo filters

🔍 SEARCH QUERY (optional, for field-level filtering with boolean logic):
Use searchQuery to filter by any specific field value. Items are either field expressions or logical operators.
Field expression: { type: "field", key: "<fieldKey>", operator: "<op>", value: "<val>" }
Logical operator: { type: "operator", operator: "AND" | "OR" | "NOT" | "(" | ")" }

Available field keys and their operators:
• socketIp (IP/CIDR): =, !=
• blockReference (Block ID): =, exists, notExists
• displayScore (risk score 0-100): =, !=, >, <, >=, <=
• incidentTypes: =, !=, exists, notExists
Valid values: "UI Anomaly", "Bot Behavior", "Automation Tool", "Spoof", "Behavioral Anomalies",
"Bad Reputation", "Volumetric Rule", "Volumetric Anomaly", "Missing Sensor Data",
"Cloud Service", "Anonymizing Service", "Denylisted Service", "Custom Denylist", "Captcha Solving Attack"
• knownBot: =, !=, exists, notExists
Example values: "Googlebot", "Bing Bot", "GPTBot", "ClaudeBot", "PerplexityBot",
"Facebook Crawler", "Twitterbot", "Pingdom", "Datadog - Synthetics", "Semrush Bot", "Ahrefs"
(150+ bots total — use exists/notExists for broad match)
• domain, path, uaServer, socketIpOrgName, userEmail, headerReferer: =, !=, contains, exists, notExists
• filterOriginReason, vid, accessTokenName: =, !=, exists, notExists
• httpStatusCode: =, !=, >, <, >=, <=
• httpMethod: =, != — values: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
• graphqlOperationType: =, !=, exists, notExists — values: query, mutation, subscription
• graphqlOperationName: =, !=, exists, notExists
• customRule: =, !=, contains, exists, notExists
• customParam1–customParam10: =, !=, contains, exists, notExists

Example — traffic from a CIDR with high risk score:
"searchQuery": [
{ "type": "field", "key": "socketIp", "operator": "=", "value": "203.0.113.0/24" },
{ "type": "operator", "operator": "AND" },
{ "type": "field", "key": "displayScore", "operator": ">=", "value": 80 }
]

✅ HIGH-VALUE PATTERNS:

1. EXECUTIVE TRAFFIC SUMMARY:
Expand All @@ -47,11 +81,15 @@ export function registerCyberfraudGetTrafficData(server: McpServer, cyberfraudSe
5. FULL SECURITY INVESTIGATION (combined):
{"startTime": "${DATE_FORMAT_EXAMPLE_START}", "endTime": "${DATE_FORMAT_EXAMPLE_END}", "metrics": true, "overtime": true, "tops": ["incidentTypes", "socketIpOrgName"], "filters": {"trafficTags": ["blocked"]}}

6. SEARCH QUERY — TOP IPs FROM A SPECIFIC CIDR:
{"startTime": "${DATE_FORMAT_EXAMPLE_START}", "endTime": "${DATE_FORMAT_EXAMPLE_END}", "tops": ["socketIp"], "searchQuery": [{"type": "field", "key": "socketIp", "operator": "=", "value": "203.0.113.0/24"}]}

⚠️ NOTES:
• At least one of overtime, metrics, or tops must be specified.
• trafficSource defaults to ["web", "mobile"] when omitted.
• app_id is derived from the API token — do not pass appIds.
• tops limit defaults to 10 (max 100). Use includeNulls to include null-valued rows.`,
• tops limit defaults to 10 (max 100). Use includeNulls to include null-valued rows.
• searchQuery and filters are additive — both are applied when provided.`,
inputSchema: TrafficDataInputBaseSchema.shape,
outputSchema: makeStructuredResponseSchema(TrafficDataOutputSchema).shape,
annotations: {
Expand Down
2 changes: 2 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { registerCodeDefenderGetHeaderInventory } from './codeDefenderGetHeaderI
import type { CyberfraudService } from '../services/cyberfraudService';
import type { CodeDefenderService } from '../services/codeDefenderService';
import { registerCyberfraudGetTrafficData } from './getTrafficData';
import { registerGetRawActivities } from './getRawActivities';

export function registerTools(
server: McpServer,
Expand All @@ -24,6 +25,7 @@ export function registerTools(
registerCyberfraudGetCustomRules(server, services.cyberfraudService);
registerCyberfraudGetAccountInfo(server, services.cyberfraudService);
registerCyberfraudGetTrafficData(server, services.cyberfraudService);
registerGetRawActivities(server, services.cyberfraudService);
}

// Register Code Defender tools if service is available
Expand Down
Loading
Loading