callman-mcp 0.1.0 → 0.2.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 (204) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +45 -16
  3. package/dist/client/api.d.ts +64 -3
  4. package/dist/client/api.d.ts.map +1 -1
  5. package/dist/client/api.js +67 -2
  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 +33 -7
  9. package/dist/client/errors.js.map +1 -1
  10. package/dist/client/types.d.ts +91 -2
  11. package/dist/client/types.d.ts.map +1 -1
  12. package/dist/context.d.ts +2 -0
  13. package/dist/context.d.ts.map +1 -1
  14. package/dist/context.js +7 -2
  15. package/dist/context.js.map +1 -1
  16. package/dist/domain/contracts.d.ts +893 -0
  17. package/dist/domain/contracts.d.ts.map +1 -0
  18. package/dist/domain/contracts.js +90 -0
  19. package/dist/domain/contracts.js.map +1 -0
  20. package/dist/domain/diagram/build.d.ts +520 -18
  21. package/dist/domain/diagram/build.d.ts.map +1 -1
  22. package/dist/domain/diagram/build.js +65 -19
  23. package/dist/domain/diagram/build.js.map +1 -1
  24. package/dist/domain/diagram/patch.d.ts +624 -51
  25. package/dist/domain/diagram/patch.d.ts.map +1 -1
  26. package/dist/domain/diagram/patch.js +180 -40
  27. package/dist/domain/diagram/patch.js.map +1 -1
  28. package/dist/domain/diagram/style.d.ts +315 -0
  29. package/dist/domain/diagram/style.d.ts.map +1 -0
  30. package/dist/domain/diagram/style.js +124 -0
  31. package/dist/domain/diagram/style.js.map +1 -0
  32. package/dist/domain/diagram/summarize.d.ts +8 -0
  33. package/dist/domain/diagram/summarize.d.ts.map +1 -1
  34. package/dist/domain/diagram/summarize.js +28 -13
  35. package/dist/domain/diagram/summarize.js.map +1 -1
  36. package/dist/domain/requestIntegrations.d.ts +719 -0
  37. package/dist/domain/requestIntegrations.d.ts.map +1 -0
  38. package/dist/domain/requestIntegrations.js +222 -0
  39. package/dist/domain/requestIntegrations.js.map +1 -0
  40. package/dist/domain/requestShape.d.ts +1350 -152
  41. package/dist/domain/requestShape.d.ts.map +1 -1
  42. package/dist/domain/requestShape.js +336 -121
  43. package/dist/domain/requestShape.js.map +1 -1
  44. package/dist/domain/resolve.d.ts +2 -1
  45. package/dist/domain/resolve.d.ts.map +1 -1
  46. package/dist/domain/resolve.js +5 -1
  47. package/dist/domain/resolve.js.map +1 -1
  48. package/dist/domain/scenario/build.d.ts +82 -26
  49. package/dist/domain/scenario/build.d.ts.map +1 -1
  50. package/dist/domain/scenario/build.js +196 -65
  51. package/dist/domain/scenario/build.js.map +1 -1
  52. package/dist/domain/scenario/nodeConfig.d.ts +11 -0
  53. package/dist/domain/scenario/nodeConfig.d.ts.map +1 -0
  54. package/dist/domain/scenario/nodeConfig.js +251 -0
  55. package/dist/domain/scenario/nodeConfig.js.map +1 -0
  56. package/dist/domain/scenario/summarize.d.ts +145 -25
  57. package/dist/domain/scenario/summarize.d.ts.map +1 -1
  58. package/dist/domain/scenario/summarize.js +84 -29
  59. package/dist/domain/scenario/summarize.js.map +1 -1
  60. package/dist/lib/mask.d.ts +0 -18
  61. package/dist/lib/mask.d.ts.map +1 -1
  62. package/dist/lib/mask.js +2 -44
  63. package/dist/lib/mask.js.map +1 -1
  64. package/dist/lib/pagination.d.ts +3 -2
  65. package/dist/lib/pagination.d.ts.map +1 -1
  66. package/dist/lib/pagination.js +13 -8
  67. package/dist/lib/pagination.js.map +1 -1
  68. package/dist/lib/redact.d.ts +12 -0
  69. package/dist/lib/redact.d.ts.map +1 -0
  70. package/dist/lib/redact.js +50 -0
  71. package/dist/lib/redact.js.map +1 -0
  72. package/dist/lib/strict.d.ts +4 -0
  73. package/dist/lib/strict.d.ts.map +1 -0
  74. package/dist/lib/strict.js +14 -0
  75. package/dist/lib/strict.js.map +1 -0
  76. package/dist/prompts/index.js +7 -7
  77. package/dist/prompts/index.js.map +1 -1
  78. package/dist/resources/docs/contracts.d.ts +2 -0
  79. package/dist/resources/docs/contracts.d.ts.map +1 -0
  80. package/dist/resources/docs/contracts.js +74 -0
  81. package/dist/resources/docs/contracts.js.map +1 -0
  82. package/dist/resources/docs/diagramSchema.d.ts +1 -1
  83. package/dist/resources/docs/diagramSchema.d.ts.map +1 -1
  84. package/dist/resources/docs/diagramSchema.js +95 -28
  85. package/dist/resources/docs/diagramSchema.js.map +1 -1
  86. package/dist/resources/docs/overview.d.ts +1 -1
  87. package/dist/resources/docs/overview.d.ts.map +1 -1
  88. package/dist/resources/docs/overview.js +29 -7
  89. package/dist/resources/docs/overview.js.map +1 -1
  90. package/dist/resources/docs/requestSchema.d.ts +1 -1
  91. package/dist/resources/docs/requestSchema.d.ts.map +1 -1
  92. package/dist/resources/docs/requestSchema.js +85 -16
  93. package/dist/resources/docs/requestSchema.js.map +1 -1
  94. package/dist/resources/generated/aiPacks.d.ts +1 -1
  95. package/dist/resources/generated/aiPacks.d.ts.map +1 -1
  96. package/dist/resources/generated/aiPacks.js +3 -2
  97. package/dist/resources/generated/aiPacks.js.map +1 -1
  98. package/dist/resources/index.d.ts.map +1 -1
  99. package/dist/resources/index.js +19 -19
  100. package/dist/resources/index.js.map +1 -1
  101. package/dist/run/executeCollection.d.ts +3 -1
  102. package/dist/run/executeCollection.d.ts.map +1 -1
  103. package/dist/run/executeCollection.js +16 -4
  104. package/dist/run/executeCollection.js.map +1 -1
  105. package/dist/run/summarize.d.ts +17 -6
  106. package/dist/run/summarize.d.ts.map +1 -1
  107. package/dist/run/summarize.js +48 -41
  108. package/dist/run/summarize.js.map +1 -1
  109. package/dist/run/types.d.ts +1 -0
  110. package/dist/run/types.d.ts.map +1 -1
  111. package/dist/server.d.ts +1 -1
  112. package/dist/server.d.ts.map +1 -1
  113. package/dist/server.js +1 -1
  114. package/dist/server.js.map +1 -1
  115. package/dist/tools/catalog.d.ts +1 -1
  116. package/dist/tools/catalog.d.ts.map +1 -1
  117. package/dist/tools/catalog.js +18 -0
  118. package/dist/tools/catalog.js.map +1 -1
  119. package/dist/tools/collections.d.ts.map +1 -1
  120. package/dist/tools/collections.js +53 -41
  121. package/dist/tools/collections.js.map +1 -1
  122. package/dist/tools/contracts.d.ts +4 -0
  123. package/dist/tools/contracts.d.ts.map +1 -0
  124. package/dist/tools/contracts.js +201 -0
  125. package/dist/tools/contracts.js.map +1 -0
  126. package/dist/tools/diagrams.d.ts.map +1 -1
  127. package/dist/tools/diagrams.js +19 -10
  128. package/dist/tools/diagrams.js.map +1 -1
  129. package/dist/tools/environments.d.ts.map +1 -1
  130. package/dist/tools/environments.js +184 -64
  131. package/dist/tools/environments.js.map +1 -1
  132. package/dist/tools/import.d.ts.map +1 -1
  133. package/dist/tools/import.js +111 -20
  134. package/dist/tools/import.js.map +1 -1
  135. package/dist/tools/index.d.ts +2 -1
  136. package/dist/tools/index.d.ts.map +1 -1
  137. package/dist/tools/index.js +19 -1
  138. package/dist/tools/index.js.map +1 -1
  139. package/dist/tools/lookups.d.ts +38 -7
  140. package/dist/tools/lookups.d.ts.map +1 -1
  141. package/dist/tools/lookups.js +113 -10
  142. package/dist/tools/lookups.js.map +1 -1
  143. package/dist/tools/meta.d.ts.map +1 -1
  144. package/dist/tools/meta.js +9 -4
  145. package/dist/tools/meta.js.map +1 -1
  146. package/dist/tools/requests.d.ts.map +1 -1
  147. package/dist/tools/requests.js +138 -50
  148. package/dist/tools/requests.js.map +1 -1
  149. package/dist/tools/run.d.ts.map +1 -1
  150. package/dist/tools/run.js +84 -28
  151. package/dist/tools/run.js.map +1 -1
  152. package/dist/tools/scenarios.d.ts.map +1 -1
  153. package/dist/tools/scenarios.js +238 -75
  154. package/dist/tools/scenarios.js.map +1 -1
  155. package/dist/tools/shared.d.ts +1 -0
  156. package/dist/tools/shared.d.ts.map +1 -1
  157. package/dist/tools/shared.js +9 -1
  158. package/dist/tools/shared.js.map +1 -1
  159. package/dist/tools/workspaces.d.ts.map +1 -1
  160. package/dist/tools/workspaces.js +44 -7
  161. package/dist/tools/workspaces.js.map +1 -1
  162. package/dist-cjs/client/api.js +67 -2
  163. package/dist-cjs/client/errors.js +33 -7
  164. package/dist-cjs/context.js +7 -2
  165. package/dist-cjs/domain/contracts.js +93 -0
  166. package/dist-cjs/domain/diagram/build.js +71 -20
  167. package/dist-cjs/domain/diagram/patch.js +179 -39
  168. package/dist-cjs/domain/diagram/style.js +130 -0
  169. package/dist-cjs/domain/diagram/summarize.js +28 -13
  170. package/dist-cjs/domain/requestIntegrations.js +228 -0
  171. package/dist-cjs/domain/requestShape.js +342 -121
  172. package/dist-cjs/domain/resolve.js +5 -1
  173. package/dist-cjs/domain/scenario/build.js +197 -65
  174. package/dist-cjs/domain/scenario/nodeConfig.js +260 -0
  175. package/dist-cjs/domain/scenario/summarize.js +86 -29
  176. package/dist-cjs/lib/mask.js +3 -48
  177. package/dist-cjs/lib/pagination.js +13 -8
  178. package/dist-cjs/lib/redact.js +53 -0
  179. package/dist-cjs/lib/strict.js +18 -0
  180. package/dist-cjs/prompts/index.js +7 -7
  181. package/dist-cjs/resources/docs/contracts.js +76 -0
  182. package/dist-cjs/resources/docs/diagramSchema.js +95 -28
  183. package/dist-cjs/resources/docs/overview.js +29 -7
  184. package/dist-cjs/resources/docs/requestSchema.js +85 -16
  185. package/dist-cjs/resources/generated/aiPacks.js +3 -2
  186. package/dist-cjs/resources/index.js +17 -17
  187. package/dist-cjs/run/executeCollection.js +16 -4
  188. package/dist-cjs/run/summarize.js +48 -41
  189. package/dist-cjs/server.js +1 -1
  190. package/dist-cjs/tools/catalog.js +18 -0
  191. package/dist-cjs/tools/collections.js +52 -40
  192. package/dist-cjs/tools/contracts.js +204 -0
  193. package/dist-cjs/tools/diagrams.js +19 -10
  194. package/dist-cjs/tools/environments.js +182 -62
  195. package/dist-cjs/tools/import.js +107 -16
  196. package/dist-cjs/tools/index.js +21 -2
  197. package/dist-cjs/tools/lookups.js +123 -11
  198. package/dist-cjs/tools/meta.js +9 -4
  199. package/dist-cjs/tools/requests.js +134 -46
  200. package/dist-cjs/tools/run.js +82 -26
  201. package/dist-cjs/tools/scenarios.js +235 -72
  202. package/dist-cjs/tools/shared.js +11 -2
  203. package/dist-cjs/tools/workspaces.js +43 -6
  204. package/package.json +2 -2
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerContractTools = void 0;
4
+ const zod_1 = require("zod");
5
+ const errors_js_1 = require("../client/errors.js");
6
+ const contracts_js_1 = require("../domain/contracts.js");
7
+ const requestShape_js_1 = require("../domain/requestShape.js");
8
+ const lookups_js_1 = require("./lookups.js");
9
+ const shared_js_1 = require("./shared.js");
10
+ // Contract checks on a saved request (the desktop's Contracts tab): field
11
+ // rules and the data-driven contract. Every runner evaluates them after the
12
+ // response — run_request / run_collection report them under `contracts`.
13
+ // Read callman://docs/contracts for the rule vocabulary and path grammar.
14
+ const requestArgs = {
15
+ workspace: shared_js_1.workspaceArg,
16
+ request: (0, shared_js_1.refArg)("Request", "(name, \"METHOD url\" or id)"),
17
+ collection: zod_1.z.string().max(200).optional().describe("Narrow a request name lookup to one collection"),
18
+ };
19
+ const ruleView = (rule) => ({ id: rule.id, path: rule.path, enabled: rule.enabled, rules: rule.rules });
20
+ const registerContractTools = (server, ctx) => {
21
+ server.registerTool("get_contract_checks", {
22
+ title: "Get a request's contract checks",
23
+ description: "Read a saved request's contract checks: field rules ({ id, path, rules, enabled }) and its data-driven contract (columns + rows, each row with expectedStatus and assertions). These run after every response of the request (desktop, CLI, run_request, run_collection).",
24
+ inputSchema: requestArgs,
25
+ annotations: shared_js_1.READ,
26
+ }, (0, shared_js_1.guarded)(async ({ workspace, request, collection }) => {
27
+ const { ws, value, note } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
28
+ const { request: found, annotation } = value;
29
+ const [rules, dataContract] = await Promise.all([ctx.api.listContractRules(ws.id, found.id), ctx.api.getDataContract(ws.id, found.id)]);
30
+ return (0, shared_js_1.ok)({
31
+ request: (0, requestShape_js_1.requestSummaryView)(found),
32
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
33
+ rules: rules.items.map(ruleView),
34
+ dataContract: dataContract.rows.length > 0 || dataContract.columns.length > 0 ? { columns: dataContract.columns, rows: dataContract.rows } : null,
35
+ ...(annotation || note ? { resolved: [{ input: request, matched: found.name, id: found.id, note: [annotation, note].filter(Boolean).join("; ") }] } : {}),
36
+ });
37
+ }));
38
+ server.registerTool("add_contract_rules", {
39
+ title: "Add contract rules to a request",
40
+ description: "Add field rules to a saved request's contract (one call, up to 200): each is { path, rules, enabled? } — e.g. { path: \"users[].id\", rules: { required: true, type: \"string\", format: \"uuid\" } } or { path: \"response.status\", rules: { equals: 200 } }. replace: true removes the request's existing rules first (needs requests:delete). Try them against a sample response with test_contract_rules. Rule vocabulary: callman://docs/contracts.",
41
+ inputSchema: {
42
+ ...requestArgs,
43
+ rules: zod_1.z.array(contracts_js_1.contractRuleInputSchema).min(1).max(200),
44
+ replace: zod_1.z.boolean().optional().describe("Delete the request's existing rules first"),
45
+ dryRun: shared_js_1.dryRunArg,
46
+ },
47
+ annotations: shared_js_1.WRITE,
48
+ }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, rules, replace, dryRun }) => {
49
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
50
+ const { request: found } = value;
51
+ const existing = (await ctx.api.listContractRules(ws.id, found.id)).items;
52
+ const duplicates = replace ? [] : rules.filter((rule) => existing.some((entry) => entry.path === rule.path)).map((rule) => rule.path);
53
+ const warnings = duplicates.length > 0 ? [`These paths already had rules and now have another one each: ${duplicates.join(", ")} — use update_contract_rule to change an existing rule instead.`] : [];
54
+ if (dryRun) {
55
+ return (0, shared_js_1.ok)({ dryRun: true, request: (0, requestShape_js_1.requestSummaryView)(found),
56
+ workspace: (0, lookups_js_1.workspaceTag)(ws), adding: rules.length, ...(replace ? { removing: existing.length } : {}), warnings });
57
+ }
58
+ if (replace) {
59
+ for (const rule of existing)
60
+ await ctx.api.deleteContractRule(ws.id, rule.id);
61
+ }
62
+ const created = await ctx.api.createContractRules(ws.id, { requestId: found.id, rules: rules.map((rule) => ({ ...rule, enabled: rule.enabled ?? true })) });
63
+ return (0, shared_js_1.ok)({
64
+ request: (0, requestShape_js_1.requestSummaryView)(found),
65
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
66
+ added: created.items.map(ruleView),
67
+ ...(replace ? { removed: existing.length } : {}),
68
+ ...(warnings.length > 0 ? { warnings } : {}),
69
+ });
70
+ }));
71
+ server.registerTool("update_contract_rule", {
72
+ title: "Change one contract rule",
73
+ description: "Change one contract rule by id (from get_contract_checks): its path, its rules (the whole rules object is replaced — send every check you want to keep) and/or enabled.",
74
+ inputSchema: {
75
+ workspace: shared_js_1.workspaceArg,
76
+ rule: zod_1.z.string().min(1).max(64).describe("Rule id from get_contract_checks"),
77
+ path: contracts_js_1.contractPathSchema.optional(),
78
+ rules: contracts_js_1.contractRulesSchema.optional(),
79
+ enabled: zod_1.z.boolean().optional(),
80
+ },
81
+ annotations: shared_js_1.IDEMPOTENT_WRITE,
82
+ }, (0, shared_js_1.guarded)(async ({ workspace, rule, path, rules, enabled }) => {
83
+ if (path === undefined && rules === undefined && enabled === undefined)
84
+ throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass path, rules and/or enabled.");
85
+ const body = { ...(path !== undefined ? { path } : {}), ...(rules !== undefined ? { rules } : {}), ...(enabled !== undefined ? { enabled } : {}) };
86
+ // A rule id lives in exactly one workspace; the others answer 404.
87
+ const { ws, value: updated } = await (0, lookups_js_1.findInWorkspaces)(ctx, workspace, "contract rule", rule, (candidate) => ctx.api.updateContractRule(candidate.id, rule, body), { matchOf: () => "id" });
88
+ return (0, shared_js_1.ok)({ rule: ruleView(updated), workspace: (0, lookups_js_1.workspaceTag)(ws) });
89
+ }));
90
+ server.registerTool("delete_contract_rules", {
91
+ title: "Delete contract rules",
92
+ description: "Delete contract rules of a request — the listed rule ids, or all of them when `rules` is omitted. Permanent — confirm with the user first.",
93
+ inputSchema: {
94
+ ...requestArgs,
95
+ rules: zod_1.z.array(zod_1.z.string().min(1).max(64)).max(200).optional().describe("Rule ids; omit to delete every rule of the request"),
96
+ confirm: shared_js_1.confirmArg,
97
+ },
98
+ annotations: shared_js_1.DESTRUCTIVE,
99
+ }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, rules, confirm }) => {
100
+ (0, shared_js_1.requireConfirm)(confirm, "contract rules");
101
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection, { exact: true });
102
+ const { request: found } = value;
103
+ const existing = (await ctx.api.listContractRules(ws.id, found.id)).items;
104
+ const ids = rules ?? existing.map((rule) => rule.id);
105
+ const unknown = ids.filter((id) => !existing.some((rule) => rule.id === id));
106
+ if (unknown.length > 0) {
107
+ throw new errors_js_1.ToolInputError("CONTRACT_RULE_NOT_FOUND", `Not rules of "${found.name}": ${unknown.join(", ")}.`, { rules: existing.map(ruleView) });
108
+ }
109
+ const deleted = [];
110
+ const failed = [];
111
+ for (const id of ids) {
112
+ try {
113
+ await ctx.api.deleteContractRule(ws.id, id);
114
+ deleted.push(id);
115
+ }
116
+ catch (error) {
117
+ failed.push({ rule: id, error: (0, shared_js_1.failureOf)(error) });
118
+ }
119
+ }
120
+ return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.requestSummaryView)(found),
121
+ workspace: (0, lookups_js_1.workspaceTag)(ws), deleted, failed });
122
+ }));
123
+ server.registerTool("test_contract_rules", {
124
+ title: "Test contract rules against a response body",
125
+ description: "Evaluate a request's ENABLED contract rules against a response body you provide (e.g. a sample from the API docs or a previous run) — on the server, nothing is sent to the API and nothing is saved. Returns pass/fail per rule. `response.*` rules (status, headers) need a real run: use run_request.",
126
+ inputSchema: {
127
+ ...requestArgs,
128
+ responseBody: zod_1.z.unknown().describe("The response body (JSON value) to check"),
129
+ },
130
+ annotations: shared_js_1.READ,
131
+ }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, responseBody }) => {
132
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
133
+ const { request: found } = value;
134
+ const result = await ctx.api.validateContractRules(ws.id, { requestId: found.id, responseBody: responseBody ?? null });
135
+ return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.requestSummaryView)(found),
136
+ workspace: (0, lookups_js_1.workspaceTag)(ws), summary: result.summary, results: result.results });
137
+ }));
138
+ server.registerTool("set_data_contract", {
139
+ title: "Set a request's data-driven contract",
140
+ description: `Replace a request's data-driven contract: the request runs once per row with the row's values as {{data.<column>}}, and each row checks its own expectedStatus and assertions (same rule vocabulary as contract rules). Limits: ${contracts_js_1.DATA_CONTRACT_LIMITS.columns} columns, ${contracts_js_1.DATA_CONTRACT_LIMITS.rows} rows, ${contracts_js_1.DATA_CONTRACT_LIMITS.assertions} assertions per row. Put {{data.<column>}} into the request's url/body/headers with update_request so the rows are used.`,
141
+ inputSchema: {
142
+ ...requestArgs,
143
+ columns: zod_1.z.array(zod_1.z.string().trim().min(1).max(200)).max(contracts_js_1.DATA_CONTRACT_LIMITS.columns).describe("Column names — referenced as {{data.<column>}}"),
144
+ rows: zod_1.z.array(contracts_js_1.dataContractRowSchema).min(1).max(contracts_js_1.DATA_CONTRACT_LIMITS.rows),
145
+ },
146
+ annotations: shared_js_1.IDEMPOTENT_WRITE,
147
+ }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, columns, rows }) => {
148
+ const undeclared = (0, contracts_js_1.undeclaredDataColumns)(columns, rows);
149
+ if (undeclared.length > 0) {
150
+ throw new errors_js_1.ToolInputError("DATA_COLUMN_UNDECLARED", `Rows use column(s) not in columns: ${undeclared.join(", ")}.`);
151
+ }
152
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection);
153
+ const { request: found } = value;
154
+ const saved = await ctx.api.putDataContract(ws.id, { requestId: found.id, columns, rows });
155
+ const text = JSON.stringify([found.url, found.headers, found.body, found.query]);
156
+ const unused = columns.filter((column) => !text.includes(`{{data.${column}}}`));
157
+ return (0, shared_js_1.ok)({
158
+ request: (0, requestShape_js_1.requestSummaryView)(found),
159
+ workspace: (0, lookups_js_1.workspaceTag)(ws),
160
+ dataContract: { columns: saved.columns, rows: saved.rows.length },
161
+ ...(unused.length > 0 ? { warnings: [`The request does not reference {{data.${unused.join("}}, {{data.")}}} yet — add them to its url, query, headers or body with update_request.`] } : {}),
162
+ });
163
+ }));
164
+ server.registerTool("delete_data_contract", {
165
+ title: "Delete a request's data-driven contract",
166
+ description: "Remove a request's data-driven contract (all rows). Permanent — confirm with the user first.",
167
+ inputSchema: { ...requestArgs, confirm: shared_js_1.confirmArg },
168
+ annotations: shared_js_1.DESTRUCTIVE,
169
+ }, (0, shared_js_1.guarded)(async ({ workspace, request, collection, confirm }) => {
170
+ (0, shared_js_1.requireConfirm)(confirm, "a data contract");
171
+ const { ws, value } = await (0, lookups_js_1.findRequest)(ctx, workspace, request, collection, { exact: true });
172
+ const { request: found } = value;
173
+ await ctx.api.deleteDataContract(ws.id, found.id);
174
+ return (0, shared_js_1.ok)({ request: (0, requestShape_js_1.requestSummaryView)(found),
175
+ workspace: (0, lookups_js_1.workspaceTag)(ws), deleted: true });
176
+ }));
177
+ server.registerTool("list_connections", {
178
+ title: "List workspace connections",
179
+ description: "List database / Redis / Kafka / notification / AI connections (id, name, type, provider, workspace — never credentials) across all your workspaces, or one `workspace`. A connection belongs to one workspace: use one from the same workspace as the request or scenario. Request database/Kafka/Redis steps and scenario db/redis/kafka/notification/ai nodes reference a connection by this id (or name). Connections are created and tested in the desktop app.",
180
+ inputSchema: {
181
+ workspace: shared_js_1.workspaceArg,
182
+ type: zod_1.z.enum(["database", "redis", "kafka", "notification", "ai"]).optional(),
183
+ },
184
+ annotations: shared_js_1.READ,
185
+ }, (0, shared_js_1.guarded)(async ({ workspace, type }) => {
186
+ const workspaces = await (0, lookups_js_1.workspacesForListing)(ctx, workspace);
187
+ const perWorkspace = await Promise.all(workspaces.map(async (ws) => (await ctx.api.listConnections(ws.id, type)).map((connection) => ({ ws, connection }))));
188
+ return (0, shared_js_1.ok)({
189
+ ...(workspaces.length === 1 ? { workspace: (0, lookups_js_1.workspaceTag)(workspaces[0]) } : { workspaces: workspaces.map(lookups_js_1.workspaceTag) }),
190
+ connections: perWorkspace.flat().map(({ ws, connection }) => ({
191
+ id: connection.id,
192
+ name: connection.name,
193
+ ...(workspaces.length > 1 ? { workspace: (0, lookups_js_1.workspaceTag)(ws) } : {}),
194
+ type: connection.type,
195
+ provider: connection.provider,
196
+ ...(connection.description ? { description: connection.description } : {}),
197
+ ...(connection.isActive === false ? { active: false } : {}),
198
+ ...(connection.managed ? { managed: true } : {}),
199
+ ...(connection.lastTestStatus ? { lastTest: connection.lastTestStatus } : {}),
200
+ })),
201
+ });
202
+ }));
203
+ };
204
+ exports.registerContractTools = registerContractTools;
@@ -31,12 +31,12 @@ const registerDiagramTools = (server, ctx) => {
31
31
  const all = await (0, lookups_js_1.loadDiagrams)(ctx);
32
32
  const needle = query?.trim().toLowerCase();
33
33
  const filtered = needle ? all.filter((diagram) => diagram.name.toLowerCase().includes(needle)) : all;
34
- const page = (0, pagination_js_1.sliceWithCursor)(filtered, cursor, limit);
34
+ const page = (0, pagination_js_1.sliceWithCursor)(filtered, cursor, limit, needle ?? "");
35
35
  return (0, shared_js_1.ok)({ diagrams: page.items.map(summarize_js_1.diagramListView), total: page.total, nextCursor: page.nextCursor });
36
36
  }));
37
37
  server.registerTool("get_diagram", {
38
38
  title: "Get a diagram",
39
- description: "Read a diagram: `graph` (nodes with type/label/parent and edges with their meaning — enough to describe or edit it) or `full` (the complete DiagramDocument, up to 512 KB).",
39
+ description: "Read a diagram: `summary` (counts, owner, sharing), `graph` (default — nodes with type/label/parent/style and edges with semantic/color, in the same shape the write tools accept: enough to describe, restyle or edit it) or `full` (the complete stored DiagramDocument with positions, sizes and routes, up to 512 KB).",
40
40
  inputSchema: { diagram: (0, shared_js_1.refArg)("Diagram"), view: zod_1.z.enum(["summary", "graph", "full"]).default("graph") },
41
41
  annotations: shared_js_1.READ,
42
42
  }, (0, shared_js_1.guarded)(async ({ diagram, view }) => {
@@ -48,7 +48,7 @@ const registerDiagramTools = (server, ctx) => {
48
48
  }));
49
49
  server.registerTool("create_diagram", {
50
50
  title: "Create a system-design diagram",
51
- description: "Create a diagram from typed nodes and edges. Node `type` must come from callman://diagram/components (e.g. net.service, net.gateway, db.postgres, mq.kafka, aws.lambda, flow.process); unknown types are rejected with suggestions unless strictTypes: false. Group nodes with a container (`parent`). Edge `semantic`: sync (default), async, fallback, bus. Layout is automatic when nodes have no positions. Keep it readable: 8–25 nodes, short labels.",
51
+ description: "Create a diagram from typed nodes and edges. Node `type` must come from callman://diagram/components (e.g. net.service, net.api-gateway, db.postgres, mq.kafka, aws.lambda, flow.process); unknown types are rejected with suggestions unless strictTypes: false. Group nodes inside a container node — frame, pool.horizontal or pool.vertical — with `parent`. Style nodes with `style` (fill / stroke / textColor as CSS colors, strokeWidth, dashed, radius, bold, fontSize, opacity…) and edges with `semantic` (sync default, async, fallback, bus) plus an optional `color`. Layout is automatic when nodes have no positions. Read callman://docs/diagram-schema for every field. Keep it readable: 8–25 nodes, short labels.",
52
52
  inputSchema: {
53
53
  name: zod_1.z.string().min(1).max(200).describe("Diagram name (library entry)"),
54
54
  ...build_js_1.diagramInputSchema.shape,
@@ -81,7 +81,7 @@ const registerDiagramTools = (server, ctx) => {
81
81
  }));
82
82
  server.registerTool("update_diagram", {
83
83
  title: "Replace a diagram's content",
84
- description: "Replace a diagram's whole document (nodes + edges) and/or rename it, keeping its identity and sharing. For small edits prefer patch_diagram.",
84
+ description: "Replace a diagram's whole document (nodes + edges, same fields as create_diagram) and/or rename it, retitle it or change its canvas, keeping its identity and sharing. Passing nodes replaces ALL nodes and edges (omitted edges are removed). For small edits — including restyling — prefer patch_diagram.",
85
85
  inputSchema: {
86
86
  diagram: (0, shared_js_1.refArg)("Diagram"),
87
87
  name: zod_1.z.string().min(1).max(200).optional(),
@@ -95,8 +95,9 @@ const registerDiagramTools = (server, ctx) => {
95
95
  },
96
96
  annotations: shared_js_1.IDEMPOTENT_WRITE,
97
97
  }, (0, shared_js_1.guarded)(async ({ diagram, name, title, nodes, edges, canvas, autoLayout, direction, strictTypes }) => {
98
- if (name === undefined && nodes === undefined)
99
- throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass name and/or nodes (+ edges).");
98
+ if (name === undefined && nodes === undefined && title === undefined && canvas === undefined) {
99
+ throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass name, title, canvas and/or nodes (+ edges).");
100
+ }
100
101
  const resolved = await (0, lookups_js_1.resolveDiagram)(ctx, diagram);
101
102
  if (!resolved.item.canEdit)
102
103
  throw new errors_js_1.ToolInputError("DIAGRAM_READ_ONLY", "You have read-only access to this diagram.");
@@ -108,14 +109,21 @@ const registerDiagramTools = (server, ctx) => {
108
109
  const built = (0, build_js_1.buildDiagramDocument)({ title: title ?? existing.meta.title, nodes, edges: edges ?? [], canvas: canvas ?? existing.canvas }, { strictTypes, existingId: existing.id, createdAt: existing.meta.createdAt });
109
110
  document = built.document;
110
111
  warnings = built.warnings;
112
+ if (edges === undefined && existing.edges.length > 0) {
113
+ warnings.push(`edges was omitted, so the ${existing.edges.length} existing edge(s) were removed. Pass edges to keep connections, or use patch_diagram for partial edits.`);
114
+ }
111
115
  if (autoLayout || built.unpositionedNodeIds.length > 0) {
112
116
  const laid = await (0, layout_js_1.layoutDocument)(document, { direction });
113
117
  document = laid.document;
114
118
  layoutNote = laid.warning ?? `Laid out with ${laid.engine}.`;
115
119
  }
116
120
  }
117
- else if (title !== undefined) {
118
- document = { ...existing, meta: { ...existing.meta, title, updatedAt: new Date().toISOString() } };
121
+ else if (title !== undefined || canvas !== undefined) {
122
+ document = {
123
+ ...existing,
124
+ ...(canvas !== undefined ? { canvas: { ...existing.canvas, ...canvas } } : {}),
125
+ meta: { ...existing.meta, ...(title !== undefined ? { title } : {}), updatedAt: new Date().toISOString() },
126
+ };
119
127
  }
120
128
  const updated = await ctx.api.updateDiagram(resolved.item.id, { ...(name !== undefined ? { name: name.trim() } : {}), ...(document ? { document } : {}) });
121
129
  ctx.invalidate("diagrams");
@@ -123,7 +131,7 @@ const registerDiagramTools = (server, ctx) => {
123
131
  }));
124
132
  server.registerTool("patch_diagram", {
125
133
  title: "Edit a diagram incrementally",
126
- description: "Apply operations to a diagram without resending it: addNode, updateNode, removeNode (drops its edges), addEdge, updateEdge, removeEdge, setTitle. `relayout`: none | new (place only new nodes, default) | all (re-run automatic layout).",
134
+ description: "Apply operations to a diagram without resending it: addNode, updateNode, removeNode (drops its edges), addEdge, updateEdge, removeEdge, setTitle. updateNode `changes` MERGE: style and data keys you pass change, a key set to null is removed. updateEdge can change semantic, color, label, arrows and ends. Moving a node (position/size/parent) re-routes its edges. `relayout`: none | new (place only new nodes, default) | all (re-run automatic layout). Call get_diagram (view: graph) first to see node ids and current styles.",
127
135
  inputSchema: {
128
136
  diagram: (0, shared_js_1.refArg)("Diagram"),
129
137
  ops: zod_1.z.array(patch_js_1.diagramPatchOpSchema).min(1).max(200),
@@ -157,6 +165,7 @@ const registerDiagramTools = (server, ctx) => {
157
165
  added: patched.addedNodeIds,
158
166
  removedNodes: patched.removedNodeIds,
159
167
  removedEdges: patched.removedEdgeIds,
168
+ ...(patched.reroutedEdgeIds.length > 0 ? { reroutedEdges: patched.reroutedEdgeIds } : {}),
160
169
  warnings: patched.warnings,
161
170
  ...(layoutNote ? { layout: layoutNote } : {}),
162
171
  resolved: [(0, resolve_js_1.resolvedSummary)(diagram, resolved)],
@@ -169,7 +178,7 @@ const registerDiagramTools = (server, ctx) => {
169
178
  annotations: shared_js_1.DESTRUCTIVE,
170
179
  }, (0, shared_js_1.guarded)(async ({ diagram, confirm }) => {
171
180
  (0, shared_js_1.requireConfirm)(confirm, "a diagram");
172
- const resolved = await (0, lookups_js_1.resolveDiagram)(ctx, diagram);
181
+ const resolved = await (0, lookups_js_1.resolveDiagram)(ctx, diagram, { exact: true });
173
182
  await ctx.api.deleteDiagram(resolved.item.id);
174
183
  ctx.invalidate("diagrams");
175
184
  return (0, shared_js_1.ok)({ deleted: { id: resolved.item.id, name: resolved.item.name }, resolved: [(0, resolve_js_1.resolvedSummary)(diagram, resolved)] });