diff --git a/package-lock.json b/package-lock.json index 7bee359..3545f5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -424,9 +424,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -436,7 +436,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/finalhandler": { "version": "2.1.1", diff --git a/src/tools/abuse.ts b/src/tools/abuse.ts index 1d83d2e..fc2cb28 100644 --- a/src/tools/abuse.ts +++ b/src/tools/abuse.ts @@ -17,11 +17,11 @@ export function registerAbuseTools(server: McpServer) { annotations: { readOnlyHint: true, }, - description: `Decision policy: this is a single-domain tool. Use it only when the user asks for abuse contact data only. If the same IP request also needs security, ownership/company/ASN, location/city, timezone, network, or currency data, call lookup_ip once with include and targeted fields/excludes instead of chaining tools. + description: `Read-only abuse contact lookup via GET /v3/abuse. Paid only. Cost: 1 credit. Use only for abuse contact data; use lookup_ip with include=abuse when the same IP also needs location, security, ASN/company, timezone, network, or currency. -Dedicated abuse lookup via GET /v3/abuse. Paid only. Cost: 1 credit. Returns JSON rooted at ip and abuse with route, registered country, name, organization, kind, address, emails, and phone_numbers for reporting abuse. +Returns { ip, abuse } with route, country, name, organization, kind, address, emails, and phone_numbers for reporting abuse. -fields/excludes use comma-separated paths such as abuse.emails; ip is always returned. force_refresh bypasses this server's cache only when the user asks. Use lookup_ip with include=abuse when the same request also needs geolocation or other IP domains. If this tool is used, call it once per IP target and post-process locally.`, +fields/excludes use comma-separated abuse.* paths such as abuse.emails; ip is always returned. force_refresh bypasses cache and makes a fresh upstream request only when the user asks. Call once per IP target and post-process locally.`, inputSchema: { ip: z .string() @@ -33,18 +33,20 @@ fields/excludes use comma-separated paths such as abuse.emails; ip is always ret .string() .optional() .describe( - "Comma-separated fields to return (e.g. emails,organization). Reduces response size. Works on all plans." + "Comma-separated abuse fields to return (e.g. abuse.emails,abuse.organization). Reduces response size." ), excludes: z .string() .optional() .describe( - "Comma-separated fields to exclude from response (e.g. phone_numbers,address)." + "Comma-separated abuse fields to exclude from response (e.g. abuse.phone_numbers,abuse.address)." ), force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached abuse contact data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { diff --git a/src/tools/asn.ts b/src/tools/asn.ts index 7f4525b..6a18726 100644 --- a/src/tools/asn.ts +++ b/src/tools/asn.ts @@ -92,11 +92,9 @@ export function registerAsnTools(server: McpServer) { annotations: { readOnlyHint: true, }, - description: `Detailed ASN lookup via GET /v3/asn. Paid only. Cost: 1 credit per successful lookup. Query by AS number or IP. Returns JSON rooted at asn, with core ASN details and optional peers, downstreams, upstreams, routes, and raw whois_response. + description: `Read-only ASN enrichment via GET /v3/asn. Paid only. Cost: 1 credit. Query by asn or ip; asn takes priority over ip. Use for ASN relationships, route prefixes, allocation details, or WHOIS; use lookup_ip for basic ASN with geolocation. -Use lookup_ip for basic ASN fields included with IP geolocation. Use lookup_asn when the user asks for ASN relationships, route prefixes, allocation details, or WHOIS. Decide include values before the call, then use fields/excludes dot paths to trim the response locally. - -asn takes priority over ip. include accepts peers, downstreams, upstreams, routes, and whois_response. fields/excludes can use full paths such as asn.upstreams.as_number or root-relative paths such as upstreams.as_number. force_refresh bypasses this server's cache only when the user asks. Call this tool once per ASN/IP target and post-process locally.`, +Returns { asn } with core fields plus optional peers, downstreams, upstreams, routes, and whois_response. include accepts peers, downstreams, upstreams, routes, and whois_response. fields/excludes can use full paths such as asn.upstreams.as_number or root-relative paths such as upstreams.as_number. force_refresh bypasses cache and makes a fresh upstream request only when the user asks.`, inputSchema: { asn: z .string() @@ -131,7 +129,9 @@ asn takes priority over ip. include accepts peers, downstreams, upstreams, route force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached ASN data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { diff --git a/src/tools/astronomy.ts b/src/tools/astronomy.ts index e12124e..6525b3c 100644 --- a/src/tools/astronomy.ts +++ b/src/tools/astronomy.ts @@ -47,13 +47,11 @@ export function registerAstronomyTools(server: McpServer) { annotations: { readOnlyHint: true, }, - description: `Single-date astronomy lookup via GET /v3/astronomy. Works on free and paid plans. Cost: 1 credit. Look up by coordinates, location, or IP, with optional date and elevation. If no location selector is provided, the API uses the caller's IP location. + description: `Read-only single-date astronomy lookup via GET /v3/astronomy. Works on free and paid plans. Cost: 1 credit. Use for one date and real-time sun/moon position; use get_astronomy_time_series for daily sunrise, moon, and twilight data across a date range. -Returns location details plus astronomy data such as sunrise, sunset, moonrise, moonset, morning and evening twilight blocks, day length, sun and moon status, positions, and moon phase fields. +Returns { location, astronomy } plus ip for IP/caller lookups. astronomy includes date/current_time, sunrise/sunset, moonrise/moonset, morning/evening twilight blocks, day_length, sun and moon status, altitude, azimuth, distance, moon_phase, moon_illumination_percentage, and moon_angle. -Use this tool for one date, especially when the answer needs real-time positional fields such as sun or moon altitude and azimuth. Use get_astronomy_time_series instead for daily sunrise, sunset, moon, or twilight data across a date range. - -lat and long must be provided together; date must be YYYY-MM-DD; elevation must be 0-10000 meters. time_zone changes timestamp formatting to include the full date. lang only changes location fields; non-English lang is paid-only and returns 401 on free plans.`, +Select by lat/long, location, IP, or caller IP when no selector is provided. lat and long must be provided together; date must be YYYY-MM-DD; elevation must be 0-10000 meters. time_zone changes timestamp formatting to include full dates. lang only changes location fields; non-English lang is paid-only and returns 401 on free plans.`, inputSchema: { lat: z .string() @@ -149,11 +147,11 @@ lat and long must be provided together; date must be YYYY-MM-DD; elevation must annotations: { readOnlyHint: true, }, - description: `Daily astronomy time series via GET /v3/astronomy/timeSeries for up to 90 days. Works on free and paid plans. Cost: 1 credit per request. + description: `Read-only daily astronomy series via GET /v3/astronomy/timeSeries. Works on free and paid plans. Cost: 1 credit per request. Use for date ranges up to 90 days; use get_astronomy for one date or real-time sun/moon altitude and azimuth. -Returns location details plus an astronomy array with one daily entry per date. Use get_astronomy instead when you need real-time positional fields such as sun or moon altitude and azimuth. +Returns { location, astronomy: [...] } with one daily item per date containing sunrise/sunset, moonrise/moonset, twilight blocks, day_length, sun/moon status, and moon_phase. Select by lat/long, location, IP, or caller IP when no selector is provided. -Location can be specified by coordinates, city/address, or IP. If no location is given, uses the caller's IP. dateStart and dateEnd are required YYYY-MM-DD values with a maximum 90-day span. lat and long must be provided together; elevation must be 0-10000 meters. time_zone changes timestamp formatting to include the full date. lang only changes location fields; non-English lang is paid-only and returns 401 on free plans. force_refresh bypasses this server's cache only when the user asks.`, +dateStart and dateEnd are required YYYY-MM-DD values with a maximum 90-day span. lat and long must be provided together; elevation must be 0-10000 meters. time_zone changes timestamp formatting to include full dates. lang only changes location fields; non-English lang is paid-only and returns 401 on free plans. force_refresh bypasses cache and makes a fresh upstream request only when the user asks.`, inputSchema: { lat: z .string() @@ -206,7 +204,9 @@ Location can be specified by coordinates, city/address, or IP. If no location is force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached astronomy time-series data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { diff --git a/src/tools/geolocation.ts b/src/tools/geolocation.ts index fd67058..d2678ae 100644 --- a/src/tools/geolocation.ts +++ b/src/tools/geolocation.ts @@ -214,13 +214,11 @@ export function registerGeolocationTools(server: McpServer) { annotations: { readOnlyHint: true, }, - description: `Unified IP lookup via GET /v3/ipgeo. Cost: 1 credit for base IP data; security adds 2 credits and abuse adds 1 credit when included. Use this first when one IP request needs multiple domains such as location, company/ASN, network, timezone, currency, security, or abuse. + description: `Read-only unified IP lookup via GET /v3/ipgeo. Base lookup costs 1 credit; include=security adds 2 credits and include=abuse adds 1 credit. Use this first when one IP or domain needs multiple data domains: location, company/ASN, network, timezone, currency, security, abuse, user_agent, hostname, geo_accuracy, or dma_code. -Free plan supports core location data, country metadata, currency, time_zone, basic ASN, and fields/excludes. Paid plan adds domain lookup, company, network, extended ASN, non-English lang, and include modules such as security or abuse. If fields reference an include-only module, this server infers the required include automatically. +Returns root IP/domain data plus location, country_metadata, currency, asn, network, company, and time_zone objects. Paid include modules add security, abuse, user_agent, hostname, dma_code, or geo_accuracy. Free plans support core location, country_metadata, currency, time_zone, basic ASN, fields, and excludes; paid plans add domain lookup, company, network, extended ASN, non-English lang, and include modules. -Returns JSON with root IP data plus objects such as location, country_metadata, currency, asn, network, company, and time_zone. Requested include modules add objects such as security, abuse, user_agent, hostname, dma_code, or geo_accuracy. - -Omit ip to use the caller's IP. fields/excludes use comma-separated dot paths; ip is always returned, non-existent excludes do not error, and include takes priority over fields/excludes. Use lookup_asn only for peers, upstreams, downstreams, routes, or WHOIS. Use check_security or get_abuse_contact only for single-domain lookups.`, +ip omitted means caller IP. fields/excludes use comma-separated dot paths; ip is always returned, unknown excludes do not error, and include wins over fields/excludes. This server auto-adds include modules referenced by fields. Use lookup_asn only for peers, upstreams, downstreams, routes, or WHOIS; use check_security or get_abuse_contact only for security-only or abuse-only requests.`, inputSchema: { ip: z .string() @@ -255,7 +253,9 @@ Omit ip to use the caller's IP. fields/excludes use comma-separated dot paths; i force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to bypass cached lookup_ip data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { @@ -305,9 +305,9 @@ Omit ip to use the caller's IP. fields/excludes use comma-separated dot paths; i annotations: { readOnlyHint: true, }, - description: `Bulk IP lookup via POST /v3/ipgeo-bulk. Paid only. Cost: 1 credit per valid IP for base geolocation. This MCP server accepts up to ${MAX_BULK_ITEMS.toLocaleString()} IPs per request. + description: `Read-only bulk IP lookup via POST /v3/ipgeo-bulk. Paid only. Base geolocation costs 1 credit per valid IP, with security adding 2 and abuse adding 1 per valid IP. This MCP server accepts up to ${MAX_BULK_ITEMS.toLocaleString()} IPs per request. -Use it when multiple IPs need location or mixed IP domains. Include modules such as security or abuse add their normal per-valid-IP credit costs. Private, bogon, and malformed IPs are not billed by the upstream API. fields, excludes, lang, and include behave like lookup_ip for each item. For bulk security-only checks, prefer bulk_security_check.`, +Use it when multiple IPs or domains need location data or mixed IP domains. Private, bogon, and malformed IPs are not billed by the upstream API. fields, excludes, lang, and include behave like lookup_ip for each item; this server also infers include modules from fields. For security-only batches, use bulk_security_check.`, inputSchema: { ips: z .array(z.string()) @@ -343,7 +343,9 @@ Use it when multiple IPs need location or mixed IP domains. Include modules such force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to bypass cached bulk geolocation data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { @@ -390,7 +392,7 @@ Use it when multiple IPs need location or mixed IP domains. Include modules such readOnlyHint: true, }, description: - "Return the public IP address of the machine running this MCP server via GET /v3/getip. No API key, account, or credits are required. Returns a plain IP address string, not geolocation data. Use this only when the user asks for the server or caller public IP; use lookup_ip for location, ASN, timezone, currency, security, or abuse data.", + "Return the public IP address of the machine running this MCP server via GET /v3/getip. No input parameters, API key, account, or credits are required. Returns a plain IP address string, not geolocation data. Use this only when the user asks for the server or caller public IP; use lookup_ip for location, ASN, timezone, currency, security, or abuse data.", inputSchema: {}, }, async () => { @@ -412,11 +414,9 @@ Use it when multiple IPs need location or mixed IP domains. Include modules such annotations: { readOnlyHint: true, }, - description: `Decision policy: this is a single-domain tool. Use it only when the user asks for ownership data (company/ASN) only. If the same IP request also needs security, abuse, location/city, timezone, network, or currency data, call lookup_ip once with include and targeted fields/excludes instead of chaining tools. - -Ownership lookup via GET /v3/ipgeo with company and ASN only. Paid only. Cost: 1 credit. Returns JSON containing company and asn objects: company name/type/domain plus ASN as_number, organization, country, type, domain, date_allocated, and rir when available. + description: `Read-only ownership lookup via GET /v3/ipgeo. Paid only. Cost: 1 credit. Use only for company/ASN ownership; use lookup_ip once if the same IP request also needs location, security, abuse, network, timezone, or currency. -ip is optional and omitted means the caller's IP. force_refresh bypasses this server's cache only when the user asks to refresh or rerun. If this tool is used, call it once per IP target and post-process locally.`, +Returns { company, asn }: company name/type/domain plus ASN as_number, organization, country, type, domain, date_allocated, and rir when available. ip omitted means caller IP. force_refresh bypasses cache and makes a fresh upstream request only when the user asks. Call once per IP target and post-process locally.`, inputSchema: { ip: z .string() @@ -427,7 +427,9 @@ ip is optional and omitted means the caller's IP. force_refresh bypasses this se force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached ownership data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { @@ -459,9 +461,9 @@ ip is optional and omitted means the caller's IP. force_refresh bypasses this se annotations: { readOnlyHint: true, }, - description: `Currency and country metadata lookup via GET /v3/ipgeo with currency and country_metadata only. Works on free and paid plans. Cost: 1 credit. + description: `Read-only currency and country metadata lookup via GET /v3/ipgeo. Works on free and paid plans. Cost: 1 credit per successful lookup. -Returns JSON containing currency and country_metadata objects, including currency code/name/symbol and country-level metadata returned by the upstream API. ip is optional and omitted means the caller's IP. force_refresh bypasses this server's cache only when the user asks. +Returns { currency, country_metadata }: currency code/name/symbol plus country calling_code, tld, and languages. ip selects the IP used to derive country and currency; omit it for caller IP. force_refresh bypasses cache and makes a fresh upstream request only when the user asks. Use this tool for currency-only or country-metadata-only requests. If the request needs more IP data, prefer one lookup_ip call with targeted fields/excludes.`, inputSchema: { @@ -469,12 +471,14 @@ Use this tool for currency-only or country-metadata-only requests. If the reques .string() .optional() .describe( - "IPv4 or IPv6 address to look up. Omit to check the caller's IP." + "IPv4 or IPv6 address whose country determines the returned currency and country metadata. Omit to use the caller's IP." ), force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached currency/country data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { @@ -506,9 +510,9 @@ Use this tool for currency-only or country-metadata-only requests. If the reques annotations: { readOnlyHint: true, }, - description: `Network lookup via GET /v3/ipgeo with network only. Paid only. Cost: 1 credit. Returns JSON containing the network object, including route prefix, connection type, anycast status, and related routing fields returned by the upstream API. + description: `Read-only network lookup via GET /v3/ipgeo. Paid only. Cost: 1 credit. Returns { network } with route CIDR prefix, connection_type, and is_anycast. -ip is optional and omitted means the caller's IP. force_refresh bypasses this server's cache only when the user asks. Use this tool for network-only requests. If the request also needs other IP domains, prefer one lookup_ip call with include plus targeted fields/excludes.`, +ip omitted means caller IP. force_refresh bypasses cache and makes a fresh upstream request only when the user asks. Use this for network-only requests; use lookup_ip once if the request also needs location, ASN/company, timezone, currency, security, or abuse.`, inputSchema: { ip: z .string() @@ -519,7 +523,9 @@ ip is optional and omitted means the caller's IP. force_refresh bypasses this se force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached network data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { diff --git a/src/tools/security.ts b/src/tools/security.ts index e559f69..7b19d8f 100644 --- a/src/tools/security.ts +++ b/src/tools/security.ts @@ -46,11 +46,11 @@ export function registerSecurityTools(server: McpServer) { annotations: { readOnlyHint: true, }, - description: `Decision policy: this is a single-domain tool. Use it only when the user asks for security/threat data only. If the same IP request also needs ownership/company/ASN, location/city, network, timezone, currency, or abuse data, call lookup_ip once with include and targeted fields/excludes instead of chaining tools. + description: `Read-only security lookup via GET /v3/security. Paid only. Cost: 2 credits. Use only for security/threat data; use lookup_ip with include=security when the same request also needs location, ASN/company, network, timezone, currency, or abuse. -Dedicated IP security lookup via GET /v3/security. Paid only. Cost: 2 credits. Returns JSON rooted at ip and security with threat score plus VPN, proxy, Tor, bot, spam, attacker, relay, anonymity, and cloud-provider indicators. +Returns { ip, security } with threat_score, VPN, proxy, residential proxy, Tor, relay, anonymity, bot, spam, known attacker, and cloud-provider fields; provider names, confidence scores, and last_seen dates appear when available. -fields/excludes use comma-separated dot paths such as security.threat_score; ip is always returned. force_refresh bypasses this server's cache only when the user asks. Use lookup_ip with include=security when the same request also needs other IP domains. If this tool is used, call it once per IP target and post-process locally.`, +fields/excludes use comma-separated security.* dot paths; ip is always returned. force_refresh bypasses cache and makes a fresh upstream request only when the user asks. Call once per IP target and post-process locally.`, inputSchema: { ip: z .string() @@ -73,7 +73,9 @@ fields/excludes use comma-separated dot paths such as security.threat_score; ip force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached security data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, @@ -116,11 +118,9 @@ fields/excludes use comma-separated dot paths such as security.threat_score; ip annotations: { readOnlyHint: true, }, - description: `Decision policy: this is a single-domain bulk tool. Use it only when the user asks for security/threat data only. If each IP request also needs other domains (ownership, location, network, timezone, currency, or abuse), call bulk_lookup_ip once with include and targeted fields/excludes. - -Bulk IP security lookup via POST /v3/security-bulk for up to ${MAX_BULK_ITEMS.toLocaleString()} IPs per MCP request. Paid only. Cost: 2 credits per valid IP. Private, bogon, and malformed IPs are not billed by the upstream API. + description: `Read-only bulk security lookup via POST /v3/security-bulk. Paid only. Cost: 2 credits per valid IP. This MCP server accepts up to ${MAX_BULK_ITEMS.toLocaleString()} IPs; private, bogon, and malformed IPs are not billed by the upstream API. -Returns one security result per valid IP with the same security object as check_security. fields/excludes use security.* dot paths for each item; force_refresh bypasses this server's cache only when the user asks. Use bulk_lookup_ip with include=security when the same batch also needs geolocation or other IP domains. Call this tool once per IP batch and post-process locally.`, +Use only for security-only batches; use bulk_lookup_ip with include=security when each IP also needs geolocation or other IP domains. Returns one { ip, security } result per valid IP with the same security fields as check_security. fields/excludes use security.* dot paths per item. force_refresh bypasses cache and makes a fresh upstream request only when the user asks.`, inputSchema: { ips: z .array(z.string()) @@ -144,7 +144,9 @@ Returns one security result per valid IP with the same security object as check_ force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached bulk security data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { diff --git a/src/tools/timezone.ts b/src/tools/timezone.ts index 638c8d6..3acd993 100644 --- a/src/tools/timezone.ts +++ b/src/tools/timezone.ts @@ -17,13 +17,11 @@ export function registerTimezoneTools(server: McpServer) { annotations: { readOnlyHint: true, }, - description: `Timezone lookup via GET /v3/timezone. Works on free and paid plans. Cost: 1 credit. Look up current local time and timezone metadata by IANA timezone, coordinates, location, IP, airport code, or UN/LOCODE. + description: `Read-only timezone lookup via GET /v3/timezone. Works on free and paid plans. Cost: 1 credit. Use when the user asks for one place, IP, airport, UN/LOCODE, or IANA timezone's current local time or timezone metadata; use convert_timezone for source-to-destination conversion. -Returns location details plus a time_zone object with offsets, current time values, date/time variants, abbreviations, DST status, and transition dates. Airport lookups also include airport details. +Returns { time_zone } plus location, airport, city, or ip context depending on selector. time_zone includes name, offset, offset_with_dst, current_time, current_time_unix, date/time variants, time_24, time_12, timezone abbreviations, is_dst, dst_savings, dst_exists, and dst_start/dst_end when available. -Use this tool when the user asks what timezone or current local time applies to one place, IP, airport, or UN/LOCODE. Use convert_timezone instead when the user provides a source and destination and asks to convert a time between them. - -Parameter priority follows the upstream API: tz, then lat/long, location, ip, iata_code, icao_code, then lo_code. lat and long must be provided together. The lang parameter only changes location fields; non-English lang is paid-only and returns 401 on free plans.`, +Selector priority is tz, lat/long, location, ip, iata_code, icao_code, then lo_code. lat and long must be provided together. lang only changes location fields; non-English lang is paid-only and returns 401 on free plans.`, inputSchema: { tz: z .string() @@ -112,11 +110,9 @@ Parameter priority follows the upstream API: tz, then lat/long, location, ip, ia annotations: { readOnlyHint: true, }, - description: `Convert time between two locations via GET /v3/timezone/convert. Works on free and paid plans. Cost: 1 credit. - -Use this when converting a supplied time or current time from one source to one destination. Use get_timezone instead for one place's current time or timezone metadata. + description: `Read-only time conversion via GET /v3/timezone/convert. Works on free and paid plans. Cost: 1 credit. Use only when the user provides one source selector and one destination selector; use get_timezone for a single place's current time or metadata. -Specify one complete source selector and one complete destination selector by IANA timezone, coordinates, location, airport code, or UN/LOCODE. Coordinate selectors require both lat and long on each side. time is optional and must be yyyy-MM-dd HH:mm or yyyy-MM-dd HH:mm:ss when supplied. Returns original time, converted time, diff_hour, and diff_min.`, +Source/destination selectors are tz_from/tz_to, location_from/location_to, iata_from/iata_to, icao_from/icao_to, locode_from/locode_to, or lat_from+long_from and lat_to+long_to. time is optional and must be yyyy-MM-dd HH:mm or yyyy-MM-dd HH:mm:ss. Returns original time, converted time, diff_hour, and diff_min.`, inputSchema: { time: z .string() diff --git a/src/tools/useragent.ts b/src/tools/useragent.ts index 961c673..7a727e3 100644 --- a/src/tools/useragent.ts +++ b/src/tools/useragent.ts @@ -24,9 +24,9 @@ export function registerUserAgentTools(server: McpServer) { annotations: { readOnlyHint: true, }, - description: `Parse an explicit user-agent string via POST /v3/user-agent. Paid only for this POST-based tool. Cost: 1 credit per successful user-agent. Returns JSON with parsed user_agent_string, name, version, version_major, device, engine, and operating_system fields; device and OS types can classify Robot, Hacker, Anonymized, or Unknown values. + description: `Read-only custom user-agent parsing via POST /v3/user-agent. Paid only for POST payload parsing. Cost: 1 credit per successful string. This tool parses only the explicit uaString value; it cannot read caller headers or transport metadata. -uaString must be the exact non-empty user-agent string to parse. This MCP tool does not infer a caller user-agent from the MCP transport. force_refresh bypasses this server's cache only when the user asks. Use bulk_parse_user_agent for multiple strings.`, +Returns { user_agent_string, name, version, version_major, device, engine, operating_system }; device.type and operating_system.type can include Desktop, Mobile, Robot, Hacker, Anonymized, or Unknown. uaString must be the exact non-empty user-agent string to parse. force_refresh bypasses cache and makes a fresh upstream request only when the user asks. Use bulk_parse_user_agent for multiple strings.`, inputSchema: { uaString: z .string() @@ -36,7 +36,9 @@ uaString must be the exact non-empty user-agent string to parse. This MCP tool d force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached user-agent parsing data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { @@ -69,9 +71,9 @@ uaString must be the exact non-empty user-agent string to parse. This MCP tool d annotations: { readOnlyHint: true, }, - description: `Bulk user-agent parsing via POST /v3/user-agent-bulk for up to ${MAX_BULK_ITEMS.toLocaleString()} strings per MCP request. Paid only. Cost: 1 credit per successful user-agent string. + description: `Read-only bulk user-agent parsing via POST /v3/user-agent-bulk. Paid only. Cost: 1 credit per successful string. This MCP server accepts up to ${MAX_BULK_ITEMS.toLocaleString()} explicit user-agent strings. -Returns one parsed result per string with the same user_agent_string, name, version, version_major, device, engine, and operating_system fields as parse_user_agent. uaStrings must be a non-empty array of explicit user-agent strings. force_refresh bypasses this server's cache only when the user asks. Use parse_user_agent for one string.`, +Returns one parsed object per string with user_agent_string, name, version, version_major, device, engine, and operating_system fields. uaStrings must be a non-empty array of exact user-agent strings; use parse_user_agent for one string. force_refresh bypasses cache and makes a fresh upstream request only when the user asks.`, inputSchema: { uaStrings: z .array(z.string()) @@ -83,7 +85,9 @@ Returns one parsed result per string with the same user_agent_string, name, vers force_refresh: z .boolean() .optional() - .describe("Default false. Leave unset unless the user asks to refresh or rerun."), + .describe( + "Default false. Set true only when the user asks to refresh cached bulk user-agent parsing data; a successful refresh makes a new upstream request and can consume credits." + ), }, }, async (params) => { diff --git a/tests/mcp.protocol.test.mjs b/tests/mcp.protocol.test.mjs index 96063f4..16eccc7 100644 --- a/tests/mcp.protocol.test.mjs +++ b/tests/mcp.protocol.test.mjs @@ -171,18 +171,18 @@ test("timezone and astronomy tool docs include Glama-facing selection guidance", ); assert.match( timezoneSource, - /Use this tool when the user asks what timezone or current local time applies/i + /Use when the user asks for one place, IP, airport, UN\/LOCODE/i ); - assert.match(timezoneSource, /Use convert_timezone instead/i); + assert.match(timezoneSource, /use convert_timezone for source-to-destination conversion/i); assert.match( astronomySource, /non-English lang is paid-only and returns 401 on free plans\./ ); assert.match( astronomySource, - /If no location selector is provided, the API uses the caller's IP location\./ + /caller IP when no selector is provided/i ); - assert.match(astronomySource, /Use get_astronomy_time_series instead/i); + assert.match(astronomySource, /use get_astronomy_time_series for daily sunrise/i); }); test("tool descriptions include response shape and parameter semantics for Glama scoring", async () => { @@ -192,16 +192,16 @@ test("tool descriptions include response shape and parameter semantics for Glama const abuseSource = await readRepoFile("src/tools/abuse.ts"); const userAgentSource = await readRepoFile("src/tools/useragent.ts"); - assert.match(geolocationSource, /Returns JSON with root IP data/); + assert.match(geolocationSource, /Returns root IP\/domain data/); assert.match(geolocationSource, /fields\/excludes use comma-separated dot paths/); assert.match(geolocationSource, /not geolocation data/); assert.match(geolocationSource, /Private, bogon, and malformed/); - assert.match(securitySource, /Returns JSON rooted at ip and security/); + assert.match(securitySource, /Returns \{ ip, security \}/); assert.match(securitySource, /security\.\* dot paths/); - assert.match(asnSource, /Returns JSON rooted at asn/); + assert.match(asnSource, /Returns \{ asn \}/); assert.match(asnSource, /asn takes priority over ip/); - assert.match(abuseSource, /Returns JSON rooted at ip and abuse/); - assert.match(userAgentSource, /Paid only for this POST-based tool/); + assert.match(abuseSource, /Returns \{ ip, abuse \}/); + assert.match(userAgentSource, /Paid only for POST payload parsing/); assert.match(userAgentSource, /uaString must be the exact non-empty user-agent string/); }); diff --git a/tests/tools.integration.test.mjs b/tests/tools.integration.test.mjs index 436afea..6be34a9 100644 --- a/tests/tools.integration.test.mjs +++ b/tests/tools.integration.test.mjs @@ -350,19 +350,19 @@ test("registers all MCP tools with expected metadata", async (t) => { assert.match( tools.get("check_security").definition.description, - /single-domain tool/i + /Use only for security\/threat data/i ); assert.match( tools.get("get_abuse_contact").definition.description, - /single-domain tool/i + /Use only for abuse contact data/i ); assert.match( tools.get("lookup_company").definition.description, - /single-domain tool/i + /Use only for company\/ASN ownership/i ); assert.match( tools.get("lookup_asn").definition.description, - /Call this tool once per ASN\/IP target/i + /Query by asn or ip; asn takes priority/i ); const bulkLookupIpsSchema = tools.get("bulk_lookup_ip").definition.inputSchema.ips;