mmt-mcp 1.44.1-pre → 1.44.2-pre

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 (22) hide show
  1. package/dist/examples/basic/05_api_test_suite/README.md +2 -2
  2. package/dist/examples/basic/05_api_test_suite/echo.mmt +0 -2
  3. package/dist/examples/professional/07_mock_server_timeout/README.md +1 -1
  4. package/dist/examples/professional/07_mock_server_timeout/test/slow_timeout_test.mmt +1 -1
  5. package/dist/examples/professional/11_token_resolution/README.md +2 -0
  6. package/dist/examples/professional/11_token_resolution/api/current_xml_body.mmt +0 -1
  7. package/dist/examples/professional/11_token_resolution/api/env_xml_body.mmt +0 -1
  8. package/dist/examples/professional/11_token_resolution/api/input_json_body.mmt +8 -0
  9. package/dist/examples/professional/11_token_resolution/api/input_urlencoded_body.mmt +8 -1
  10. package/dist/examples/professional/11_token_resolution/api/input_xml_body.mmt +28 -0
  11. package/dist/examples/professional/11_token_resolution/api/random_urlencoded_body.mmt +0 -1
  12. package/dist/examples/professional/11_token_resolution/api/random_xml_body.mmt +0 -1
  13. package/dist/examples/professional/11_token_resolution/test/current_xml_body_test.mmt +0 -1
  14. package/dist/examples/professional/11_token_resolution/test/env_xml_body_test.mmt +0 -1
  15. package/dist/examples/professional/11_token_resolution/test/input_json_body_test.mmt +2 -0
  16. package/dist/examples/professional/11_token_resolution/test/input_urlencoded_body_test.mmt +2 -1
  17. package/dist/examples/professional/11_token_resolution/test/input_xml_body_test.mmt +8 -0
  18. package/dist/examples/professional/11_token_resolution/test/random_urlencoded_body_test.mmt +0 -1
  19. package/dist/examples/professional/11_token_resolution/test/random_xml_body_test.mmt +0 -1
  20. package/dist/server.cjs +52 -6
  21. package/dist/server.js +52 -6
  22. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
- # API example `expect` / `require` in a suite
1
+ # API example `expect` in a suite
2
2
 
3
- This example shows a `type: api` file with per-example `expect` / `require`, listed in a suite.
3
+ This example shows a `type: api` file with per-example `expect`, listed in a suite.
4
4
 
5
5
  ```bash
6
6
  npx testlight run suite.mmt
@@ -9,5 +9,3 @@ examples:
9
9
  title: Happy path
10
10
  expect:
11
11
  status_code: 200
12
- require:
13
- status_code: == 200
@@ -23,4 +23,4 @@ This professional example demonstrates a request timeout against a delayed local
23
23
  npx testlight run examples/professional/07_mock_server_timeout/test/slow_timeout_test.mmt
24
24
  ```
25
25
 
26
- This example is expected to fail with a request timeout because the request timeout is shorter than the mock response delay.
26
+ This example should pass: the request times out (`status: -1`) because the request timeout is shorter than the mock response delay.
@@ -11,5 +11,5 @@ steps:
11
11
  - call: slow_request
12
12
  id: timeout_result
13
13
  expect:
14
- status: 200
14
+ status: -1
15
15
  report: all
@@ -22,6 +22,8 @@ Each request also sends the other spellings of one token:
22
22
 
23
23
  A whole-field example expect uses the same token (`e:account_age`, `i:username`, `c:day`, `r:uuid`, including a slice such as `i:city[0:3]`). `e:`, `i:`, and `c:` resolve to the value that was sent. Text formats still match when a number or boolean was sent as text. `r:` is checked as a regex of that generator, because each draw is new. `c:epoch` changes every second, so that expect stays a range. Text wrapped around a token (`asda42`, `asda15`) stays a concrete string or a regex.
24
24
 
25
+ The `i:` examples also send `version` (`"1.0"`) and `price` (`9.5`). `input_xml_body.mmt` puts numeric, boolean, and decimal tokens in XML attributes (`<item number="100" flag="true" …>`). Attribute values are always quoted, so the request stays valid XML.
26
+
25
27
  ## Environment
26
28
 
27
29
  `multimeter.mmt` is loaded as the workspace environment. The active values are:
@@ -16,7 +16,6 @@ outputs:
16
16
  quoted_curly: body.body.quoted_curly
17
17
  curly_whole: body.body.curly_whole
18
18
  text_slice: body.body.text_slice
19
- missing_embed: body.body.missing_embed
20
19
  status_code: status
21
20
  url: https://test.mmt.dev/echo
22
21
  method: post
@@ -18,7 +18,6 @@ outputs:
18
18
  quoted_curly: body.body.quoted_curly
19
19
  curly_whole: body.body.curly_whole
20
20
  text_slice: body.body.text_slice
21
- missing_embed: body.body.missing_embed
22
21
  status_code: status
23
22
  url: https://test.mmt.dev/echo
24
23
  method: post
@@ -6,6 +6,8 @@ inputs:
6
6
  age: 100
7
7
  active: true
8
8
  city: Berlin
9
+ version: "1.0"
10
+ price: 9.5
9
11
  outputs:
10
12
  username: body.body.username
11
13
  age: body.body.age
@@ -24,6 +26,8 @@ outputs:
24
26
  curly_whole: body.body.curly_whole
25
27
  text_slice: body.body.text_slice
26
28
  missing_embed: body.body.missing_embed
29
+ version_text: body.body.version
30
+ price_value: body.body.price
27
31
  status_code: status
28
32
  url: https://test.mmt.dev/echo
29
33
  method: post
@@ -46,6 +50,8 @@ body:
46
50
  curly_whole: {{i:city}}
47
51
  text_slice: <<i:city[0:3]>>
48
52
  missing_embed: "x<<i:nickname>>y"
53
+ version: i:version
54
+ price: i:price
49
55
  examples:
50
56
  - id: defaults
51
57
  title: Check outputs
@@ -68,3 +74,5 @@ examples:
68
74
  curly_whole: i:city
69
75
  text_slice: i:city[0:3]
70
76
  missing_embed: "xi:nicknamey"
77
+ version_text: "1.0"
78
+ price_value: 9.5
@@ -6,6 +6,8 @@ inputs:
6
6
  age: 100
7
7
  active: true
8
8
  city: Berlin
9
+ version: "1.0"
10
+ price: 9.5
9
11
  outputs:
10
12
  username: body.body.username
11
13
  age: body.body.age
@@ -22,7 +24,8 @@ outputs:
22
24
  quoted_curly: body.body.quoted_curly
23
25
  curly_whole: body.body.curly_whole
24
26
  text_slice: body.body.text_slice
25
- missing_embed: body.body.missing_embed
27
+ version_text: body.body.version
28
+ price_value: body.body.price
26
29
  status_code: status
27
30
  url: https://test.mmt.dev/echo
28
31
  method: post
@@ -43,6 +46,8 @@ body:
43
46
  quoted_curly: "{{i:username}}"
44
47
  curly_whole: {{i:city}}
45
48
  text_slice: <<i:city[0:3]>>
49
+ version: i:version
50
+ price: i:price
46
51
  examples:
47
52
  - id: defaults
48
53
  title: Check outputs
@@ -63,3 +68,5 @@ examples:
63
68
  quoted_curly: i:username
64
69
  curly_whole: i:city
65
70
  text_slice: i:city[0:3]
71
+ version_text: "1.0"
72
+ price_value: "9.5"
@@ -6,6 +6,8 @@ inputs:
6
6
  age: 100
7
7
  active: true
8
8
  city: Berlin
9
+ version: "1.0"
10
+ price: 9.5
9
11
  outputs:
10
12
  username: body.body.username
11
13
  age: body.body.age
@@ -23,6 +25,14 @@ outputs:
23
25
  curly_whole: body.body.curly_whole
24
26
  text_slice: body.body.text_slice
25
27
  missing_embed: body.body.missing_embed
28
+ attr_number: body.body.item.number
29
+ attr_bool: body.body.item.flag
30
+ attr_text: body.body.item.version
31
+ attr_angle: body.body.item.angle
32
+ attr_embedded: body.body.item.embedded
33
+ attr_literal: body.body.item.literal
34
+ attr_decimal: body.body.item.decimal
35
+ price_text: body.body.price
26
36
  status_code: status
27
37
  url: https://test.mmt.dev/echo
28
38
  method: post
@@ -45,6 +55,16 @@ body:
45
55
  curly_whole: {{i:city}}
46
56
  text_slice: <<i:city[0:3]>>
47
57
  missing_embed: "x<<i:nickname>>y"
58
+ item:
59
+ _attributes:
60
+ number: i:age
61
+ flag: i:active
62
+ version: i:version
63
+ angle: <<i:age>>
64
+ embedded: "n<<i:age>>"
65
+ literal: "1.0"
66
+ decimal: i:price
67
+ price: i:price
48
68
  examples:
49
69
  - id: defaults
50
70
  title: Check outputs
@@ -66,3 +86,11 @@ examples:
66
86
  curly_whole: i:city
67
87
  text_slice: i:city[0:3]
68
88
  missing_embed: "xi:nicknamey"
89
+ attr_number: "100"
90
+ attr_bool: "true"
91
+ attr_text: "1.0"
92
+ attr_angle: "100"
93
+ attr_embedded: "n100"
94
+ attr_literal: "1.0"
95
+ attr_decimal: "9.5"
96
+ price_text: "9.5"
@@ -15,7 +15,6 @@ outputs:
15
15
  angle_both: body.body.angle_both
16
16
  quoted_curly: body.body.quoted_curly
17
17
  curly_whole: body.body.curly_whole
18
- missing_embed: body.body.missing_embed
19
18
  status_code: status
20
19
  url: https://test.mmt.dev/echo
21
20
  method: post
@@ -15,7 +15,6 @@ outputs:
15
15
  angle_both: body.body.angle_both
16
16
  quoted_curly: body.body.quoted_curly
17
17
  curly_whole: body.body.curly_whole
18
- missing_embed: body.body.missing_embed
19
18
  status_code: status
20
19
  url: https://test.mmt.dev/echo
21
20
  method: post
@@ -22,4 +22,3 @@ steps:
22
22
  quoted_curly: c:day
23
23
  curly_whole: "!= null"
24
24
  text_slice: "=# 1"
25
- missing_embed: "xc:not_a_tokeny"
@@ -24,4 +24,3 @@ steps:
24
24
  quoted_curly: "42"
25
25
  curly_whole: "10"
26
26
  text_slice: "https://"
27
- missing_embed: "xe:regiony"
@@ -27,3 +27,5 @@ steps:
27
27
  curly_whole: "Berlin"
28
28
  text_slice: "Ber"
29
29
  missing_embed: "xi:nicknamey"
30
+ version_text: "1.0"
31
+ price_value: 9.5
@@ -25,4 +25,5 @@ steps:
25
25
  quoted_curly: i:username
26
26
  curly_whole: "Berlin"
27
27
  text_slice: "Ber"
28
- missing_embed: "xi:nicknamey"
28
+ version_text: "1.0"
29
+ price_value: "9.5"
@@ -26,3 +26,11 @@ steps:
26
26
  curly_whole: "Berlin"
27
27
  text_slice: "Ber"
28
28
  missing_embed: "xi:nicknamey"
29
+ attr_number: "100"
30
+ attr_bool: "true"
31
+ attr_text: "1.0"
32
+ attr_angle: "100"
33
+ attr_embedded: "n100"
34
+ attr_literal: "1.0"
35
+ attr_decimal: "9.5"
36
+ price_text: "9.5"
@@ -25,4 +25,3 @@ steps:
25
25
  curly_whole:
26
26
  - "!= null"
27
27
  - "!C r:"
28
- missing_embed: "xr:not_a_tokeny"
@@ -25,4 +25,3 @@ steps:
25
25
  curly_whole:
26
26
  - "!= null"
27
27
  - "!C r:"
28
- missing_embed: "xr:not_a_tokeny"
package/dist/server.cjs CHANGED
@@ -14766,6 +14766,46 @@ function yamlValueToInputBox(val) {
14766
14766
  }
14767
14767
  return String(val);
14768
14768
  }
14769
+ function inputBoxToYamlValue(val) {
14770
+ if (val === null || val === void 0) {
14771
+ return "";
14772
+ }
14773
+ if (typeof val !== "string") {
14774
+ return val;
14775
+ }
14776
+ const t = val.trim();
14777
+ if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
14778
+ const inner = t.slice(1, -1);
14779
+ if (isPlainTokenScalar(inner)) {
14780
+ return wrapLiteralToken(inner);
14781
+ }
14782
+ return inner;
14783
+ }
14784
+ if (t === "omit") {
14785
+ return OMIT_SENTINEL2;
14786
+ }
14787
+ if (t === "null") {
14788
+ return null;
14789
+ }
14790
+ if (t.toLowerCase() === "true") {
14791
+ return true;
14792
+ }
14793
+ if (t.toLowerCase() === "false") {
14794
+ return false;
14795
+ }
14796
+ const num = Number(t);
14797
+ if (!Number.isNaN(num) && t !== "") {
14798
+ return num;
14799
+ }
14800
+ if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]")) {
14801
+ try {
14802
+ return JSON.parse(t);
14803
+ } catch {
14804
+ return val;
14805
+ }
14806
+ }
14807
+ return val;
14808
+ }
14769
14809
 
14770
14810
  // ../core/src/yamlAstMerge.ts
14771
14811
  var import_yaml = require("yaml");
@@ -15282,7 +15322,17 @@ function formValueToString(value, keepLiteralMarkers = false) {
15282
15322
  }
15283
15323
  return stringifyJsonWithRuntimeTokens(value, false);
15284
15324
  }
15325
+ function isNonCanonicalNumberText(text) {
15326
+ if (isLiteralTokenValue(text) || isTokenLikeScalar(text)) {
15327
+ return false;
15328
+ }
15329
+ const typed = inputBoxToYamlValue(text);
15330
+ return typeof typed === "number" && String(typed) !== text;
15331
+ }
15285
15332
  function editorFormValue(value) {
15333
+ if (typeof value === "string" && isNonCanonicalNumberText(value)) {
15334
+ return value;
15335
+ }
15286
15336
  if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
15287
15337
  return yamlValueToInputBox(value);
15288
15338
  }
@@ -15916,18 +15966,14 @@ var apiToJSfunc = async (ctx) => {
15916
15966
  "${JSON.stringify($1)}"
15917
15967
  );
15918
15968
  }
15919
- const entries = Object.entries(ctx.api.inputs ?? {});
15969
+ const entries = reqFormat2 === "json" ? Object.entries(ctx.api.inputs ?? {}) : [];
15920
15970
  for (const [name, value] of entries) {
15921
15971
  const quoted = new RegExp(`"\\$\\{${name}\\}"`, "g");
15922
- if (reqFormat2 === "json" && typeof value !== "string") {
15972
+ if (typeof value !== "string") {
15923
15973
  formattedBody = formattedBody.replace(
15924
15974
  quoted,
15925
15975
  () => "${JSON.stringify(" + name + ")}"
15926
15976
  );
15927
- } else if (typeof value === "string") {
15928
- formattedBody = formattedBody.replace(quoted, '"${' + name + '}"');
15929
- } else {
15930
- formattedBody = formattedBody.replace(quoted, "${" + name + "}");
15931
15977
  }
15932
15978
  }
15933
15979
  if (reqFormat2 === "json") {
package/dist/server.js CHANGED
@@ -14766,6 +14766,46 @@ function yamlValueToInputBox(val) {
14766
14766
  }
14767
14767
  return String(val);
14768
14768
  }
14769
+ function inputBoxToYamlValue(val) {
14770
+ if (val === null || val === void 0) {
14771
+ return "";
14772
+ }
14773
+ if (typeof val !== "string") {
14774
+ return val;
14775
+ }
14776
+ const t = val.trim();
14777
+ if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
14778
+ const inner = t.slice(1, -1);
14779
+ if (isPlainTokenScalar(inner)) {
14780
+ return wrapLiteralToken(inner);
14781
+ }
14782
+ return inner;
14783
+ }
14784
+ if (t === "omit") {
14785
+ return OMIT_SENTINEL2;
14786
+ }
14787
+ if (t === "null") {
14788
+ return null;
14789
+ }
14790
+ if (t.toLowerCase() === "true") {
14791
+ return true;
14792
+ }
14793
+ if (t.toLowerCase() === "false") {
14794
+ return false;
14795
+ }
14796
+ const num = Number(t);
14797
+ if (!Number.isNaN(num) && t !== "") {
14798
+ return num;
14799
+ }
14800
+ if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]")) {
14801
+ try {
14802
+ return JSON.parse(t);
14803
+ } catch {
14804
+ return val;
14805
+ }
14806
+ }
14807
+ return val;
14808
+ }
14769
14809
 
14770
14810
  // ../core/src/yamlAstMerge.ts
14771
14811
  var import_yaml = require("yaml");
@@ -15282,7 +15322,17 @@ function formValueToString(value, keepLiteralMarkers = false) {
15282
15322
  }
15283
15323
  return stringifyJsonWithRuntimeTokens(value, false);
15284
15324
  }
15325
+ function isNonCanonicalNumberText(text) {
15326
+ if (isLiteralTokenValue(text) || isTokenLikeScalar(text)) {
15327
+ return false;
15328
+ }
15329
+ const typed = inputBoxToYamlValue(text);
15330
+ return typeof typed === "number" && String(typed) !== text;
15331
+ }
15285
15332
  function editorFormValue(value) {
15333
+ if (typeof value === "string" && isNonCanonicalNumberText(value)) {
15334
+ return value;
15335
+ }
15286
15336
  if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
15287
15337
  return yamlValueToInputBox(value);
15288
15338
  }
@@ -15916,18 +15966,14 @@ var apiToJSfunc = async (ctx) => {
15916
15966
  "${JSON.stringify($1)}"
15917
15967
  );
15918
15968
  }
15919
- const entries = Object.entries(ctx.api.inputs ?? {});
15969
+ const entries = reqFormat2 === "json" ? Object.entries(ctx.api.inputs ?? {}) : [];
15920
15970
  for (const [name, value] of entries) {
15921
15971
  const quoted = new RegExp(`"\\$\\{${name}\\}"`, "g");
15922
- if (reqFormat2 === "json" && typeof value !== "string") {
15972
+ if (typeof value !== "string") {
15923
15973
  formattedBody = formattedBody.replace(
15924
15974
  quoted,
15925
15975
  () => "${JSON.stringify(" + name + ")}"
15926
15976
  );
15927
- } else if (typeof value === "string") {
15928
- formattedBody = formattedBody.replace(quoted, '"${' + name + '}"');
15929
- } else {
15930
- formattedBody = formattedBody.replace(quoted, "${" + name + "}");
15931
15977
  }
15932
15978
  }
15933
15979
  if (reqFormat2 === "json") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mmt-mcp",
3
- "version": "1.44.1-pre",
3
+ "version": "1.44.2-pre",
4
4
  "description": "Multimeter MCP server for Cursor, Copilot, and Claude. Scaffold, validate, format, and run YAML .mmt API tests. Do not invent .mmt syntax.",
5
5
  "mcpName": "io.github.mshobeyri/multimeter",
6
6
  "type": "module",