callman-mcp 0.8.0 → 0.9.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +25 -9
  3. package/dist/client/api.d.ts +39 -1
  4. package/dist/client/api.d.ts.map +1 -1
  5. package/dist/client/api.js +53 -0
  6. package/dist/client/api.js.map +1 -1
  7. package/dist/client/errors.d.ts.map +1 -1
  8. package/dist/client/errors.js +28 -1
  9. package/dist/client/errors.js.map +1 -1
  10. package/dist/client/http.d.ts +2 -1
  11. package/dist/client/http.d.ts.map +1 -1
  12. package/dist/client/http.js +4 -2
  13. package/dist/client/http.js.map +1 -1
  14. package/dist/client/types.d.ts +92 -1
  15. package/dist/client/types.d.ts.map +1 -1
  16. package/dist/client/types.js +0 -3
  17. package/dist/client/types.js.map +1 -1
  18. package/dist/config.d.ts +1 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +2 -0
  21. package/dist/config.js.map +1 -1
  22. package/dist/context.d.ts +8 -0
  23. package/dist/context.d.ts.map +1 -1
  24. package/dist/context.js +1 -0
  25. package/dist/context.js.map +1 -1
  26. package/dist/index.js +17 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/resources/docs/mocks.d.ts +1 -1
  29. package/dist/resources/docs/mocks.d.ts.map +1 -1
  30. package/dist/resources/docs/mocks.js +349 -114
  31. package/dist/resources/docs/mocks.js.map +1 -1
  32. package/dist/resources/docs/overview.d.ts +1 -1
  33. package/dist/resources/docs/overview.d.ts.map +1 -1
  34. package/dist/resources/docs/overview.js +5 -1
  35. package/dist/resources/docs/overview.js.map +1 -1
  36. package/dist/resources/generated/aiPacks.d.ts +1 -1
  37. package/dist/resources/generated/aiPacks.js +2 -2
  38. package/dist/resources/index.js +1 -1
  39. package/dist/resources/index.js.map +1 -1
  40. package/dist/server.d.ts +3 -1
  41. package/dist/server.d.ts.map +1 -1
  42. package/dist/server.js +2 -0
  43. package/dist/server.js.map +1 -1
  44. package/dist/tools/catalog.d.ts.map +1 -1
  45. package/dist/tools/catalog.js +15 -1
  46. package/dist/tools/catalog.js.map +1 -1
  47. package/dist/tools/import.d.ts.map +1 -1
  48. package/dist/tools/import.js +7 -1
  49. package/dist/tools/import.js.map +1 -1
  50. package/dist/tools/mockData.d.ts +4 -0
  51. package/dist/tools/mockData.d.ts.map +1 -0
  52. package/dist/tools/mockData.js +384 -0
  53. package/dist/tools/mockData.js.map +1 -0
  54. package/dist/tools/mockEngine.d.ts +129 -0
  55. package/dist/tools/mockEngine.d.ts.map +1 -0
  56. package/dist/tools/mockEngine.js +225 -0
  57. package/dist/tools/mockEngine.js.map +1 -0
  58. package/dist/tools/mockImport.d.ts +5 -0
  59. package/dist/tools/mockImport.d.ts.map +1 -0
  60. package/dist/tools/mockImport.js +298 -0
  61. package/dist/tools/mockImport.js.map +1 -0
  62. package/dist/tools/mockSchemas.d.ts +12272 -0
  63. package/dist/tools/mockSchemas.d.ts.map +1 -0
  64. package/dist/tools/mockSchemas.js +247 -0
  65. package/dist/tools/mockSchemas.js.map +1 -0
  66. package/dist/tools/mockShared.d.ts +5449 -77
  67. package/dist/tools/mockShared.d.ts.map +1 -1
  68. package/dist/tools/mockShared.js +101 -43
  69. package/dist/tools/mockShared.js.map +1 -1
  70. package/dist/tools/mocks.d.ts.map +1 -1
  71. package/dist/tools/mocks.js +117 -35
  72. package/dist/tools/mocks.js.map +1 -1
  73. package/dist/tools/mocksLocal.d.ts +27 -3
  74. package/dist/tools/mocksLocal.d.ts.map +1 -1
  75. package/dist/tools/mocksLocal.js +118 -26
  76. package/dist/tools/mocksLocal.js.map +1 -1
  77. package/dist/tools/uiTestAdmin.d.ts.map +1 -1
  78. package/dist/tools/uiTestAdmin.js +3 -1
  79. package/dist/tools/uiTestAdmin.js.map +1 -1
  80. package/dist-cjs/client/api.js +53 -0
  81. package/dist-cjs/client/errors.js +28 -1
  82. package/dist-cjs/client/http.js +4 -2
  83. package/dist-cjs/client/types.js +0 -3
  84. package/dist-cjs/config.js +2 -0
  85. package/dist-cjs/context.js +1 -0
  86. package/dist-cjs/resources/docs/mocks.js +349 -114
  87. package/dist-cjs/resources/docs/overview.js +5 -1
  88. package/dist-cjs/resources/generated/aiPacks.js +2 -2
  89. package/dist-cjs/resources/index.js +1 -1
  90. package/dist-cjs/server.js +2 -0
  91. package/dist-cjs/tools/catalog.js +15 -1
  92. package/dist-cjs/tools/import.js +7 -1
  93. package/dist-cjs/tools/mockData.js +387 -0
  94. package/dist-cjs/tools/mockEngine.js +243 -0
  95. package/dist-cjs/tools/mockImport.js +302 -0
  96. package/dist-cjs/tools/mockSchemas.js +253 -0
  97. package/dist-cjs/tools/mockShared.js +113 -53
  98. package/dist-cjs/tools/mocks.js +121 -39
  99. package/dist-cjs/tools/mocksLocal.js +133 -35
  100. package/dist-cjs/tools/uiTestAdmin.js +3 -1
  101. package/package.json +3 -3
@@ -1,9 +1,34 @@
1
+ import { MOCK_DATA_OPERATIONS, MOCK_FAKER_HINTS, MOCK_FIELD_TYPES, MOCK_ID_STRATEGIES, MOCK_RESOURCE_ENDPOINTS, MOCK_RESOURCE_TEMPLATES, MOCK_TEMPLATE_CATALOG } from "callman-core/mock-engine";
2
+ // The vocabularies below come from callman-core, so this page always lists
3
+ // exactly the words the engine (and the tools' schemas) take.
4
+ const list = (values) => values.map((value) => `\`${value}\``).join(", ");
5
+ const templateCatalog = () => {
6
+ const groups = new Map();
7
+ for (const entry of MOCK_TEMPLATE_CATALOG)
8
+ groups.set(entry.group, [...(groups.get(entry.group) ?? []), `\`{{${entry.token}}}\` ${entry.description.toLowerCase()}`]);
9
+ return [...groups].map(([group, tokens]) => ` - **${group}**: ${tokens.join(" · ")}`).join("\n");
10
+ };
1
11
  export const MOCKS_DOC = `# Mock APIs through MCP
2
12
 
3
- A mock API is a fake HTTP API that answers with the responses you describe. Use it when a user says
4
- *"I need an API that returns …"* — to build a front end before the backend exists, to test a client
5
- against errors and slow answers, or to give a test a stable dependency. You create it with a few tool
6
- calls and hand the user a URL.
13
+ A mock API is a fake HTTP API that answers like the real one. Use it when a user says *"I need an API that
14
+ returns …"* — to build a front end before the backend exists, to test a client against errors, slow answers
15
+ and sign-in, or to give a test a stable dependency. You create it with a few tool calls and hand the user a URL.
16
+
17
+ With **mock engine v2** a mock can replace a real API: it has its own small database (**resources** with
18
+ auto CRUD endpoints, filtering, paging and relations), **routes that read and write that data**, **user
19
+ sign-in** with JWTs, **faults** on purpose and **test sessions** that keep parallel test runs apart — and it
20
+ can be **imported** from an OpenAPI document, a Postman or a Callman collection.
21
+
22
+ ## Which engine the server runs
23
+
24
+ \`GET /api/mocks/policy\` says it: \`engine: { version: 2, features, limits }\` means mock engine v2; no
25
+ \`engine\` means v1 (static, template and builder routes over state only). The tools check this for you. On a
26
+ v1 server every v2 part — \`resources\`, \`userAuth\`, \`faults\`, routes with \`data\` / \`faults\` /
27
+ \`protected\`, the resource / record / session tools and import_mock — is refused with
28
+ **MOCK_ENGINE_UNSUPPORTED before anything is created** (an older server would silently drop what it does not
29
+ know). The same answer carries the mock lifetime: \`defaultTtlHours\` / \`maxTtlHours\` are set by the
30
+ deployment (on-prem the admin's mock lifetime, 2 days unless changed); \`engine.limits\` has this
31
+ installation's limits.
7
32
 
8
33
  ## Two places a mock can live (\`target\`)
9
34
 
@@ -11,53 +36,126 @@ calls and hand the user a URL.
11
36
  |---|---|---|
12
37
  | Where | On the Callman server | Inside the user's Callman desktop app |
13
38
  | URL | \`<server>/mock/<slug>/…\` — public, reachable by anyone with the URL (or the token) | \`http://127.0.0.1:<port>/…\` — this computer only |
14
- | Responses | static, **template** ({{…}}), **builder** presets, **variants**, request validation, delay | **static only** + delay |
15
- | State | yes (builder routes read and write it) | no |
16
- | Lifetime | expires after 1 / 6 / 24 / 48 h (default 48) | until deleted; lives while the app is open |
39
+ | Answers | static, **template**, **builder** presets, **variants**, request validation, delay; v2: resources, data routes, sign-in, faults, sessions | static on every desktop; everything else on a desktop app with mock engine v2 (else 409 **LOCAL_MOCK_DESKTOP_OUTDATED**) |
40
+ | Lifetime | the deployment's mock lifetime by default, any shorter \`ttlHours\`; renew with \`update_mock { ttlHours }\` | until deleted; lives while the app is open |
17
41
  | Needs | token with \`mocks:*\` | token with \`mocks:*\` **and** the desktop app open, signed in, with Settings → AI tools → "Let AI tools manage local mock APIs on this computer" on |
18
42
  | Routes are edited with | add_mock_route / update_mock_route / delete_mock_route | \`update_mock { routes }\` — REPLACES all routes |
19
43
  | Always on? | yes (no start/stop) | start_mock / stop_mock |
20
44
 
21
- If the server's \`publicUrl\` does not answer from your machine (an installation that never set its public address announces a host nobody can reach), use \`alternativeUrl\` from the same result — it is the API address plus \`/mock/<slug>\`.
45
+ If the server's \`publicUrl\` does not answer from your machine (an installation that never set its public
46
+ address announces a host nobody can reach), use \`alternativeUrl\` from the same result — it is the API
47
+ address plus \`/mock/<slug>\`.
22
48
 
23
- Pick **server** unless the user wants it on their own machine, needs no network exposure, or a tool on
24
- their computer must call it at localhost. Desktop calls go through a relay: the app picks the request up
25
- within a few seconds. If it answers \`LOCAL_MOCK_DESKTOP_OFFLINE\`, tell the user to open the app and
26
- switch the setting on (or fall back to a server mock).
49
+ Pick **server** unless the user wants it on their own machine, needs no network exposure, or a tool on their
50
+ computer must call it at localhost. Desktop calls go through a relay: the app picks the request up within a
51
+ few seconds. If it answers \`LOCAL_MOCK_DESKTOP_OFFLINE\`, tell the user to open the app and switch the setting
52
+ on (or fall back to a server mock).
27
53
 
28
- ## From a user's request to routes
54
+ ## From a user's request to a mock
29
55
 
30
56
  1. List first (\`list_mocks\`) — reuse or extend a mock that already fits instead of creating a duplicate.
31
- 2. For each endpoint the user needs, decide: **method + path**, the **status code** and the **body**.
32
- Use real-looking data; ask only when the contract is truly unknown.
33
- 3. A fixed answer → a static response. Values taken from the request (the id in the path, a field of the
34
- body) or random data → a template. A small CRUD API that remembers what was created → builder
35
- routes over state. Different answers for different inputs (a 401 for a bad header, a 404 for one id)
36
- → variants.
37
- 4. Call \`create_mock\` with all routes at once. The result has the URL and a \`curl\` line per route —
38
- give them to the user, and try one if you can.
39
- 5. Iterate with \`update_mock_route\` / \`add_mock_route\`; read what real clients sent with \`get_mock_logs\`.
57
+ 2. There is a spec or a collection already? → \`import_mock\` (dryRun first).
58
+ 3. Decide per endpoint:
59
+ - things clients **create, list, read, change, delete** (users, orders, products) → a **resource**: one
60
+ entry gives all CRUD endpoints, validation, paging, filters and relations;
61
+ - an operation over that data that is not plain CRUD (cancel an order, find a user by email, count) → a
62
+ route with \`data\`;
63
+ - a fixed answer → a static route; values from the request or random data → a template route;
64
+ - sign-in, "only logged-in users", "users see only their own orders" → \`userAuth\` + \`protected\` /
65
+ \`ownerField\`;
66
+ - "sometimes fails", "is slow", "fails the first two calls" → \`faults\`;
67
+ - different answers for different inputs → variants.
68
+ 4. Call \`create_mock\` with all of it at once — on a v2 server it is one atomic request (all or nothing). The
69
+ result has the URL and a \`curl\` line per route, resource endpoint and sign-in endpoint — give them to the
70
+ user, and try one if you can. \`dryRun: true\` first shows what would be sent and any \`issues\`.
71
+ 5. Iterate: add_mock_route / update_mock_route, add_mock_resource / update_mock_resource; read what real
72
+ clients sent with \`get_mock_logs\` and what they stored with \`query_mock_records\`.
73
+
74
+ ## Resources — a database inside the mock (engine v2)
40
75
 
41
- \`\`\`json
42
- create_mock {
43
- "name": "Orders API", "slug": "orders-api", "ttlHours": 24,
44
- "routes": [
45
- { "method": "GET", "path": "/orders", "statusCode": 200, "body": [{ "id": 1, "status": "NEW" }, { "id": 2, "status": "PAID" }] },
46
- { "method": "GET", "path": "/orders/:id", "response": { "type": "template", "statusCode": 200, "body": { "id": "{{request.params.id}}", "status": "NEW", "createdAt": "{{now}}" } } },
47
- { "method": "POST", "path": "/orders", "statusCode": 201, "body": { "id": "{{faker.uuid}}" }, "delay": { "mode": "fixed", "ms": 300 } }
48
- ]
49
- }
76
+ \`\`\`
77
+ { name, template?, basePath?, idField?, idStrategy?, lookupField?, allowClientId?, fields?, additionalFields?,
78
+ timestamps?, ownerField?, protected?, endpoints?, list?, seed? }
50
79
  \`\`\`
51
80
 
52
- ## A route
81
+ - **name**: lowercase first letter, letters / digits / \`_\` / \`-\` (users, orders, line-items); unique per mock.
82
+ - **template**: start from a ready resource — ${list(MOCK_RESOURCE_TEMPLATES.map((template) => template.id))} — with
83
+ fields, id strategy and generated seed; keys you pass override it (\`{ "template": "users" }\` alone works).
84
+ - **basePath**: default \`/<name>\`. **idField**: default \`id\`. **idStrategy**: ${list(MOCK_ID_STRATEGIES)}
85
+ (default uuid; \`client\` = the caller sends it). **lookupField**: what \`/:id\` looks up by (e.g. \`slug\`).
86
+ - **fields**: \`{ name, type, required?, nullable?, unique?, uniqueCaseInsensitive?, default?, enum?, min?, max?,
87
+ minLength?, maxLength?, pattern?, items?, readOnly?, writeOnly?, ref?, fakerHint? }\`. Types:
88
+ ${list(MOCK_FIELD_TYPES)}. A \`password\` field is stored salted-hashed and never returned; \`default\` may be a
89
+ template (\`"{{now}}"\`, \`"{{auth.sub}}"\`); \`fakerHint\` says how seed data is generated
90
+ (${list(MOCK_FAKER_HINTS.slice(0, 12))} … any \`$random*\` token, or a template). No fields = schemaless: anything goes.
91
+ - **additionalFields**: \`allow\` (default) / \`strip\` / \`reject\` (400) for fields that are not declared.
92
+ - **timestamps**: \`{ createdAt, updatedAt }\` field names (default those; \`null\` switches one off).
93
+ - **seed**: \`{ records?: [...], generate?: N, salt? }\` — every new copy of the data starts with the records plus N
94
+ generated ones. \`reset_mock_data\` goes back to it.
95
+ - **endpoints**: per auto endpoint (${list(MOCK_RESOURCE_ENDPOINTS)}): \`false\` switches it off; an object sets
96
+ \`protected\`, \`faults\`, \`delay\`, \`responses\` (per outcome) or \`requestValidation\`.
97
+ - **list**: \`envelope\` (\`array\` default; \`data-meta\` → \`{ data, meta: { total, page, limit } }\`; \`items\`; \`custom\`
98
+ with \`customBody\`), \`defaultLimit\` (null = all up to \`maxLimit\`), \`maxLimit\`, \`totalCountHeader\`, \`linkHeader\`,
99
+ \`filterMode\` (\`declared\` fields only / \`any\` query parameter), \`searchable\` (\`?q=\`), \`defaultSort\`.
100
+
101
+ ### Auto CRUD endpoints
102
+
103
+ | Request | Answer |
104
+ |---|---|
105
+ | \`GET /<basePath>\` | the list (query language below), \`X-Total-Count\` and \`Link\` headers |
106
+ | \`POST /<basePath>\` | 201 + the record (id and timestamps generated), \`Location\` header |
107
+ | \`GET /<basePath>/:id\` | the record, or 404 |
108
+ | \`PUT /<basePath>/:id\` | replace (id kept) · \`PATCH\` change the given fields |
109
+ | \`DELETE /<basePath>/:id\` | 204 (relations: cascade / nullify / restrict → 409) |
110
+ | \`GET\\|POST /<parent>/:<fk>/<child>\` | nested, for every \`ref\` field (\`/users/7/orders\`) |
111
+
112
+ Writes are validated: **400** \`{ error, invalidFields: [{ field, message }] }\`, **409** \`{ error, field }\` for a
113
+ taken unique value or id, **413** for a record over the size limit, **409** at the record limit. These
114
+ endpoints are computed, never stored as routes: they do not count against the route limit, and an explicit
115
+ route with the same path wins. Records can also be read and written without HTTP: \`query_mock_records\`,
116
+ \`insert_mock_records\`, \`update_mock_records\`, \`delete_mock_records\`.
117
+
118
+ ### The list query language (json-server style)
119
+
120
+ | Query | Meaning |
121
+ |---|---|
122
+ | \`status=paid\` · \`status=paid&status=new\` | equals · repeated = any of them |
123
+ | \`total_gte=10\` \`_gt\` \`_lte\` \`_lt\` \`_ne\` | comparisons |
124
+ | \`id_in=1,2,3\` · \`id_nin=4\` | in / not in a list |
125
+ | \`email_exists=true\` | field present (or \`false\`: missing) |
126
+ | \`tags_contains=red\` | an array field holds the value |
127
+ | \`name_like=ada\` | case-insensitive substring (never a regular expression) |
128
+ | \`address.city=Baku\` | nested fields |
129
+ | \`q=ada\` | full-text search (\`list.searchable\`, or a schemaless resource) |
130
+ | \`_sort=-total,name\` (or \`_sort=total&_order=desc\`) | sort, \`-\` = descending |
131
+ | \`_page=2&_limit=10\` (or \`_per_page\`) · \`_start=20&_end=30\` | paging · slicing |
132
+ | \`_expand=user\` · \`_embed=orders\` | inline the record a ref points to · the records pointing here |
133
+ | \`_fields=id,name\` | only these fields |
134
+
135
+ \`query_mock_records\` takes the same as arguments: \`where\` (equality), \`filters: [{ field, op, value }]\`
136
+ (\`op\`: eq ne gt gte lt lte in nin exists contains like), \`q\`, \`sort\`, \`page\` / \`limit\` (default 20),
137
+ \`start\` / \`end\`, \`expand\`, \`embed\`, \`fields\` — or \`query: "status=paid&_sort=-total"\` verbatim — and \`id\` for
138
+ one record. It returns the records, the total and the query string to reuse with curl.
139
+
140
+ ### Relations — \`ref\`, \`_expand\`, \`_embed\`
141
+
142
+ A field \`{ "name": "userId", "type": "string", "ref": { "resource": "users", "onDelete": "cascade" } }\` makes
143
+ orders point at users: \`GET /orders?_expand=user\` inlines each order's user (the name is the field without
144
+ \`Id\`; \`ref.as\` changes it), \`GET /users/7?_embed=orders\` lists a user's orders, and \`/users/7/orders\` answers
145
+ GET and POST. \`onDelete\`: \`restrict\` (deleting the user answers 409 while orders exist), \`cascade\`
146
+ (deletes them), \`nullify\` (sets the field null), \`none\` (default). \`ref.check\` (default true) refuses an
147
+ order whose user does not exist (400).
148
+
149
+ ## Routes
53
150
 
54
151
  \`\`\`
55
- { method, path, name?, response | (statusCode?, headers?, body?), variants?, delay?, requestValidation? }
152
+ { method, path, name?, response | (statusCode?, headers?, body?), variants?, delay?, requestValidation?,
153
+ data?, faults?, protected? }
56
154
  \`\`\`
57
155
 
58
156
  - **method**: GET POST PUT DELETE PATCH or ANY (any method that no specific route takes).
59
- - **path**: starts with \`/\`; letters, digits and \`/ _ - : * .\`; no \`..\`; at most 256 characters.
60
- \`:name\` is a path parameter (letters, digits, underscore: \`:userId\`, not \`:user-id\`). A trailing
157
+ - **path**: starts with \`/\`; letters, digits and \`/ _ - : * .\`; no \`..\`; at most 256 characters; \`/__callman\`
158
+ is reserved. \`:name\` is a path parameter (letters, digits, underscore: \`:userId\`, not \`:user-id\`). A trailing
61
159
  \`/*\` (or a \`*\` segment) matches everything below. The most specific route wins (literal segment >
62
160
  parameter > wildcard); a trailing slash is ignored. One route per method + path.
63
161
  - **response**: the full shape below. **Shorthand** for the common case: \`statusCode\` (default 200),
@@ -65,8 +163,9 @@ create_mock {
65
163
  - **name**: a label (shown in lists; usable as a reference).
66
164
  - **delay**: \`{ mode: "none" }\`, \`{ mode: "fixed", ms }\` or \`{ mode: "random", minMs, maxMs }\` — the
67
165
  answer is held back that long (server ≤ 60 000 ms, desktop ≤ 30 000 ms). Use it to test timeouts and spinners.
166
+ - **data**, **faults**, **protected** (engine v2): below.
68
167
 
69
- ### Responses (server)
168
+ ### Responses
70
169
 
71
170
  \`\`\`
72
171
  { "type": "static", "statusCode": 200, "headers": { "X-Foo": "1" }, "body": <any JSON> }
@@ -77,26 +176,22 @@ create_mock {
77
176
  - **body**: any JSON value. An object/array is sent as JSON (\`content-type: application/json\` unless you
78
177
  set one); a *string* is sent as is (\`text/plain\` unless you set a content type — for XML/HTML put
79
178
  the text in \`body\` and set \`headers\`). No body = empty response (use for 204).
80
- - **template** — every string in the body may contain \`{{expression}}\`. A string that is *only* one
81
- expression keeps its type (number, boolean, object); inside longer text the value becomes text.
82
- Unknown expressions become \`null\` (whole) or empty (inline).
83
- - \`request.params.<name>\`, \`request.query.<name>\`, \`request.headers.<name>\` (case-insensitive),
84
- \`request.body.<dotted.path>\`, \`request.method\`
85
- - \`state.<key>\` — the mock's state (see below)
86
- - \`faker.email|uuid|fullName|firstName|lastName|phone|address|company|lorem|boolean|int\`
87
- - \`random.int(min,max)\`, \`random.float(min,max)\`, \`random.boolean\`
88
- - \`now\`, \`now+5m\`, \`now-2d\` (units s m h d) → ISO timestamps
89
- - Example: \`{ "id": "{{request.params.id}}", "email": "{{faker.email}}", "age": "{{random.int(18,65)}}" }\`
90
- (\`age\` becomes a number).
91
-
92
- ### Builder presets (server)
93
-
94
- A builder route does the work of a REST endpoint over **state** (arrays of objects, stored per mock).
95
- \`config.preset\` must equal \`preset\`. Required keys are marked; the rest have defaults.
179
+ - **template** — every string in the body (and in header values) may contain \`{{expression}}\`. A string that is
180
+ *only* one expression keeps its type (number, boolean, object); inside longer text the value becomes text.
181
+ Unknown expressions become \`null\` (whole) or empty (inline). Example:
182
+ \`{ "id": "{{request.params.id}}", "email": "{{faker.email}}", "age": "{{random.int(18,65)}}" }\` (\`age\` becomes a number).
183
+ The expressions:
184
+ ${templateCatalog()}
185
+
186
+ ### Builder presets (state-backed, every engine)
187
+
188
+ A builder route does the work of a REST endpoint over **state** (arrays of objects, stored per mock). On a v2
189
+ server prefer a **resource** — it does all of this with validation, relations and sessions. \`config\` is typed
190
+ per preset; \`config.preset\` must equal \`preset\`. Required keys are marked; the rest have defaults.
96
191
 
97
192
  | preset | config | what it does |
98
193
  |---|---|---|
99
- | \`list\` | **source**: \`{kind:"static",data:[…]}\` or \`{kind:"state",stateKey}\`; \`filtering:{enabled,rules:[{queryParam,field,operator:equals\\|contains\\|startsWith\\|in}]}\`; \`sorting:{enabled,sortByParam:"sortBy",orderParam:"order",allowedFields:[…]}\`; \`pagination:{enabled,strategy:page\\|offset\\|cursor,defaultPageSize:20,maxPageSize:100,envelope:standard\\|data-meta\\|raw}\`; \`statusCode\`, \`headers\` | filter by query params, sort, paginate (\`?page=&limit=\`, \`?offset=&limit=\`, \`?cursor=<last id>&limit=\`). Envelopes: standard \`{items,total,page,totalPages}\`, data-meta \`{data,meta:{total,page,limit}}\`, raw array |
194
+ | \`list\` | **source**: \`{kind:"static",data:[…]}\` or \`{kind:"state",stateKey}\`; \`filtering:{enabled,rules:[{queryParam,field,operator:equals\\|contains\\|startsWith\\|in}]}\`; \`sorting:{enabled,sortByParam:"sortBy",orderParam:"order",allowedFields:[…]}\`; \`pagination:{enabled,strategy:page\\|offset\\|cursor,defaultPageSize:20,maxPageSize:100,envelope:standard\\|data-meta\\|raw}\`; \`statusCode\`, \`headers\` | filter by query params, sort, paginate. Envelopes: standard \`{items,total,page,totalPages}\`, data-meta \`{data,meta:{total,page,limit}}\`, raw array |
100
195
  | \`detail\` | **source**, **notFoundBehavior**:\`{statusCode:404,body?}\`, \`paramName:"id"\`, \`lookupField:"id"\` | the item whose \`lookupField\` equals the path parameter; otherwise the not-found answer |
101
196
  | \`create\` | **stateKey**, \`autoGenerate:{id:"uuid"\\|"incremental",createdAt,updatedAt}\`, \`requiredFields:[…]\` (missing → 400), \`responseBehavior:return-created\\|return-id\\|status-only\`, \`statusCode:201\` | appends the request body (plus generated fields) to \`state[stateKey]\` |
102
197
  | \`update\` | **stateKey**, **notFoundBehavior**, \`paramName\`, \`lookupField\`, \`mergeStrategy:partial\\|full\`, \`autoUpdateUpdatedAt:true\`, \`statusCode:200\` | replaces/merges the matching item with the request body |
@@ -104,83 +199,213 @@ A builder route does the work of a REST endpoint over **state** (arrays of objec
104
199
  | \`echo\` | \`statusCode\`, \`headers\` | answers with the request: \`{method,params,query,headers,body}\` — handy to see what a client sends |
105
200
  | \`custom\` | \`statusCode\`, \`headers\`, \`body\` | a fixed answer through the builder |
106
201
 
107
- A full CRUD mock over one \`users\` collection (the path parameter name must match \`paramName\`):
108
-
109
- \`\`\`json
110
- create_mock { "name": "Users", "slug": "users-api", "routes": [
111
- { "method": "GET", "path": "/users", "response": { "type": "builder", "preset": "list", "config": { "preset": "list", "source": { "kind": "state", "stateKey": "users" }, "pagination": { "enabled": true } } } },
112
- { "method": "GET", "path": "/users/:id", "response": { "type": "builder", "preset": "detail", "config": { "preset": "detail", "source": { "kind": "state", "stateKey": "users" }, "notFoundBehavior": { "statusCode": 404 } } } },
113
- { "method": "POST", "path": "/users", "response": { "type": "builder", "preset": "create", "config": { "preset": "create", "stateKey": "users", "autoGenerate": { "id": "incremental", "createdAt": true }, "requiredFields": ["name"] } } },
114
- { "method": "PATCH", "path": "/users/:id", "response": { "type": "builder", "preset": "update", "config": { "preset": "update", "stateKey": "users", "notFoundBehavior": { "statusCode": 404 } } } },
115
- { "method": "DELETE", "path": "/users/:id", "response": { "type": "builder", "preset": "delete", "config": { "preset": "delete", "stateKey": "users", "notFoundBehavior": { "statusCode": 404 } } } }
116
- ] }
117
- set_mock_state { "mock": "users-api", "initialState": { "users": [{ "id": 1, "name": "Ada" }] }, "state": { "users": [{ "id": 1, "name": "Ada" }] } }
118
- \`\`\`
119
-
120
- ### State
202
+ State is a JSON object per mock (limit 1 MB). \`get_mock_state\` shows \`state\` (live) and \`initialState\`;
203
+ \`set_mock_state\` replaces either wholesale; \`reset_mock_state\` copies \`initialState\` back into \`state\`.
204
+ \`{{state.key}}\` templates read it. Resource data is separate (\`reset_mock_data\`).
121
205
 
122
- State is a JSON object per mock (limit 1 MB). Builder \`create\`/\`update\`/\`delete\` change it as requests
123
- arrive (a missing array is created by the first \`create\`); \`list\`/\`detail\` with \`source.kind: "state"\` and
124
- \`{{state.key}}\` templates read it. \`get_mock_state\` shows \`state\` (live) and \`initialState\`;
125
- \`set_mock_state\` replaces either wholesale; \`reset_mock_state\` copies \`initialState\` back into \`state\`
126
- (do that between test runs). Seed *both* when you want a reset to return to the seed data.
127
-
128
- ### Variants — different answers for different requests (server)
206
+ ### Variants — different answers for different requests
129
207
 
130
208
  A route can carry up to 20 variants. Each has \`priority\` (lower is checked first), a \`condition\` and a
131
209
  \`response\` (same shapes as above). The first variant whose condition matches answers; otherwise the
132
- route's own \`response\` does.
210
+ route's own \`response\` (or \`data\`) does.
133
211
 
134
212
  \`\`\`json
135
213
  { "method": "GET", "path": "/profile",
136
214
  "response": { "type": "static", "statusCode": 200, "body": { "name": "Ada" } },
137
215
  "variants": [
138
- { "priority": 1, "condition": { "type": "all", "rules": [{ "field": "headers.authorization", "operator": "not-exists" }] },
139
- "response": { "type": "static", "statusCode": 401, "body": { "error": "unauthorized" } } },
140
- { "priority": 2, "condition": { "type": "any", "rules": [{ "field": "query.fail", "operator": "equals", "value": "1" }] },
216
+ { "priority": 1, "condition": { "type": "any", "rules": [{ "field": "query.fail", "operator": "equals", "value": "1" }] },
141
217
  "response": { "type": "static", "statusCode": 500, "body": { "error": "boom" } } } ] }
142
218
  \`\`\`
143
219
 
144
220
  - \`condition.type\`: \`all\` (every rule) or \`any\` (at least one); 1–20 rules.
145
- - \`field\`: \`headers.<name>\`, \`params.<name>\`, \`query.<name>\`, \`body.<dotted.path>\`.
221
+ - \`field\`: \`headers.<name>\`, \`params.<name>\`, \`query.<name>\`, \`body.<dotted.path>\` (v2 also \`auth.sub\`,
222
+ \`auth.claims.<name>\`, \`session.id\`).
146
223
  - \`operator\`: exists, not-exists (null and empty count as missing), equals, not-equals, contains,
147
- starts-with, matches (a regular expression in \`value\`; an invalid one never matches). Values are compared as text.
224
+ starts-with, matches (a regular expression in \`value\`; unsafe patterns are refused). Values are compared as text.
225
+
226
+ ### Request validation
227
+
228
+ \`requestValidation: { "mode": "schema", "schema": { …JSON Schema draft-07… } }\` makes the route reject a body
229
+ that does not match with **400** and the list of invalid fields. Checked on POST, PUT and PATCH. \`mode: "any"\`
230
+ (default) accepts anything; \`update_mock_route\` with \`requestValidation: null\` removes the gate.
231
+
232
+ ### Data routes — \`data\` (engine v2)
233
+
234
+ A route that reads or writes a resource instead of answering a fixed body:
235
+
236
+ \`\`\`
237
+ data: { operation, resource, match?: [{ field, op?, value, skipIfEmpty? }], write?: { source?, pick?, omit?, set? },
238
+ mode?: "merge"|"replace", many?, find?: { useRequestQuery?, sort?, limit? }, responses? }
239
+ \`\`\`
240
+
241
+ - **operation**: ${list(MOCK_DATA_OPERATIONS)}. insert creates from the body; find answers a list (with
242
+ \`find.useRequestQuery\` the caller's list query applies too); findOne / update / delete / upsert act on the
243
+ first match (\`many: true\` → every match); count answers \`{ count }\`.
244
+ - **match**: every rule must hold; \`value\` is a template or a literal — \`"{{request.params.id}}"\`,
245
+ \`"{{request.query.status}}"\`, \`"{{auth.sub}}"\`; \`op\` eq (default) ne gt gte lt lte in contains like;
246
+ \`skipIfEmpty\` ignores a rule whose value is empty (optional filters).
247
+ - **write**: \`source\` (default \`"{{request.body}}"\`), \`pick\` / \`omit\` fields, \`set\` fields last
248
+ (\`{ "status": "cancelled", "cancelledAt": "{{now}}" }\`).
249
+ - The answer is the record(s); no match → 404. **responses** overrides per outcome (success, created, notFound,
250
+ conflict, validationError, unauthorized, forbidden, limitExceeded, payloadTooLarge); a **template** \`response\`
251
+ on the route shapes the success answer with \`{{record}}\`, \`{{records}}\`, \`{{total}}\`. A static response
252
+ or the shorthand is refused on a data route.
253
+
254
+ \`\`\`json
255
+ { "method": "GET", "path": "/users/by-email/:email", "data": { "operation": "findOne", "resource": "users", "match": [{ "field": "email", "value": "{{request.params.email}}" }] } }
256
+ { "method": "POST", "path": "/orders/:id/cancel", "data": { "operation": "update", "resource": "orders", "match": [{ "field": "id", "value": "{{request.params.id}}" }], "write": { "source": "{}", "set": { "status": "cancelled" } } } }
257
+ { "method": "GET", "path": "/stats/orders", "data": { "operation": "count", "resource": "orders", "match": [{ "field": "status", "value": "{{request.query.status}}", "skipIfEmpty": true }] } }
258
+ \`\`\`
259
+
260
+ ## User sign-in — \`userAuth\` (engine v2)
261
+
262
+ \`\`\`
263
+ userAuth: { usersResource?: "users", identityField?: "email", passwordField?: "password", tokenTtlSeconds?: 3600,
264
+ roleClaim?: "role", claims?, protect?: "none"|"resources"|"all", endpoints?, responses?, issuer?, audience? }
265
+ \`\`\`
266
+
267
+ - \`POST /auth/register\` (201) and \`POST /auth/login\` (200) take \`{ <identityField>, <passwordField> }\` (register:
268
+ the whole user) and answer \`{ accessToken, tokenType: "Bearer", expiresIn, user }\`; \`GET /auth/me\` answers the
269
+ signed-in user. Paths change with \`endpoints: { login: { path }, register: { enabled: false } }\`.
270
+ - The users resource needs the identity field (make it \`unique\`) and a \`password\` field (type password).
271
+ - Tokens are HS256 JWTs signed with a per-mock secret (never shown); callers send \`Authorization: Bearer <token>\`.
272
+ Missing / invalid → **401** with \`WWW-Authenticate: Bearer\`; a role that does not fit → **403**.
273
+ - What needs a user: \`protect\` sets the default — \`resources\` (default: every resource endpoint), \`all\` (every
274
+ route too) or \`none\`. \`protected\` on a route, a resource or one endpoint overrides it: \`true\` (any signed-in
275
+ user), \`{ "roles": ["admin"] }\` (the \`roleClaim\` field of the user must be one of them), \`false\` (public).
276
+ - \`ownerField\` on a resource (\`"ownerField": "userId"\`) scopes it to the signed-in user: they list, read and
277
+ change only their own records, and new records get their id.
278
+ - Templates see the user: \`{{auth.sub}}\`, \`{{auth.user.<field>}}\`, \`{{auth.claims.<name>}}\`.
279
+ - \`X-Mock-Token\` (the mock's own access gate, \`auth: "token"\`) is separate and checked first.
280
+ - \`update_mock { userAuth }\` changes it (the secret is kept), \`update_mock { userAuth: null }\` switches it off.
281
+
282
+ ## Faults — fail on purpose (engine v2)
283
+
284
+ \`\`\`
285
+ faults: { errorRate?, errorStatus?: 500, errorBody?, delay?, timeoutRate?, dropRate?, failFirst? }
286
+ \`\`\`
287
+
288
+ | Key | Effect |
289
+ |---|---|
290
+ | \`errorRate\` | % of requests answered with \`errorStatus\` (and \`errorBody\`) |
291
+ | \`delay\` | extra delay on every request (\`{ mode: "fixed", ms }\` / \`{ mode: "random", minMs, maxMs }\`) |
292
+ | \`timeoutRate\` | % of requests held unanswered, then 504 — the client's timeout fires first |
293
+ | \`dropRate\` | % of requests whose connection is closed without an answer (a proxy in front shows 502) |
294
+ | \`failFirst\` | the first N calls (per test session) fail with \`errorStatus\`, then it recovers — retry logic |
295
+
296
+ Set them for the whole mock (\`create_mock\` / \`update_mock { faults }\`, null removes), for one route (the
297
+ route's \`faults\`) or for one resource endpoint (\`endpoints: { create: { faults: … } }\`). \`reset_mock_data\`
298
+ restarts the \`failFirst\` counters. A mock whose faults were set as full rules (conditions, sequences) shows
299
+ them as \`faultRules\` next to the flat form.
300
+
301
+ ## Test sessions — \`X-Mock-Session\` (engine v2)
302
+
303
+ A caller that sends \`X-Mock-Session: <id>\` (1–64 letters, digits, \`. _ : -\`) gets **its own copy of the data**,
304
+ seeded on first use and dropped after being idle — so parallel test runs never see each other's writes. Too many
305
+ active sessions → 429 (\`x-mock-limit-reason: session-limit\`). A caller can read and reset its own session at
306
+ \`GET /__callman/session\` and \`POST /__callman/session/reset\`. From here: \`list_mock_sessions\`, and \`session\`
307
+ on query_mock_records, insert / update / delete_mock_records and reset_mock_data.
308
+
309
+ ## Import — \`import_mock\` (engine v2)
310
+
311
+ Builds a mock from an **OpenAPI 3.x / Swagger 2.0** document (JSON or YAML), a **Postman** collection (its saved
312
+ examples) or a **Callman** collection (\`collection\`: its requests with their newest recorded responses,
313
+ secrets redacted). One source: \`content\` (the document text, e.g. a file you read), \`url\` or \`filePath\`.
314
+ **This MCP server reads URLs and files itself — the Callman server never fetches anything.** Files, and URLs on
315
+ localhost or a private network, are refused (MOCK_IMPORT_SOURCE_BLOCKED) unless the user set
316
+ \`CALLMAN_ALLOW_LOCAL_SOURCES=1\` for this server — read the file yourself and pass \`content\` instead.
317
+
318
+ - \`mode: "auto"\` (default): CRUD groups (\`GET/POST /pets\` + \`GET/PUT/DELETE /pets/{id}\`) become resources with
319
+ the examples as seed records; everything else becomes routes. \`mode: "routes"\`: one route per operation.
320
+ - Extra responses become variants; a caller picks one with \`X-Mock-Response-Code: 404\` or
321
+ \`X-Mock-Response-Name: <example name>\`. OpenAPI request schemas become request validation
322
+ (\`validateRequests: false\` turns it off); \`keepServerBasePath\` keeps \`/api/v3\` in the paths.
323
+ - \`dryRun: true\` shows the routes and resources it would create and the **report**: what was converted, what
324
+ needs attention (\`warning\`) and what was \`lost\` — read it to the user.
325
+ - \`into: { mock, strategy }\` adds to an existing mock: \`merge\` (default) keeps its routes and resources and adds
326
+ the new ones (same method + path or resource name is skipped); \`replace\` swaps them.
327
+ - \`target: "desktop"\` converts on the server and writes a local mock to the desktop app.
328
+
329
+ ## Worked example — users + orders CRUD with login
330
+
331
+ \`\`\`json
332
+ create_mock {
333
+ "name": "Shop API", "slug": "shop-api",
334
+ "resources": [
335
+ { "template": "users" },
336
+ { "name": "orders", "idStrategy": "incremental", "ownerField": "userId",
337
+ "fields": [
338
+ { "name": "userId", "type": "string", "ref": { "resource": "users", "onDelete": "cascade" } },
339
+ { "name": "total", "type": "number", "required": true, "min": 0, "fakerHint": "$randomPrice" },
340
+ { "name": "status", "type": "string", "enum": ["pending", "paid", "cancelled"], "default": "pending" }
341
+ ] }
342
+ ],
343
+ "userAuth": { "usersResource": "users", "protect": "resources" },
344
+ "routes": [
345
+ { "method": "POST", "path": "/orders/:id/cancel", "protected": true,
346
+ "data": { "operation": "update", "resource": "orders",
347
+ "match": [{ "field": "id", "value": "{{request.params.id}}" }, { "field": "userId", "value": "{{auth.sub}}" }],
348
+ "write": { "source": "{}", "set": { "status": "cancelled" } } } },
349
+ { "method": "GET", "path": "/health", "statusCode": 200, "body": { "ok": true }, "protected": false }
350
+ ]
351
+ }
352
+ \`\`\`
353
+
354
+ What the client (or you, with the curl lines from the result) can now do against \`<server>/mock/shop-api\`:
355
+
356
+ \`\`\`
357
+ POST /auth/register { "email": "ada@example.com", "password": "secret123", "name": "Ada" } → 201 { accessToken, user }
358
+ POST /auth/login { "email": "ada@example.com", "password": "secret123" } → 200 { accessToken, tokenType, expiresIn, user }
359
+ GET /auth/me Authorization: Bearer <accessToken> → Ada (no password)
360
+ POST /orders { "total": 42 } Authorization: Bearer … → 201 { id: 1, userId: <Ada's id>, total: 42, status: "pending", createdAt, updatedAt }
361
+ GET /orders?status=pending&_sort=-total&_expand=user (Bearer) → Ada's orders only, each with its user; X-Total-Count
362
+ PATCH /orders/1 { "status": "paid" } (Bearer) → the changed order
363
+ POST /orders/1/cancel (Bearer) → status "cancelled"
364
+ POST /orders { "total": -1 } (Bearer) → 400 { error, invalidFields: [{ field: "total", … }] }
365
+ GET /orders (no token) → 401
366
+ \`\`\`
148
367
 
149
- ### Request validation (server)
368
+ Then, for the user's tests:
150
369
 
151
- \`requestValidation: { "mode": "schema", "schema": { …JSON Schema draft-07… } }\` makes the route reject a
152
- body that does not match with **400** and the list of invalid fields — to let a client team verify what
153
- they send. Checked on POST, PUT and PATCH requests. \`mode: "any"\` (default) accepts anything. An invalid
154
- schema is refused when you save the route. \`update_mock_route\` with \`requestValidation: null\` removes the gate.
370
+ - \`update_mock { "mock": "shop-api", "faults": { "failFirst": 2, "errorStatus": 503 } }\` — the first two calls fail,
371
+ the third succeeds (retry logic); \`reset_mock_data\` restarts the count.
372
+ - Each test run sends its own \`X-Mock-Session: run-<n>\` header → isolated data; \`list_mock_sessions\` shows them.
373
+ - \`query_mock_records { "mock": "shop-api", "resource": "orders", "filters": [{ "field": "total", "op": "gte", "value": 10 }], "expand": ["user"] }\`
374
+ shows what the client stored; \`insert_mock_records\` prepares data; \`reset_mock_data\` puts the seed back.
155
375
 
156
- ### Auth (server)
376
+ ## Auth of the mock itself (server)
157
377
 
158
- \`auth: "public"\` (default) — anyone with the URL. \`auth: "token"\` — callers must send
159
- \`X-Mock-Token: <token>\` (else 401). The token is generated by the server and returned **once**, in the
160
- create_mock / update_mock result; \`get_mock\` keeps it masked unless the token holds \`environments:reveal\`
161
- and you pass \`reveal: true\`. Never put it into places the user did not ask for.
378
+ \`auth: "public"\` (default) — anyone with the URL. \`auth: "token"\` — callers must send \`X-Mock-Token: <token>\`
379
+ (else 401). The token is generated by the server and returned **once**, in the create_mock / update_mock result;
380
+ \`get_mock\` keeps it masked unless the token holds \`environments:reveal\` and you pass \`reveal: true\`. Never put
381
+ it into places the user did not ask for. (User sign-in above is a different thing: it simulates the API's own login.)
162
382
 
163
- ## Limits (server defaults; an installation may change them)
383
+ ## Limits (server defaults; an installation may change them — \`GET /api/mocks/policy\` → \`engine.limits\`)
164
384
 
165
- 20 mocks per workspace · 50 routes per mock · response body ≤ 100 KB · request body ≤ 1 MB ·
166
- state ≤ 1 MB · 20 000 requests in total per mock, then 429 · 30 requests per second per mock, then 429 ·
167
- the 500 newest request logs are kept · slugs: 3–48 chars, lowercase letters/digits/single dashes, start
168
- with a letter, a few reserved words (api, mock, admin …) refused · lifetime 1/6/24/48 h, then **410 Gone**.
169
- Local (desktop): response body ≤ 256 KB, ports 1024–65535 (omit \`port\` and the app picks a free one),
170
- the mock listens on 127.0.0.1 only.
385
+ 20 mocks per workspace · 50 routes per mock · response body ≤ 100 KB · request body ≤ 1 MB · state ≤ 1 MB ·
386
+ 20 000 requests in total per mock, then 429 · 30 requests per second per mock, then 429 · the 500 newest request
387
+ logs are kept · slugs: 3–48 chars, lowercase letters/digits/single dashes, start with a letter, a few reserved
388
+ words (api, mock, admin …) refused · lifetime up to the deployment's mock lifetime (longer requests are capped),
389
+ then **410 Gone** — renew before that with \`update_mock { ttlHours }\`. A flood from one address gets 429 with
390
+ \`X-Mock-Limit-Reason: ip-rate-limit\`. Engine v2: 20 resources per mock, 100 fields per resource, 1 000 records
391
+ per resource, 16 KB per record, 512 KB of seed records, 20 active test sessions (idle after 60 min), 2 MB per import.
392
+ Local (desktop): ports 1024–65535 (omit \`port\` and the app picks a free one), the mock
393
+ listens on 127.0.0.1 only; a relay command carries at most 1 MB (a route body sent to an older desktop app: 256 KB).
171
394
 
172
395
  ## Desktop (local) mocks
173
396
 
174
- \`create_mock { target: "desktop", name, port?, autoStart?, routes? }\` creates a local mock in the user's
175
- app and starts it (\`start: false\` to skip). Routes are static: \`{ method, path, name?, statusCode, headers, body, delay }\`
176
- (or \`response: { type: "static", … }\` — the same route object you would give a server mock works as
177
- long as it is static; \`variants\`, \`requestValidation\`, template and builder responses are refused
178
- with LOCAL_MOCK_FEATURE_UNSUPPORTED). \`body\` is text: a string is sent verbatim, anything else is
179
- sent as formatted JSON. \`:param\` segments and a trailing \`/*\` work; every request is logged
180
- (\`get_mock_logs { target: "desktop" }\`, the latest requests only, kept until the app quits).
181
- To change routes: \`get_mock { target: "desktop" }\`, edit, \`update_mock { routes: [...all of them] }\`.
182
- \`start_mock\` / \`stop_mock\` / \`delete_mock\` act on one by name or id. Mocks the AI created carry an "AI"
183
- badge in the app. The mock is only reachable from the user's computer — don't hand out the URL to others.
397
+ \`create_mock { target: "desktop", name, port?, autoStart?, routes?, resources?, userAuth?, faults? }\` creates a
398
+ local mock in the user's app and starts it (\`start: false\` to skip). Static routes —
399
+ \`{ method, path, name?, statusCode, headers, body, delay }\` or \`response: { type: "static", … }\` — work with every
400
+ desktop app: \`body\` is text (a string is sent verbatim, anything else as formatted JSON). Templates, variants,
401
+ request validation, resources, data routes, sign-in and faults need a server and a desktop app with mock engine
402
+ v2 (the same engine runs on the desktop); with a v1 server they are refused (LOCAL_MOCK_FEATURE_UNSUPPORTED /
403
+ MOCK_ENGINE_UNSUPPORTED), with an older desktop app the server answers LOCAL_MOCK_DESKTOP_OUTDATED.
404
+ \`:param\` segments and a trailing \`/*\` work; every request is logged (\`get_mock_logs { target: "desktop" }\`, the
405
+ latest requests only, kept until the app quits). To change routes: \`get_mock { target: "desktop" }\`, edit,
406
+ \`update_mock { routes: [...all of them] }\`. The resource, record and session tools take \`target: "desktop"\` too.
407
+ \`start_mock\` / \`stop_mock\` / \`delete_mock\` act on one by name or id. Mocks the AI created carry an "AI" badge in
408
+ the app. The mock is only reachable from the user's computer — don't hand out the URL to others.
184
409
 
185
410
  ## Tools
186
411
 
@@ -188,23 +413,33 @@ badge in the app. The mock is only reachable from the user's computer — don't
188
413
  |---|---|---|
189
414
  | list_mocks, get_mock, get_mock_logs | mocks:read | \`target\` server/desktop |
190
415
  | get_mock_stats, get_mock_state | mocks:read | server |
416
+ | query_mock_records, list_mock_sessions | mocks:read | engine v2, \`target\` server/desktop |
191
417
  | create_mock, update_mock | mocks:write | \`target\` server/desktop |
192
418
  | add_mock_route, update_mock_route, reorder_mock_routes, set_mock_state, reset_mock_state | mocks:write | server |
419
+ | add_mock_resource, update_mock_resource, insert_mock_records, update_mock_records, reset_mock_data, import_mock | mocks:write | engine v2, \`target\` server/desktop |
193
420
  | start_mock, stop_mock | mocks:write | desktop |
194
421
  | delete_mock | mocks:delete (+ \`confirm: true\`) | \`target\` server/desktop |
195
422
  | delete_mock_route | mocks:delete (+ \`confirm: true\`) | server |
423
+ | delete_mock_resource, delete_mock_records | mocks:delete (+ \`confirm: true\`) | engine v2, \`target\` server/desktop |
196
424
 
197
425
  \`mock\` is the name, the slug or the id. A route is referenced by routeId, \`"GET /users/:id"\`, its path
198
- (when unique) or its name. If \`create_mock\` fails on a route, the new mock is deleted again (needs
199
- mocks:delete; otherwise the result says it is kept) and the error names the failing route and why.
426
+ (when unique) or its name; a resource by its name (or id). On a v1 server, if \`create_mock\` fails on a route,
427
+ the new mock is deleted again (needs mocks:delete; otherwise the result says it is kept) and the error names the
428
+ failing route and why; on a v2 server the create is atomic.
200
429
 
201
430
  ## Errors you may meet
202
431
 
203
432
  \`PAT_SCOPE_MISSING\` (token lacks mocks:read/write/delete) · \`FEATURE_DISABLED\` (mocks switched off for the
204
- account) · \`MOCK_SLUG_TAKEN\` (pick another slug) · \`MOCK_LIMIT_REACHED\` (delete an old mock) ·
205
- \`ROUTE_PATH_CONFLICT\` (the method + path exists) · \`MOCK_ROUTE_LIMIT_REACHED\` · \`VALIDATION_ERROR\`
206
- (details name the field) · \`LOCAL_MOCK_DESKTOP_OFFLINE\` (open the desktop app and switch the setting on) ·
207
- \`LOCAL_MOCK_TIMEOUT\` (the app did not answer; check with list_mocks before repeating a create) ·
208
- \`LOCAL_MOCK_FEATURE_UNSUPPORTED\` (use a server mock).
433
+ account) · \`MOCK_ENGINE_UNSUPPORTED\` (a v2 part on a v1 server — nothing was written) · \`MOCK_DEFINITION_INVALID\`
434
+ (\`details.issues\` names each problem by path; nothing was written) · \`MOCK_SLUG_TAKEN\` (pick another slug) ·
435
+ \`MOCK_LIMIT_REACHED\` (delete an old mock) · \`ROUTE_PATH_CONFLICT\` (the method + path exists) ·
436
+ \`MOCK_ROUTE_LIMIT_REACHED\` · \`MOCK_RESOURCE_CONFLICT\` (a resource with that name exists) ·
437
+ \`MOCK_RESOURCE_NOT_FOUND\` / \`MOCK_RECORD_NOT_FOUND\` · \`MOCK_RECORD_INVALID\` (the record does not fit the fields) ·
438
+ \`MOCK_RECORD_DELETE_BLOCKED\` (a \`restrict\` reference) · \`REVISION_CONFLICT\` (changed elsewhere; read and repeat) ·
439
+ \`MOCK_IMPORT_SOURCE_BLOCKED\` (a file or private URL without CALLMAN_ALLOW_LOCAL_SOURCES) ·
440
+ \`MOCK_IMPORT_UNSUPPORTED\` / \`MOCK_IMPORT_UNREADABLE\` / \`MOCK_IMPORT_TOO_LARGE\` · \`VALIDATION_ERROR\` (details name
441
+ the field) · \`LOCAL_MOCK_DESKTOP_OFFLINE\` (open the desktop app and switch the setting on) ·
442
+ \`LOCAL_MOCK_DESKTOP_OUTDATED\` (update the desktop app, or use a server mock) · \`LOCAL_MOCK_TIMEOUT\` (the app did
443
+ not answer; check with list_mocks before repeating a create) · \`LOCAL_MOCK_FEATURE_UNSUPPORTED\` (static only here).
209
444
  `;
210
445
  //# sourceMappingURL=mocks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mocks.js","sourceRoot":"","sources":["../../../src/resources/docs/mocks.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgNxB,CAAC"}
1
+ {"version":3,"file":"mocks.js","sourceRoot":"","sources":["../../../src/resources/docs/mocks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjM,2EAA2E;AAC3E,8DAA8D;AAC9D,MAAM,IAAI,GAAG,CAAC,MAAyB,EAAU,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAErG,MAAM,eAAe,GAAG,GAAW,EAAE;IACnC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,KAAK,MAAM,KAAK,IAAI,qBAAqB;QAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,KAAK,CAAC,KAAK,QAAQ,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;IACtK,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,SAAS,KAAK,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpG,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gDAuEuB,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;;oFAExB,IAAI,CAAC,kBAAkB,CAAC;;;;IAIxG,IAAI,CAAC,gBAAgB,CAAC;;KAErB,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;;;;;sCAKF,IAAI,CAAC,uBAAuB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyFjE,eAAe,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAyDA,IAAI,CAAC,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2M5C,CAAC"}