@kaminari-ad/mcp 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.13.0] - 2026-08-20
11
+
12
+ ### Added
13
+
14
+ - Added `regexp_request_url` support to `create_custom_rule`,
15
+ `test_custom_rule`, and `update_custom_rule`. Create and preview inputs
16
+ validate the strict config (`pattern`, optional `flags: "" | "i"`) and fixed
17
+ `page` target before dispatch; update documents the API-enforced,
18
+ full-replacement contract because its immutable rule type is not part of the
19
+ update payload. Fresh scans evaluate up to 5,000 captured URLs; stored-scan
20
+ tests and rechecks reconstruct a reduced request tree and are best-effort.
21
+
10
22
  ## [0.12.0] - 2026-08-07
11
23
 
12
24
  ### Security
@@ -1025,7 +1037,8 @@ Initial public release. The first version that ships to npm under
1025
1037
  need them.
1026
1038
  - Invoice PDF fetcher — same reason.
1027
1039
 
1028
- [Unreleased]: https://github.com/kaminari-ad/mcp/compare/v0.12.0...HEAD
1040
+ [Unreleased]: https://github.com/kaminari-ad/mcp/compare/v0.13.0...HEAD
1041
+ [0.13.0]: https://github.com/kaminari-ad/mcp/compare/v0.12.0...v0.13.0
1029
1042
  [0.12.0]: https://github.com/kaminari-ad/mcp/compare/v0.11.0...v0.12.0
1030
1043
  [0.11.0]: https://github.com/kaminari-ad/mcp/compare/v0.10.0...v0.11.0
1031
1044
  [0.10.0]: https://github.com/kaminari-ad/mcp/compare/v0.8.0...v0.10.0
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { NAME, VERSION, err, ok } from './chunk-TMUGAKI2.js';
2
+ import { NAME, VERSION, err, ok } from './chunk-VFDW3UL2.js';
3
3
  import process from 'process';
4
4
  import { z } from 'zod';
5
5
 
@@ -160,10 +160,10 @@ async function main() {
160
160
  }
161
161
  const config = configResult.value;
162
162
  if (config.transport === "stdio") {
163
- const { bootstrapStdio } = await import('./stdio-bootstrap-JW6I4H3U.js');
163
+ const { bootstrapStdio } = await import('./stdio-bootstrap-VM6OOG2O.js');
164
164
  return bootstrapStdio(config);
165
165
  }
166
- const { bootstrapHttp } = await import('./http-bootstrap-Y4YKT7KV.js');
166
+ const { bootstrapHttp } = await import('./http-bootstrap-ROIMZVK2.js');
167
167
  return bootstrapHttp(config);
168
168
  }
169
169
  main().then(
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { err, ok } from './chunk-TMUGAKI2.js';
2
+ import { err, ok } from './chunk-VFDW3UL2.js';
3
3
  import { createHash, randomUUID } from 'crypto';
4
4
  import createClient from 'openapi-fetch';
5
5
  import { fetch } from 'undici';
@@ -3944,19 +3944,47 @@ var updateCampaignTool = {
3944
3944
  return ok(result.value);
3945
3945
  }
3946
3946
  };
3947
+ var requestUrlRuleConfigSchema = z.object({
3948
+ pattern: z.string().min(1).max(4096).describe("Non-empty request-URL regular expression, at most 4,096 characters."),
3949
+ flags: z.enum(["", "i"]).optional().describe("Omit or use '' for case-sensitive matching; use 'i' to ignore case.")
3950
+ }).strict();
3951
+ var REQUEST_URL_RULE_CONFIG_DOC = "For `rule_type='regexp_request_url'`, `config` must be exactly `{ pattern: string, flags?: '' | 'i' }`: pattern is non-empty and at most 4,096 characters; omit flags or use `''` for case-sensitive matching, or `'i'` for case-insensitive matching. `target` must be `'page'`. Fresh scans inspect up to 5,000 captured request URLs. Tests and rechecks of stored scans reconstruct main-frame hops plus up to 200 persisted subrequests with selected resource types omitted, so historical matching is best-effort.";
3952
+ function requestUrlRuleInputError(input) {
3953
+ if (input.rule_type !== "regexp_request_url") return null;
3954
+ if (input.target !== void 0 && input.target !== "page") {
3955
+ return {
3956
+ kind: "invalid-input",
3957
+ message: "regexp_request_url requires target='page'.",
3958
+ fieldErrors: { target: ["Must be 'page' for regexp_request_url."] }
3959
+ };
3960
+ }
3961
+ const parsed = requestUrlRuleConfigSchema.safeParse(input.config);
3962
+ if (parsed.success) return null;
3963
+ const issue = parsed.error.issues[0];
3964
+ const field = issue?.path.length ? `config.${issue.path.join(".")}` : "config";
3965
+ const message = issue?.message ?? "Invalid request-URL rule config.";
3966
+ return {
3967
+ kind: "invalid-input",
3968
+ message: `Invalid regexp_request_url config: ${message}`,
3969
+ fieldErrors: { [field]: [message] }
3970
+ };
3971
+ }
3947
3972
  var COMBO_MATCH_SCOPE_DOC = 'For `rule_type=\'combo\'` the optional rule-level key `match_scope` decides where conditions are counted. `"scan"` (the default, and the behaviour when the key is absent) checks them against the union of all tags on the scan. With `"url"`, every condition must be satisfied by tags on the same link, and the tag is assigned to that link \u2014 so a rule built only from link-less tags (AI verdicts, crawler behaviour) has no link to attach to and will never match; keep those on the whole scan. Mixing the two kinds does not help either: AI verdicts and per-link detections reach the engine in different scan parts and are not evaluated together today, so such a combo fires under neither scope. Give a `"url"` rule at least one positive condition (`all_of` / `any_of` / `tag_category` + `count_gte`) \u2014 a config carrying only `none_of` matches every link that merely lacks those tags, and the API does not reject that on this path. Thresholds stay "N or more" (`count_gte` / `any_of_min`) in both scopes; no other `match_scope` value is accepted.';
3948
3973
  var COMBO_MATCH_SCOPE_READ_DOC = 'For `rule_type=\'combo\'` the returned `config` may carry the rule-level key `match_scope` ("scan" = count conditions across the whole scan, "url" = every condition must be satisfied by tags on the same link). Resend it verbatim when updating: `update_custom_rule` replaces `config` wholesale, so dropping the key reverts the rule to whole-scan matching.';
3949
3974
  var ruleConfigField = z.record(z.unknown()).superRefine((config, ctx) => {
3950
3975
  const scope = config["match_scope"];
3951
- if (scope === void 0 || scope === "scan" || scope === "url") {
3952
- return;
3976
+ if (scope !== void 0 && scope !== "scan" && scope !== "url") {
3977
+ ctx.addIssue({
3978
+ code: z.ZodIssueCode.custom,
3979
+ path: ["match_scope"],
3980
+ message: 'config.match_scope must be "scan" or "url". Omit the key for scan-wide matching (the default).'
3981
+ });
3953
3982
  }
3954
- ctx.addIssue({
3955
- code: z.ZodIssueCode.custom,
3956
- path: ["match_scope"],
3957
- message: 'config.match_scope must be "scan" or "url". Omit the key for scan-wide matching (the default).'
3958
- });
3959
3983
  });
3984
+ var requestUrlAwareRuleConfigField = z.union([
3985
+ requestUrlRuleConfigSchema,
3986
+ ruleConfigField
3987
+ ]);
3960
3988
 
3961
3989
  // src/application/tools/custom-rules/create-custom-rule.tool.ts
3962
3990
  var CreateCustomRuleInputShape = {
@@ -3967,18 +3995,18 @@ var CreateCustomRuleInputShape = {
3967
3995
  "Tag slug to assign on match. Empty = create-only (advanced). The API auto-registers a custom tag definition for this slug with `display_name = name`. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `scope=system`); colliding requests return 422 with code `checking.system_slug_reserved`. For `rule_type='llm'` use `config.tags` keys instead and leave `tag_slug` empty."
3968
3996
  ),
3969
3997
  rule_type: z.string().max(50).describe(
3970
- "Rule engine. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`. The API validates."
3998
+ "Rule engine. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `regexp_request_url`, `blacklist_domain`, `combo`, `llm`. `regexp_url` checks redirect-chain URLs only; `regexp_request_url` checks captured network and subresource URLs. The API validates."
3971
3999
  ),
3972
- config: ruleConfigField.describe(
3973
- "Rule-type-specific configuration object. Shape depends on `rule_type`. For `rule_type='llm'` the shape is `{ prompt: string, tags: { <tag_slug>: <description>, ... } }`; each key in `config.tags` is auto-registered as a custom tag definition AND must not collide with a system slug (same 422 contract as `tag_slug`). " + COMBO_MATCH_SCOPE_DOC
4000
+ config: requestUrlAwareRuleConfigField.describe(
4001
+ "Rule-type-specific configuration object. Shape depends on `rule_type`. " + REQUEST_URL_RULE_CONFIG_DOC + " `regexp_url` remains redirect-chain-only. For `rule_type='llm'` the shape is `{ prompt: string, tags: { <tag_slug>: <description>, ... } }`; each key in `config.tags` is auto-registered as a custom tag definition AND must not collide with a system slug (same 422 contract as `tag_slug`). " + COMBO_MATCH_SCOPE_DOC
3974
4002
  ),
3975
4003
  target: z.string().max(30).optional().describe(
3976
- "Where to apply the rule (e.g. 'page' for landing HTML). Default: page. See API docs for the full set of valid values."
4004
+ "Where to apply the rule (e.g. 'page' for landing HTML). `regexp_request_url` requires `target='page'`. Default: page. See API docs for the full set of valid values."
3977
4005
  )
3978
4006
  };
3979
4007
  var createCustomRuleTool = {
3980
4008
  name: "create_custom_rule",
3981
- description: "Define a custom tag-detection rule. The API auto-registers a tag definition for each slug the rule emits (`tag_slug` for non-LLM rules; `config.tags` keys for `rule_type='llm'`); slugs that collide with a built-in system tag are rejected with HTTP 422 / code `checking.system_slug_reserved`. For `rule_type='combo'` set `config.match_scope='url'` when the thresholds must be met inside one link instead of anywhere on the scan (default `'scan'`). Matches tag every future scan; existing scans are untouched until you call `recheck_scans`.",
4009
+ description: "Define a custom tag-detection rule. Use `rule_type='regexp_request_url'` to match captured network and subresource URLs on the fixed `page` target; fresh scans carry up to 5,000 URLs, while later tests/rechecks use a reduced persisted request tree and are best-effort. `rule_type='regexp_url'` remains redirect-chain-only. The API auto-registers a tag definition for each emitted slug and rejects built-in system-slug collisions with HTTP 422 / `checking.system_slug_reserved`. Matches tag future scans; existing scans are untouched until `recheck_scans`.",
3982
4010
  annotations: {
3983
4011
  title: "Create Custom Rule",
3984
4012
  readOnlyHint: false,
@@ -3988,6 +4016,8 @@ var createCustomRuleTool = {
3988
4016
  },
3989
4017
  inputSchema: z.object(CreateCustomRuleInputShape),
3990
4018
  handler: async (input, ctx) => {
4019
+ const inputError = requestUrlRuleInputError(input);
4020
+ if (inputError) return err(inputError);
3991
4021
  const body = {
3992
4022
  name: input.name,
3993
4023
  rule_type: input.rule_type,
@@ -4061,19 +4091,19 @@ var listCustomRulesTool = {
4061
4091
  };
4062
4092
  var TestCustomRuleInputShape = {
4063
4093
  rule_type: z.string().max(50).describe(
4064
- "Rule engine type. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`."
4094
+ "Rule engine type. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `regexp_request_url`, `blacklist_domain`, `combo`, `llm`. `regexp_url` checks redirect-chain URLs only; `regexp_request_url` checks captured network and subresource URLs."
4065
4095
  ),
4066
- config: ruleConfigField.describe(
4067
- "Rule-type-specific config to test. Same shape as `create_custom_rule`'s `config`. NOTE: `test_custom_rule` evaluates the rule against a scan WITHOUT persisting it, so slug-collision validation does NOT run here \u2014 verify slugs against `list_tags` (`scope=system`) before promoting to `create_custom_rule`. " + COMBO_MATCH_SCOPE_DOC
4096
+ config: requestUrlAwareRuleConfigField.describe(
4097
+ "Rule-type-specific config to test. Same shape as `create_custom_rule`'s `config`. " + REQUEST_URL_RULE_CONFIG_DOC + " `regexp_url` remains redirect-chain-only. NOTE: `test_custom_rule` evaluates the rule against a scan WITHOUT persisting it, so slug-collision validation does NOT run here \u2014 verify slugs against `list_tags` (`scope=system`) before promoting to `create_custom_rule`. " + COMBO_MATCH_SCOPE_DOC
4068
4098
  ),
4069
4099
  target: z.string().max(30).describe(
4070
- "Where to apply the rule (e.g. 'page' for landing HTML). See API docs for the full set of valid values."
4100
+ "Where to apply the rule (e.g. 'page' for landing HTML). `regexp_request_url` requires `target='page'`. See API docs for the full set of valid values."
4071
4101
  ),
4072
4102
  scan_id: z.string().uuid().describe("Existing scan UUID to evaluate the rule against.")
4073
4103
  };
4074
4104
  var testCustomRuleTool = {
4075
4105
  name: "test_custom_rule",
4076
- description: "Preview-test a rule definition against an existing scan WITHOUT persisting the rule. Returns `matched: bool`, evaluation time, and per-tag-slug details. Use to validate config before `create_custom_rule`. Combo rules are evaluated against the scan's existing tags, so `config.match_scope` is honoured \u2014 but per-URL hits are collapsed to one row per tag slug, so the result says whether the rule fires, not which URLs it would tag. NOTE: slug-collision validation does NOT run in preview mode \u2014 verify any `tag_slug` (or LLM `config.tags` keys) against `list_tags` (`scope=system`) before promoting to `create_custom_rule`, which would otherwise reject the same payload with HTTP 422 / code `checking.system_slug_reserved`.",
4106
+ description: "Preview-test a rule definition against a stored scan without persisting it. For `regexp_request_url`, the historical snapshot contains main-frame hops plus at most 200 persisted subrequests with selected resource types omitted, so a no-match does not prove the original fresh scan lacked the request. Returns match state, elapsed time, and per-tag detail; the preview response does not expose the matched request URL separately. Slug-collision validation does not run in preview mode.",
4077
4107
  annotations: {
4078
4108
  title: "Test Custom Rule",
4079
4109
  readOnlyHint: true,
@@ -4083,6 +4113,8 @@ var testCustomRuleTool = {
4083
4113
  },
4084
4114
  inputSchema: z.object(TestCustomRuleInputShape),
4085
4115
  handler: async (input, ctx) => {
4116
+ const inputError = requestUrlRuleInputError(input);
4117
+ if (inputError) return err(inputError);
4086
4118
  const result = await ctx.api.testCustomRule({
4087
4119
  rule_type: input.rule_type,
4088
4120
  config: input.config,
@@ -4096,22 +4128,22 @@ var testCustomRuleTool = {
4096
4128
  var UpdateCustomRuleInputShape = {
4097
4129
  rule_id: z.string().uuid().describe("Rule UUID to update."),
4098
4130
  name: z.string().min(1).max(200).optional().describe(
4099
- "New display name. For non-LLM rules this also becomes the new `display_name` of the auto-registered tag definition."
4131
+ "New rule display name. For PERSONAL non-LLM rules this refreshes the auto-registered tag's display name. A same-slug GLOBAL rule keeps its separately managed tag metadata unchanged; use `update_tag_definition` for that tag."
4100
4132
  ),
4101
4133
  tag_slug: z.string().max(100).optional().describe(
4102
- "New tag slug to assign on match. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `scope=system`); colliding requests return 422 with code `checking.system_slug_reserved`. Re-registering an existing custom slug refreshes its tag definition's `display_name` / `description`."
4134
+ "New tag slug to assign on match. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `scope=system`); colliding requests return 422 with code `checking.system_slug_reserved`. Leaving a GLOBAL rule on the same slug preserves its admin-managed tag metadata."
4103
4135
  ),
4104
- config: ruleConfigField.optional().describe(
4105
- "New rule-type-specific config object. Replaces the stored config wholesale \u2014 resend every key you want to keep, including a combo rule's `match_scope`. For `rule_type='llm'` the keys of `config.tags` are auto-registered as tag definitions; any key that collides with a system slug returns the same 422 contract. " + COMBO_MATCH_SCOPE_DOC
4136
+ config: requestUrlAwareRuleConfigField.optional().describe(
4137
+ "New rule-type-specific config object. Replaces the stored config wholesale \u2014 resend every key you want to keep, including a combo rule's `match_scope`. For `rule_type='llm'` the keys of `config.tags` are auto-registered as tag definitions; any key that collides with a system slug returns the same 422 contract. " + REQUEST_URL_RULE_CONFIG_DOC + " Read the rule first because `rule_type` is immutable and is not repeated in this update input. " + COMBO_MATCH_SCOPE_DOC
4106
4138
  ),
4107
4139
  target: z.string().max(30).optional().describe(
4108
- "Where to apply the rule (e.g. 'page' for landing HTML). See API docs for the full set of valid values."
4140
+ "Where to apply the rule. `regexp_request_url` is fixed to `page`; do not change it. See API docs for the valid targets of other rule types."
4109
4141
  ),
4110
4142
  is_active: z.boolean().optional().describe("Enable/disable the rule.")
4111
4143
  };
4112
4144
  var updateCustomRuleTool = {
4113
4145
  name: "update_custom_rule",
4114
- description: "Update a custom tag-detection rule. Only supplied fields are sent, but a supplied `config` replaces the stored one wholesale (omitting a combo rule's `match_scope` reverts it to the scan-wide default). Changing `tag_slug` (or `config.tags` keys for `rule_type='llm'`) onto a built-in system slug returns HTTP 422 / code `checking.system_slug_reserved`. To toggle activation, pass `is_active`. Existing tagged scans are NOT re-evaluated \u2014 call `recheck_scans` for that. (Rule engine `rule_type` cannot be changed after creation; create a new rule instead.)",
4146
+ description: "Update a custom tag-detection rule. Only supplied fields are sent, but `config` replaces the stored object wholesale; read the rule first and resend every required key. `regexp_request_url` needs a non-empty pattern (max 4,096), flags `''`/`'i'`, and the fixed `page` target. Same-slug GLOBAL rule edits preserve separately managed tag metadata; use `update_tag_definition` to change it. Existing scans are not re-evaluated until `recheck_scans`.",
4115
4147
  annotations: {
4116
4148
  title: "Update Custom Rule",
4117
4149
  readOnlyHint: false,
@@ -5659,5 +5691,5 @@ function formatToolError(error) {
5659
5691
  }
5660
5692
 
5661
5693
  export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
5662
- //# sourceMappingURL=chunk-T6GEYHLP.js.map
5663
- //# sourceMappingURL=chunk-T6GEYHLP.js.map
5694
+ //# sourceMappingURL=chunk-HOKDLKJN.js.map
5695
+ //# sourceMappingURL=chunk-HOKDLKJN.js.map