functionalscript 0.29.1 → 0.31.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 (191) hide show
  1. package/fs/asn.1/module.f.js +18 -22
  2. package/fs/asn.1/proof.f.d.ts +5 -0
  3. package/fs/asn.1/proof.f.js +38 -2
  4. package/fs/base64/module.f.d.ts +9 -0
  5. package/fs/base64/module.f.js +60 -0
  6. package/fs/base64/proof.f.d.ts +11 -0
  7. package/fs/base64/proof.f.js +117 -0
  8. package/fs/base_n/module.f.d.ts +44 -0
  9. package/fs/base_n/module.f.js +54 -0
  10. package/fs/base_n/proof.f.d.ts +10 -0
  11. package/fs/base_n/proof.f.js +86 -0
  12. package/fs/bnf/data/module.f.d.ts +4 -9
  13. package/fs/bnf/data/module.f.js +4 -3
  14. package/fs/bnf/module.f.d.ts +3 -4
  15. package/fs/bnf/module.f.js +3 -2
  16. package/fs/bnf/proof.f.d.ts +9 -0
  17. package/fs/bnf/proof.f.js +98 -1
  18. package/fs/cas/mcp/module.f.d.ts +40 -0
  19. package/fs/cas/mcp/module.f.js +223 -0
  20. package/fs/cas/mcp/proof.f.d.ts +30 -0
  21. package/fs/cas/mcp/proof.f.js +310 -0
  22. package/fs/cas/module.f.d.ts +9 -3
  23. package/fs/cas/module.f.js +104 -69
  24. package/fs/cas/proof.f.d.ts +2 -0
  25. package/fs/cas/proof.f.js +30 -1
  26. package/fs/cbase32/module.f.d.ts +1 -1
  27. package/fs/cbase32/module.f.js +19 -40
  28. package/fs/cbase32/proof.f.d.ts +1 -0
  29. package/fs/cbase32/proof.f.js +15 -0
  30. package/fs/ci/bun/module.f.d.ts +2 -2
  31. package/fs/ci/bun/module.f.js +7 -14
  32. package/fs/ci/common/module.f.d.ts +3 -1
  33. package/fs/ci/common/module.f.js +9 -2
  34. package/fs/ci/config/module.f.d.ts +7 -5
  35. package/fs/ci/config/module.f.js +11 -5
  36. package/fs/ci/deno/module.f.d.ts +1 -1
  37. package/fs/ci/deno/module.f.js +7 -5
  38. package/fs/ci/module.f.d.ts +6 -3
  39. package/fs/ci/module.f.js +24 -54
  40. package/fs/ci/node/module.f.d.ts +3 -3
  41. package/fs/ci/node/module.f.js +30 -26
  42. package/fs/ci/playwright/module.f.js +1 -1
  43. package/fs/ci/proof.f.d.ts +6 -7
  44. package/fs/ci/proof.f.js +63 -36
  45. package/fs/ci/rust/module.f.d.ts +2 -1
  46. package/fs/ci/rust/module.f.js +33 -16
  47. package/fs/cli/module.f.d.ts +3 -1
  48. package/fs/cli/module.f.js +22 -12
  49. package/fs/cli/proof.f.d.ts +5 -0
  50. package/fs/cli/proof.f.js +76 -0
  51. package/fs/crypto/hmac/proof.f.d.ts +1 -0
  52. package/fs/crypto/hmac/proof.f.js +8 -0
  53. package/fs/crypto/secp/proof.f.d.ts +1 -0
  54. package/fs/crypto/secp/proof.f.js +10 -0
  55. package/fs/crypto/sign/module.f.js +1 -4
  56. package/fs/dev/module.f.d.ts +5 -3
  57. package/fs/dev/module.f.js +13 -2
  58. package/fs/dev/package_json/proof.f.d.ts +1 -0
  59. package/fs/dev/package_json/proof.f.js +5 -0
  60. package/fs/dev/proof.f.d.ts +1 -0
  61. package/fs/dev/proof.f.js +12 -1
  62. package/fs/djs/ast/module.f.d.ts +1 -1
  63. package/fs/djs/ast/proof.f.d.ts +5 -0
  64. package/fs/djs/ast/proof.f.js +17 -0
  65. package/fs/djs/module.f.d.ts +1 -1
  66. package/fs/djs/module.f.js +2 -3
  67. package/fs/djs/serializer/module.f.js +24 -11
  68. package/fs/djs/transpiler/module.f.js +3 -4
  69. package/fs/effects/memory/module.f.d.ts +37 -0
  70. package/fs/effects/memory/module.f.js +24 -0
  71. package/fs/effects/memory/proof.f.d.ts +6 -0
  72. package/fs/effects/memory/proof.f.js +64 -0
  73. package/fs/effects/mock/module.f.d.ts +7 -2
  74. package/fs/effects/mock/module.f.js +12 -8
  75. package/fs/effects/module.d.ts +1 -1
  76. package/fs/effects/module.f.d.ts +41 -0
  77. package/fs/effects/module.f.js +25 -0
  78. package/fs/effects/module.js +6 -7
  79. package/fs/effects/node/memory/module.d.ts +20 -0
  80. package/fs/effects/node/memory/module.js +44 -0
  81. package/fs/effects/node/memory/proof.d.ts +10 -0
  82. package/fs/effects/node/memory/proof.js +35 -0
  83. package/fs/effects/node/module.d.ts +1 -1
  84. package/fs/effects/node/module.f.d.ts +54 -8
  85. package/fs/effects/node/module.f.js +55 -4
  86. package/fs/effects/node/module.js +166 -135
  87. package/fs/effects/node/proof.f.d.ts +10 -0
  88. package/fs/effects/node/proof.f.js +88 -21
  89. package/fs/effects/node/virtual/module.f.d.ts +6 -0
  90. package/fs/effects/node/virtual/module.f.js +48 -16
  91. package/fs/effects/node/virtual/proof.f.d.ts +17 -0
  92. package/fs/effects/node/virtual/proof.f.js +79 -0
  93. package/fs/effects/proof.f.d.ts +5 -0
  94. package/fs/effects/proof.f.js +54 -34
  95. package/fs/emergent_testing/module.f.d.ts +6 -1
  96. package/fs/emergent_testing/module.f.js +8 -3
  97. package/fs/emergent_testing/proof.f.d.ts +11 -0
  98. package/fs/emergent_testing/proof.f.js +52 -16
  99. package/fs/fjs/module.f.js +2 -2
  100. package/fs/fjs/proof.f.d.ts +8 -0
  101. package/fs/fjs/proof.f.js +39 -0
  102. package/fs/fsc/module.f.d.ts +5 -0
  103. package/fs/fsc/module.f.js +17 -27
  104. package/fs/fsc/proof.f.d.ts +8 -0
  105. package/fs/fsc/proof.f.js +48 -2
  106. package/fs/fsm/module.f.d.ts +2 -3
  107. package/fs/fsm/module.f.js +1 -1
  108. package/fs/html/module.f.d.ts +2 -3
  109. package/fs/html/module.f.js +7 -6
  110. package/fs/html/proof.f.d.ts +1 -0
  111. package/fs/html/proof.f.js +7 -0
  112. package/fs/js/tokenizer/module.f.js +1 -1
  113. package/fs/js/tokenizer/proof.f.js +7 -0
  114. package/fs/json/module.f.d.ts +9 -2
  115. package/fs/json/module.f.js +9 -7
  116. package/fs/json/parser/module.f.js +4 -6
  117. package/fs/json/parser/proof.f.js +19 -10
  118. package/fs/json/proof.f.d.ts +2 -1
  119. package/fs/json/proof.f.js +33 -6
  120. package/fs/json/rpc/module.f.d.ts +5 -0
  121. package/fs/json/rpc/module.f.js +16 -11
  122. package/fs/json/schema/module.f.d.ts +15 -14
  123. package/fs/json/schema/proof.f.d.ts +3 -0
  124. package/fs/json/schema/proof.f.js +16 -1
  125. package/fs/mcp/module.f.d.ts +103 -5
  126. package/fs/mcp/module.f.js +139 -2
  127. package/fs/mcp/proof.f.d.ts +34 -0
  128. package/fs/mcp/proof.f.js +207 -0
  129. package/fs/mcp/stdio/module.f.d.ts +39 -0
  130. package/fs/mcp/stdio/module.f.js +54 -0
  131. package/fs/mcp/stdio/proof.f.d.ts +10 -0
  132. package/fs/mcp/stdio/proof.f.js +92 -0
  133. package/fs/mime/module.f.d.ts +39 -0
  134. package/fs/mime/module.f.js +78 -0
  135. package/fs/mime/proof.f.d.ts +15 -0
  136. package/fs/mime/proof.f.js +30 -0
  137. package/fs/path/module.f.d.ts +8 -0
  138. package/fs/path/module.f.js +10 -3
  139. package/fs/path/proof.f.d.ts +1 -0
  140. package/fs/path/proof.f.js +40 -2
  141. package/fs/text/sgr/proof.f.js +30 -2
  142. package/fs/text/utf8/module.f.d.ts +7 -0
  143. package/fs/text/utf8/module.f.js +85 -31
  144. package/fs/text/utf8/proof.f.d.ts +1 -0
  145. package/fs/text/utf8/proof.f.js +60 -1
  146. package/fs/types/bigfloat/module.f.d.ts +3 -0
  147. package/fs/types/bigfloat/module.f.js +30 -31
  148. package/fs/types/bigint/module.f.d.ts +24 -2
  149. package/fs/types/bigint/module.f.js +30 -2
  150. package/fs/types/bigint/proof.f.d.ts +7 -0
  151. package/fs/types/bigint/proof.f.js +47 -16
  152. package/fs/types/bit_vec/module.f.d.ts +18 -0
  153. package/fs/types/bit_vec/module.f.js +18 -0
  154. package/fs/types/bit_vec/proof.f.d.ts +1 -0
  155. package/fs/types/bit_vec/proof.f.js +13 -1
  156. package/fs/types/function/operator/proof.f.d.ts +2 -0
  157. package/fs/types/function/operator/proof.f.js +20 -2
  158. package/fs/types/list/module.f.js +1 -4
  159. package/fs/types/list/proof.f.d.ts +1 -1
  160. package/fs/types/list/proof.f.js +14 -6
  161. package/fs/types/map/proof.f.d.ts +1 -0
  162. package/fs/types/map/proof.f.js +10 -1
  163. package/fs/types/nibble_set/module.f.d.ts +13 -0
  164. package/fs/types/nibble_set/module.f.js +16 -0
  165. package/fs/types/object/module.f.d.ts +12 -8
  166. package/fs/types/object/module.f.js +9 -2
  167. package/fs/types/object/proof.f.d.ts +14 -0
  168. package/fs/types/object/proof.f.js +4 -0
  169. package/fs/types/ordered_map/proof.f.d.ts +1 -0
  170. package/fs/types/ordered_map/proof.f.js +39 -79
  171. package/fs/types/phantom/module.f.d.ts +22 -0
  172. package/fs/types/phantom/module.f.js +11 -0
  173. package/fs/types/prime_field/proof.f.d.ts +7 -0
  174. package/fs/types/prime_field/proof.f.js +63 -122
  175. package/fs/types/rtti/common/module.f.d.ts +4 -4
  176. package/fs/types/rtti/common/module.f.js +1 -1
  177. package/fs/types/rtti/module.f.d.ts +2 -3
  178. package/fs/types/rtti/parse/module.f.js +0 -1
  179. package/fs/types/rtti/ts/module.f.d.ts +26 -32
  180. package/fs/types/rtti/ts/module.f.js +2 -2
  181. package/fs/types/rtti/ts/proof.f.js +3 -3
  182. package/fs/types/rtti/validate/module.f.js +0 -1
  183. package/fs/types/sorted_list/module.f.d.ts +6 -0
  184. package/fs/types/sorted_list/module.f.js +11 -0
  185. package/fs/types/sorted_set/module.f.js +2 -8
  186. package/fs/types/ts/module.f.d.ts +1 -1
  187. package/fs/types/ts/module.f.js +3 -1
  188. package/fs/types/ts/proof.f.js +3 -2
  189. package/fs/website/proof.f.d.ts +1 -0
  190. package/fs/website/proof.f.js +8 -1
  191. package/package.json +3 -3
@@ -7,32 +7,33 @@
7
7
  * @module
8
8
  */
9
9
  import { type Type as RttiType } from '../../types/rtti/module.f.ts';
10
- import type { Ts, WithOut } from '../../types/rtti/ts/module.f.ts';
10
+ import type { Ts } from '../../types/rtti/ts/module.f.ts';
11
+ import type { Phantom } from '../../types/phantom/module.f.ts';
11
12
  declare const unknownThunk: () => readonly ["const", {
12
13
  readonly type: import("../../types/rtti/module.f.ts").Or<["boolean", "number", "string", "integer", "array", "object", undefined]>;
13
14
  readonly const: import("../../types/rtti/module.f.ts").Or<readonly [() => readonly ["or", import("../../types/rtti/module.f.ts").Or<[null, import("../../types/rtti/module.f.ts").Boolean, import("../../types/rtti/module.f.ts").Number, import("../../types/rtti/module.f.ts").String]>, import("../../types/rtti/module.f.ts").Type1<"record", /*elided*/ any>, import("../../types/rtti/module.f.ts").Type1<"array", /*elided*/ any>], undefined]>;
14
- readonly not: import("../../types/rtti/module.f.ts").Or<readonly [WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>, undefined]>;
15
- readonly anyOf: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
16
- readonly items: import("../../types/rtti/module.f.ts").Or<[WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>, false, undefined]>;
17
- readonly prefixItems: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
18
- readonly properties: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"record", WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
15
+ readonly not: import("../../types/rtti/module.f.ts").Or<readonly [Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>, undefined]>;
16
+ readonly anyOf: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
17
+ readonly items: import("../../types/rtti/module.f.ts").Or<[Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>, false, undefined]>;
18
+ readonly prefixItems: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
19
+ readonly properties: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"record", Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
19
20
  readonly required: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", import("../../types/rtti/module.f.ts").String>, undefined]>;
20
- readonly additionalProperties: import("../../types/rtti/module.f.ts").Or<readonly [WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>, undefined]>;
21
+ readonly additionalProperties: import("../../types/rtti/module.f.ts").Or<readonly [Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>, undefined]>;
21
22
  }];
22
23
  /** rtti schema for a JSON Schema (draft 2020-12) document. */
23
- export declare const unknown: WithOut<typeof unknownThunk, UnknownConst>;
24
+ export declare const unknown: Phantom<typeof unknownThunk, UnknownConst>;
24
25
  /** A JSON Schema (draft 2020-12) document — the subset of keywords that `toJsonSchema` emits. */
25
26
  export type Unknown = Ts<typeof unknown>;
26
27
  declare const unknownConst: {
27
28
  readonly type: import("../../types/rtti/module.f.ts").Or<["boolean", "number", "string", "integer", "array", "object", undefined]>;
28
29
  readonly const: import("../../types/rtti/module.f.ts").Or<readonly [() => readonly ["or", import("../../types/rtti/module.f.ts").Or<[null, import("../../types/rtti/module.f.ts").Boolean, import("../../types/rtti/module.f.ts").Number, import("../../types/rtti/module.f.ts").String]>, import("../../types/rtti/module.f.ts").Type1<"record", /*elided*/ any>, import("../../types/rtti/module.f.ts").Type1<"array", /*elided*/ any>], undefined]>;
29
- readonly not: import("../../types/rtti/module.f.ts").Or<readonly [WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>, undefined]>;
30
- readonly anyOf: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
31
- readonly items: import("../../types/rtti/module.f.ts").Or<[WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>, false, undefined]>;
32
- readonly prefixItems: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
33
- readonly properties: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"record", WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
30
+ readonly not: import("../../types/rtti/module.f.ts").Or<readonly [Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>, undefined]>;
31
+ readonly anyOf: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
32
+ readonly items: import("../../types/rtti/module.f.ts").Or<[Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>, false, undefined]>;
33
+ readonly prefixItems: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
34
+ readonly properties: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"record", Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>>, undefined]>;
34
35
  readonly required: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Type1<"array", import("../../types/rtti/module.f.ts").String>, undefined]>;
35
- readonly additionalProperties: import("../../types/rtti/module.f.ts").Or<readonly [WithOut<() => readonly ["const", /*elided*/ any], UnknownConst>, undefined]>;
36
+ readonly additionalProperties: import("../../types/rtti/module.f.ts").Or<readonly [Phantom<() => readonly ["const", /*elided*/ any], UnknownConst>, undefined]>;
36
37
  };
37
38
  /**
38
39
  * Hand-written base type used as the `$out` annotation on `unknown`.
@@ -24,7 +24,10 @@ export declare const proof: {
24
24
  withOptional: () => void;
25
25
  allOptional: () => void;
26
26
  empty: () => void;
27
+ orOptional: () => void;
28
+ withConst: () => void;
27
29
  };
30
+ schemaUnknownTag: () => void;
28
31
  nested: {
29
32
  arrayOfRecords: () => void;
30
33
  orWithConst: () => void;
@@ -1,6 +1,6 @@
1
1
  import { boolean, number, string, bigint, unknown, array, record, or, option } from "../../types/rtti/module.f.js";
2
2
  import { stringify } from "../module.f.js";
3
- import { toJsonSchema } from "./module.f.js";
3
+ import { toJsonSchema, unknown as schemaUnknown } from "./module.f.js";
4
4
  const serialize = (v) => stringify(e => e)(v);
5
5
  const eq = (rtti, expected) => () => {
6
6
  const result = serialize(toJsonSchema(rtti));
@@ -50,6 +50,21 @@ export const proof = {
50
50
  properties: { x: { type: 'number' } },
51
51
  }),
52
52
  empty: eq({}, { type: 'object', properties: {} }),
53
+ orOptional: eq({ x: or(string, number, undefined) }, {
54
+ type: 'object',
55
+ properties: { x: { anyOf: [{ type: 'string' }, { type: 'number' }] } },
56
+ }),
57
+ withConst: eq({ x: null, y: string }, {
58
+ type: 'object',
59
+ properties: { x: { const: null }, y: { type: 'string' } },
60
+ required: ['x', 'y'],
61
+ }),
62
+ },
63
+ schemaUnknownTag: () => {
64
+ const r = schemaUnknown();
65
+ if (r[0] !== 'const') {
66
+ throw r[0];
67
+ }
53
68
  },
54
69
  nested: {
55
70
  arrayOfRecords: eq(array(record(boolean)), {
@@ -1,7 +1,8 @@
1
1
  import { type Unknown } from '../json/module.f.ts';
2
2
  import type { Ts } from '../types/rtti/ts/module.f.ts';
3
- import type { Operation, Effect } from '../effects/module.f.ts';
4
- import type { Response } from '../json/rpc/module.f.ts';
3
+ import { type Operation, type Effect } from '../effects/module.f.ts';
4
+ import { type Key, type MemOp } from '../effects/memory/module.f.ts';
5
+ import { type Response } from '../json/rpc/module.f.ts';
5
6
  /** Name + version pair sent in `initialize` requests and responses. */
6
7
  export declare const implementation: {
7
8
  readonly name: import("../types/rtti/module.f.ts").String;
@@ -46,6 +47,45 @@ export declare const textContent: {
46
47
  readonly text: import("../types/rtti/module.f.ts").String;
47
48
  };
48
49
  export type TextContent = Ts<typeof textContent>;
50
+ /**
51
+ * A binary resource carried inside an {@link embeddedResource}: a base64
52
+ * `blob`, an addressing `uri`, and an optional `mimeType`. This is MCP's
53
+ * `BlobResource` shape — the idiomatic way to return typed binary content so a
54
+ * `mimeType` travels alongside the bytes and clients know how to route them.
55
+ */
56
+ export declare const blobResource: {
57
+ readonly uri: import("../types/rtti/module.f.ts").String;
58
+ readonly mimeType: import("../types/rtti/module.f.ts").Or<readonly [import("../types/rtti/module.f.ts").String, undefined]>;
59
+ readonly blob: import("../types/rtti/module.f.ts").String;
60
+ };
61
+ export type BlobResource = Ts<typeof blobResource>;
62
+ /** An `EmbeddedResource` content item wrapping a {@link blobResource}. */
63
+ export declare const embeddedResource: {
64
+ readonly type: "resource";
65
+ readonly resource: {
66
+ readonly uri: import("../types/rtti/module.f.ts").String;
67
+ readonly mimeType: import("../types/rtti/module.f.ts").Or<readonly [import("../types/rtti/module.f.ts").String, undefined]>;
68
+ readonly blob: import("../types/rtti/module.f.ts").String;
69
+ };
70
+ };
71
+ export type EmbeddedResource = Ts<typeof embeddedResource>;
72
+ /**
73
+ * A single item in a `tools/call` result's `content` array: either plain
74
+ * {@link textContent} or an {@link embeddedResource} for typed binary. The
75
+ * `image` and `audio` variants are not modelled yet.
76
+ */
77
+ export declare const contentItem: import("../types/rtti/module.f.ts").Or<[{
78
+ readonly type: "text";
79
+ readonly text: import("../types/rtti/module.f.ts").String;
80
+ }, {
81
+ readonly type: "resource";
82
+ readonly resource: {
83
+ readonly uri: import("../types/rtti/module.f.ts").String;
84
+ readonly mimeType: import("../types/rtti/module.f.ts").Or<readonly [import("../types/rtti/module.f.ts").String, undefined]>;
85
+ readonly blob: import("../types/rtti/module.f.ts").String;
86
+ };
87
+ }]>;
88
+ export type ContentItem = Ts<typeof contentItem>;
49
89
  /**
50
90
  * A tool descriptor returned by `tools/list`.
51
91
  * `inputSchema` is a JSON Schema object — use `toJsonSchema` to derive it from
@@ -57,6 +97,14 @@ export declare const tool: {
57
97
  readonly inputSchema: () => readonly ["or", import("../types/rtti/module.f.ts").Or<[null, import("../types/rtti/module.f.ts").Boolean, import("../types/rtti/module.f.ts").Number, import("../types/rtti/module.f.ts").String]>, import("../types/rtti/module.f.ts").Type1<"record", /*elided*/ any>, import("../types/rtti/module.f.ts").Type1<"array", /*elided*/ any>];
58
98
  };
59
99
  export type Tool = Ts<typeof tool>;
100
+ /**
101
+ * Params for the `tools/list` request. `cursor` is an opaque pagination token
102
+ * from a previous `ToolsListResult.nextCursor`.
103
+ */
104
+ export declare const toolsListParams: {
105
+ readonly cursor: import("../types/rtti/module.f.ts").Or<readonly [import("../types/rtti/module.f.ts").String, undefined]>;
106
+ };
107
+ export type ToolsListParams = Ts<typeof toolsListParams>;
60
108
  export declare const toolsListResult: {
61
109
  readonly tools: import("../types/rtti/module.f.ts").Type1<"array", {
62
110
  readonly name: import("../types/rtti/module.f.ts").String;
@@ -72,17 +120,67 @@ export declare const toolsCallParams: {
72
120
  };
73
121
  export type ToolsCallParams = Ts<typeof toolsCallParams>;
74
122
  export declare const toolsCallResult: {
75
- readonly content: import("../types/rtti/module.f.ts").Type1<"array", {
123
+ readonly content: import("../types/rtti/module.f.ts").Type1<"array", import("../types/rtti/module.f.ts").Or<[{
76
124
  readonly type: "text";
77
125
  readonly text: import("../types/rtti/module.f.ts").String;
78
- }>;
126
+ }, {
127
+ readonly type: "resource";
128
+ readonly resource: {
129
+ readonly uri: import("../types/rtti/module.f.ts").String;
130
+ readonly mimeType: import("../types/rtti/module.f.ts").Or<readonly [import("../types/rtti/module.f.ts").String, undefined]>;
131
+ readonly blob: import("../types/rtti/module.f.ts").String;
132
+ };
133
+ }]>>;
79
134
  readonly isError: import("../types/rtti/module.f.ts").Or<readonly [import("../types/rtti/module.f.ts").Boolean, undefined]>;
80
135
  };
81
136
  export type ToolsCallResult = Ts<typeof toolsCallResult>;
82
137
  /** Per-method handlers for a hello-world MCP tool server. */
83
138
  export type McpHandlers<O extends Operation> = {
84
- readonly toolsList: () => Effect<O, ToolsListResult>;
139
+ readonly toolsList: (params: ToolsListParams) => Effect<O, ToolsListResult>;
85
140
  readonly toolsCall: (params: ToolsCallParams) => Effect<O, ToolsCallResult>;
86
141
  };
87
142
  /** Top-level handler: maps a raw JSON value to a JSON-RPC response (or `null` for notifications). */
88
143
  export type Handle<O extends Operation> = (value: Unknown) => Effect<O, Response | null>;
144
+ /** MCP error -32002: the client called a method before `initialize`. */
145
+ export declare const notInitialized: import("../types/rtti/ts/module.f.ts").StructTs<{
146
+ readonly code: import("../types/rtti/module.f.ts").Number;
147
+ readonly message: import("../types/rtti/module.f.ts").String;
148
+ readonly data: import("../types/rtti/module.f.ts").Or<readonly [() => readonly ["or", import("../types/rtti/module.f.ts").Or<[null, import("../types/rtti/module.f.ts").Boolean, import("../types/rtti/module.f.ts").Number, import("../types/rtti/module.f.ts").String]>, import("../types/rtti/module.f.ts").Type1<"record", /*elided*/ any>, import("../types/rtti/module.f.ts").Type1<"array", /*elided*/ any>], undefined]>;
149
+ }>;
150
+ /** State carried before the peer sends `initialize`. */
151
+ export type Uninitialized = readonly ['uninitialized'];
152
+ /** State after `initialize` response was sent but before `notifications/initialized` arrives. */
153
+ export type Initializing = readonly ['initializing'];
154
+ /** State carried after a successful `initialize` exchange. */
155
+ export type InitializedState = true;
156
+ /** The three phases of an MCP session. */
157
+ export type McpSessionState = Uninitialized | Initializing | readonly ['initialized', InitializedState];
158
+ /** Initial session state — always start here. */
159
+ export declare const uninitializedState: McpSessionState;
160
+ /** Static configuration supplied by the server implementer. */
161
+ export type McpConfig = {
162
+ readonly serverInfo: Implementation;
163
+ readonly capabilities: ServerCapabilities;
164
+ readonly protocolVersion: string;
165
+ };
166
+ /**
167
+ * State-machine step for an MCP session using memory effects.
168
+ *
169
+ * Given configuration, handlers, and a memory key holding the session state,
170
+ * returns a function `(value) => Effect<MemOp | O, Response | null>`.
171
+ *
172
+ * Rules:
173
+ * - `ping` returns an empty success regardless of session state; non-object
174
+ * params → -32602.
175
+ * - `initialize` is accepted only while uninitialized; a second call returns -32600.
176
+ * On success the state moves to `initializing`, not `initialized`.
177
+ * - `notifications/initialized` (no `id`) transitions `initializing` → `initialized`;
178
+ * a malformed one (non-object params) is ignored and the session stays gated;
179
+ * other notifications are silently ignored in any state.
180
+ * - Any other method before `notifications/initialized` → error -32002 (not initialized).
181
+ * - Methods gated by a capability (e.g. `tools/list`) → -32601 when the capability
182
+ * is absent.
183
+ * - `tools/list` params (an optional pagination `cursor`) are validated and passed
184
+ * to the handler; invalid params → -32602.
185
+ */
186
+ export declare const mcpStep: <O extends Operation>({ protocolVersion, capabilities, serverInfo, }: McpConfig) => (handlers: McpHandlers<O>) => (stateKey: Key<McpSessionState>) => (value: Unknown) => Effect<MemOp | O, Response | null>;
@@ -13,8 +13,12 @@
13
13
  *
14
14
  * @module
15
15
  */
16
- import { boolean, string, option, array, record } from "../types/rtti/module.f.js";
16
+ import { boolean, string, option, array, record, or } from "../types/rtti/module.f.js";
17
17
  import { unknown } from "../json/module.f.js";
18
+ import { pure } from "../effects/module.f.js";
19
+ import { read, write } from "../effects/memory/module.f.js";
20
+ import { decodeRequest, rpcError, invalidRequest, invalidParams, methodNotFound, jsonrpc, } from "../json/rpc/module.f.js";
21
+ import { validate } from "../types/rtti/validate/module.f.js";
18
22
  // ── Shared ─────────────────────────────────────────────────────────────────────
19
23
  /** Name + version pair sent in `initialize` requests and responses. */
20
24
  export const implementation = {
@@ -44,6 +48,28 @@ export const initializeResult = {
44
48
  // ── Content ────────────────────────────────────────────────────────────────────
45
49
  /** Plain-text content item returned by a tool call. */
46
50
  export const textContent = { type: 'text', text: string };
51
+ /**
52
+ * A binary resource carried inside an {@link embeddedResource}: a base64
53
+ * `blob`, an addressing `uri`, and an optional `mimeType`. This is MCP's
54
+ * `BlobResource` shape — the idiomatic way to return typed binary content so a
55
+ * `mimeType` travels alongside the bytes and clients know how to route them.
56
+ */
57
+ export const blobResource = {
58
+ uri: string,
59
+ mimeType: option(string),
60
+ blob: string,
61
+ };
62
+ /** An `EmbeddedResource` content item wrapping a {@link blobResource}. */
63
+ export const embeddedResource = {
64
+ type: 'resource',
65
+ resource: blobResource,
66
+ };
67
+ /**
68
+ * A single item in a `tools/call` result's `content` array: either plain
69
+ * {@link textContent} or an {@link embeddedResource} for typed binary. The
70
+ * `image` and `audio` variants are not modelled yet.
71
+ */
72
+ export const contentItem = or(textContent, embeddedResource);
47
73
  // ── Tools ──────────────────────────────────────────────────────────────────────
48
74
  /**
49
75
  * A tool descriptor returned by `tools/list`.
@@ -55,6 +81,13 @@ export const tool = {
55
81
  description: option(string),
56
82
  inputSchema: unknown,
57
83
  };
84
+ /**
85
+ * Params for the `tools/list` request. `cursor` is an opaque pagination token
86
+ * from a previous `ToolsListResult.nextCursor`.
87
+ */
88
+ export const toolsListParams = {
89
+ cursor: option(string),
90
+ };
58
91
  export const toolsListResult = {
59
92
  tools: array(tool),
60
93
  nextCursor: option(string),
@@ -64,6 +97,110 @@ export const toolsCallParams = {
64
97
  arguments: option(record(unknown)),
65
98
  };
66
99
  export const toolsCallResult = {
67
- content: array(textContent),
100
+ content: array(contentItem),
68
101
  isError: option(boolean),
69
102
  };
103
+ // ── Lifecycle / capability state machine ───────────────────────────────────────
104
+ const _errResponse = (id) => (error) => ({ jsonrpc, error, id });
105
+ const _okResponse = (id) => (result) => ({ jsonrpc, result, id });
106
+ /** MCP error -32002: the client called a method before `initialize`. */
107
+ export const notInitialized = rpcError(-32002)('Server not initialized');
108
+ // Params for methods that take no arguments (`ping`, `notifications/initialized`):
109
+ // absent, or an object (which may carry `_meta`).
110
+ const _noParams = option(record(unknown));
111
+ /** Initial session state — always start here. */
112
+ export const uninitializedState = ['uninitialized'];
113
+ /**
114
+ * State-machine step for an MCP session using memory effects.
115
+ *
116
+ * Given configuration, handlers, and a memory key holding the session state,
117
+ * returns a function `(value) => Effect<MemOp | O, Response | null>`.
118
+ *
119
+ * Rules:
120
+ * - `ping` returns an empty success regardless of session state; non-object
121
+ * params → -32602.
122
+ * - `initialize` is accepted only while uninitialized; a second call returns -32600.
123
+ * On success the state moves to `initializing`, not `initialized`.
124
+ * - `notifications/initialized` (no `id`) transitions `initializing` → `initialized`;
125
+ * a malformed one (non-object params) is ignored and the session stays gated;
126
+ * other notifications are silently ignored in any state.
127
+ * - Any other method before `notifications/initialized` → error -32002 (not initialized).
128
+ * - Methods gated by a capability (e.g. `tools/list`) → -32601 when the capability
129
+ * is absent.
130
+ * - `tools/list` params (an optional pagination `cursor`) are validated and passed
131
+ * to the handler; invalid params → -32602.
132
+ */
133
+ export const mcpStep = ({ protocolVersion, capabilities, serverInfo, }) => (handlers) => (stateKey) => (value) => {
134
+ const [t, message] = decodeRequest(value);
135
+ if (t === 'error') {
136
+ return pure(_errResponse(null)(invalidRequest));
137
+ }
138
+ const { id, method, params } = message;
139
+ // Notifications (no `id`) never receive a response.
140
+ // `notifications/initialized` transitions the session from initializing → initialized.
141
+ if (id === undefined) {
142
+ if (method === 'notifications/initialized') {
143
+ const [pt] = validate(_noParams)(params);
144
+ if (pt === 'error') {
145
+ // Malformed handshake — ignore it; the session stays gated.
146
+ return pure(null);
147
+ }
148
+ return read(stateKey).step(([t]) => t === 'initializing'
149
+ ? write(stateKey, ['initialized', true]).step(() => pure(null))
150
+ : pure(null));
151
+ }
152
+ return pure(null);
153
+ }
154
+ // `ping` is always valid regardless of session state, but its params
155
+ // (if present) must be an object.
156
+ if (method === 'ping') {
157
+ const [pt] = validate(_noParams)(params);
158
+ return pt === 'error'
159
+ ? pure(_errResponse(id)(invalidParams))
160
+ : pure(_okResponse(id)({}));
161
+ }
162
+ // `initialize` transitions uninitialized → initializing; reject if already done.
163
+ if (method === 'initialize') {
164
+ return read(stateKey).step(([t]) => {
165
+ if (t !== 'uninitialized') {
166
+ return pure(_errResponse(id)(invalidRequest));
167
+ }
168
+ const [pr] = validate(initializeParams)(params);
169
+ if (pr === 'error') {
170
+ return pure(_errResponse(id)(invalidParams));
171
+ }
172
+ const result = {
173
+ protocolVersion,
174
+ capabilities,
175
+ serverInfo,
176
+ };
177
+ return write(stateKey, ['initializing']).step(() => pure(_okResponse(id)(result)));
178
+ });
179
+ }
180
+ // All other methods require fully initialized state — read it first.
181
+ return read(stateKey).step(([t]) => {
182
+ if (t !== 'initialized') {
183
+ return pure(_errResponse(id)(notInitialized));
184
+ }
185
+ if (method === 'tools/list') {
186
+ if (capabilities.tools === undefined) {
187
+ return pure(_errResponse(id)(methodNotFound));
188
+ }
189
+ // `params` may be absent — `tools/list` without a cursor.
190
+ const [t, pr] = validate(toolsListParams)(params === undefined ? {} : params);
191
+ return t === 'error'
192
+ ? pure(_errResponse(id)(invalidParams))
193
+ : handlers.toolsList(pr).step(r => pure(_okResponse(id)(r)));
194
+ }
195
+ if (method === 'tools/call') {
196
+ if (capabilities.tools === undefined) {
197
+ return pure(_errResponse(id)(methodNotFound));
198
+ }
199
+ const [t, pr] = validate(toolsCallParams)(params);
200
+ return t === 'error'
201
+ ? pure(_errResponse(id)(invalidParams))
202
+ : handlers.toolsCall(pr).step(r => pure(_okResponse(id)(r)));
203
+ }
204
+ return pure(_errResponse(id)(methodNotFound));
205
+ });
206
+ };
@@ -0,0 +1,34 @@
1
+ export declare const proof: {
2
+ lifecycle: {
3
+ initialStateIsUninitialized: () => void;
4
+ initializeTransitionsToInitializing: () => void;
5
+ initializedNotificationTransitionsToInitialized: () => void;
6
+ initializeReturnsResult: () => void;
7
+ initializeWithBadParamsReturnsInvalidParams: () => void;
8
+ notificationBeforeInitReturnNull: () => void;
9
+ unknownNotificationReturnNull: () => void;
10
+ doubleInitializeReturnsInvalidRequest: () => void;
11
+ pingBeforeInitSucceeds: () => void;
12
+ pingDuringInitializingSucceeds: () => void;
13
+ pingAfterInitSucceeds: () => void;
14
+ pingWithObjectParamsSucceeds: () => void;
15
+ pingInvalidParamsReturnsInvalidParams: () => void;
16
+ initializedNotificationObjectParamsTransitions: () => void;
17
+ initializedNotificationBadParamsIgnored: () => void;
18
+ methodBeforeInitReturnsNotInitialized: () => void;
19
+ methodDuringInitializingReturnsNotInitialized: () => void;
20
+ invalidEnvelopeReturnsInvalidRequest: () => void;
21
+ };
22
+ tools: {
23
+ toolsListSucceeds: () => void;
24
+ toolsListPassesCursorToHandler: () => void;
25
+ toolsListInvalidCursorReturnsInvalidParams: () => void;
26
+ toolsCallSucceeds: () => void;
27
+ toolsCallBadParamsReturnsInvalidParams: () => void;
28
+ toolsCallAbsentArgumentsSucceeds: () => void;
29
+ toolsCallNullArgumentsReturnsInvalidParams: () => void;
30
+ toolsListWithoutCapabilityReturnsMethodNotFound: () => void;
31
+ toolsCallWithoutCapabilityReturnsMethodNotFound: () => void;
32
+ unknownMethodReturnsMethodNotFound: () => void;
33
+ };
34
+ };
@@ -0,0 +1,207 @@
1
+ import { assert, assertEq } from "../asserts/module.f.js";
2
+ import { pure } from "../effects/module.f.js";
3
+ import { run } from "../effects/mock/module.f.js";
4
+ import { asBase, asNominal, create, read } from "../effects/memory/module.f.js";
5
+ import { uninitializedState, mcpStep, notInitialized, } from "./module.f.js";
6
+ const initial = { next: 0, values: {} };
7
+ const mock = {
8
+ memCreate: value => state => {
9
+ const id = `k${state.next}`;
10
+ const key = asNominal(id);
11
+ return [{ next: state.next + 1, values: { ...state.values, [id]: value } }, key];
12
+ },
13
+ memRead: key => state => [state, state.values[asBase(key)]],
14
+ memWrite: (key, value) => state => {
15
+ const id = asBase(key);
16
+ return [{ ...state, values: { ...state.values, [id]: value } }, undefined];
17
+ },
18
+ };
19
+ // ── Fixtures ──────────────────────────────────────────────────────────────────
20
+ const config = {
21
+ serverInfo: { name: 'test-server', version: '0.1.0' },
22
+ capabilities: { tools: {} },
23
+ protocolVersion: '2024-11-05',
24
+ };
25
+ const configNoTools = { ...config, capabilities: {} };
26
+ const handlers = {
27
+ // Echoes a received cursor as `nextCursor` so tests can observe pagination params.
28
+ toolsList: (p) => pure(p.cursor === undefined
29
+ ? { tools: [{ name: 'greet', inputSchema: {} }] }
30
+ : { tools: [], nextCursor: p.cursor }),
31
+ toolsCall: (_p) => pure({ content: [{ type: 'text', text: 'hello' }] }),
32
+ };
33
+ // Run a memory effect against the mock, return the result.
34
+ const runMem = (effect) => run(mock)(initial)(effect)[1];
35
+ // TypeScript infers O = Operation (the upper bound) rather than O = never when
36
+ // O flows through McpHandlers<never>, so we cast the widened type down to MemOp.
37
+ const asMemEffect = (e) => e;
38
+ // Run one step from uninitializedState, return [response, newState].
39
+ const step1 = (cfg) => (msg) => runMem(asMemEffect(create(uninitializedState).step(key => mcpStep(cfg)(handlers)(key)(msg).step(resp => read(key).step(state => pure([resp, state]))))));
40
+ // Run initialize then a second step, return [response, newState] of the second.
41
+ const step2 = (cfg) => (msg1) => (msg2) => runMem(asMemEffect(create(uninitializedState).step(key => mcpStep(cfg)(handlers)(key)(msg1).step(() => mcpStep(cfg)(handlers)(key)(msg2).step(resp => read(key).step(state => pure([resp, state])))))));
42
+ // Run initialize, notifications/initialized, then a third step; return [response, newState] of the third.
43
+ const step3 = (cfg) => (msg1) => (msg2) => (msg3) => runMem(asMemEffect(create(uninitializedState).step(key => mcpStep(cfg)(handlers)(key)(msg1).step(() => mcpStep(cfg)(handlers)(key)(msg2).step(() => mcpStep(cfg)(handlers)(key)(msg3).step(resp => read(key).step(state => pure([resp, state]))))))));
44
+ // ── Test messages ─────────────────────────────────────────────────────────────
45
+ const initMsg = { jsonrpc: '2.0', method: 'initialize', id: 1,
46
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'client', version: '0.0.1' } } };
47
+ const initNotif = { jsonrpc: '2.0', method: 'notifications/initialized' };
48
+ // ── Tests ─────────────────────────────────────────────────────────────────────
49
+ export const proof = {
50
+ lifecycle: {
51
+ initialStateIsUninitialized: () => {
52
+ assertEq(uninitializedState[0], 'uninitialized');
53
+ },
54
+ initializeTransitionsToInitializing: () => {
55
+ const [, newState] = step1(config)(initMsg);
56
+ assertEq(newState[0], 'initializing');
57
+ },
58
+ initializedNotificationTransitionsToInitialized: () => {
59
+ const [resp, newState] = step2(config)(initMsg)(initNotif);
60
+ assertEq(resp, null);
61
+ assertEq(newState[0], 'initialized');
62
+ },
63
+ initializeReturnsResult: () => {
64
+ const [resp] = step1(config)(initMsg);
65
+ assert(resp !== null && typeof resp === 'object' && 'result' in resp);
66
+ const r = resp.result;
67
+ assertEq(r.protocolVersion, '2024-11-05');
68
+ },
69
+ initializeWithBadParamsReturnsInvalidParams: () => {
70
+ const bad = { jsonrpc: '2.0', method: 'initialize', id: 2, params: { wrong: true } };
71
+ const [resp, newState] = step1(config)(bad);
72
+ assertEq(newState[0], 'uninitialized');
73
+ assertEq(resp.error.code, -32602);
74
+ },
75
+ notificationBeforeInitReturnNull: () => {
76
+ const [resp, newState] = step1(config)(initNotif);
77
+ assertEq(newState[0], 'uninitialized');
78
+ assertEq(resp, null);
79
+ },
80
+ unknownNotificationReturnNull: () => {
81
+ const notif = { jsonrpc: '2.0', method: 'notifications/unknown' };
82
+ const [resp] = step2(config)(initMsg)(notif);
83
+ assertEq(resp, null);
84
+ },
85
+ doubleInitializeReturnsInvalidRequest: () => {
86
+ const [resp, newState] = step2(config)(initMsg)(initMsg);
87
+ assertEq(newState[0], 'initializing');
88
+ assertEq(resp.error.code, -32600);
89
+ },
90
+ pingBeforeInitSucceeds: () => {
91
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 11 };
92
+ const [resp, newState] = step1(config)(msg);
93
+ assertEq(newState[0], 'uninitialized');
94
+ assert(!('error' in resp));
95
+ },
96
+ pingDuringInitializingSucceeds: () => {
97
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 12 };
98
+ const [resp, newState] = step2(config)(initMsg)(msg);
99
+ assertEq(newState[0], 'initializing');
100
+ assert(!('error' in resp));
101
+ },
102
+ pingAfterInitSucceeds: () => {
103
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 15 };
104
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
105
+ assert(!('error' in resp));
106
+ },
107
+ pingWithObjectParamsSucceeds: () => {
108
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 19, params: {} };
109
+ const [resp] = step1(config)(msg);
110
+ assert(!('error' in resp));
111
+ },
112
+ pingInvalidParamsReturnsInvalidParams: () => {
113
+ const msg = { jsonrpc: '2.0', method: 'ping', id: 20, params: 1 };
114
+ const [resp] = step1(config)(msg);
115
+ assertEq(resp.error.code, -32602);
116
+ },
117
+ initializedNotificationObjectParamsTransitions: () => {
118
+ const notif = { jsonrpc: '2.0', method: 'notifications/initialized', params: {} };
119
+ const [resp, newState] = step2(config)(initMsg)(notif);
120
+ assertEq(resp, null);
121
+ assertEq(newState[0], 'initialized');
122
+ },
123
+ initializedNotificationBadParamsIgnored: () => {
124
+ const notif = { jsonrpc: '2.0', method: 'notifications/initialized', params: 1 };
125
+ const [resp, newState] = step2(config)(initMsg)(notif);
126
+ assertEq(resp, null);
127
+ assertEq(newState[0], 'initializing');
128
+ },
129
+ methodBeforeInitReturnsNotInitialized: () => {
130
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 3 };
131
+ const [resp, newState] = step1(config)(msg);
132
+ assertEq(newState[0], 'uninitialized');
133
+ assertEq(resp.error.code, notInitialized.code);
134
+ },
135
+ methodDuringInitializingReturnsNotInitialized: () => {
136
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 16 };
137
+ const [resp, newState] = step2(config)(initMsg)(msg);
138
+ assertEq(newState[0], 'initializing');
139
+ assertEq(resp.error.code, notInitialized.code);
140
+ },
141
+ invalidEnvelopeReturnsInvalidRequest: () => {
142
+ const bad = { jsonrpc: '1.0', method: 'ping', id: 4 };
143
+ const [resp] = step1(config)(bad);
144
+ assertEq(resp.error.code, -32600);
145
+ assertEq(resp.id, null);
146
+ },
147
+ },
148
+ tools: {
149
+ toolsListSucceeds: () => {
150
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 5 };
151
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
152
+ assertEq(resp.result.tools.length, 1);
153
+ assertEq(resp.result.tools[0].name, 'greet');
154
+ },
155
+ toolsListPassesCursorToHandler: () => {
156
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 17,
157
+ params: { cursor: 'page-2' } };
158
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
159
+ assertEq(resp.result.nextCursor, 'page-2');
160
+ },
161
+ toolsListInvalidCursorReturnsInvalidParams: () => {
162
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 18,
163
+ params: { cursor: 42 } };
164
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
165
+ assertEq(resp.error.code, -32602);
166
+ },
167
+ toolsCallSucceeds: () => {
168
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 6,
169
+ params: { name: 'greet', arguments: {} } };
170
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
171
+ assertEq(resp.result.content[0].text, 'hello');
172
+ },
173
+ toolsCallBadParamsReturnsInvalidParams: () => {
174
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 7, params: { missing: true } };
175
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
176
+ assertEq(resp.error.code, -32602);
177
+ },
178
+ toolsCallAbsentArgumentsSucceeds: () => {
179
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 13,
180
+ params: { name: 'greet' } };
181
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
182
+ assertEq(resp.result.content[0].text, 'hello');
183
+ },
184
+ toolsCallNullArgumentsReturnsInvalidParams: () => {
185
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 14,
186
+ params: { name: 'greet', arguments: null } };
187
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
188
+ assertEq(resp.error.code, -32602);
189
+ },
190
+ toolsListWithoutCapabilityReturnsMethodNotFound: () => {
191
+ const msg = { jsonrpc: '2.0', method: 'tools/list', id: 8 };
192
+ const [resp] = step3(configNoTools)(initMsg)(initNotif)(msg);
193
+ assertEq(resp.error.code, -32601);
194
+ },
195
+ toolsCallWithoutCapabilityReturnsMethodNotFound: () => {
196
+ const msg = { jsonrpc: '2.0', method: 'tools/call', id: 9,
197
+ params: { name: 'greet', arguments: {} } };
198
+ const [resp] = step3(configNoTools)(initMsg)(initNotif)(msg);
199
+ assertEq(resp.error.code, -32601);
200
+ },
201
+ unknownMethodReturnsMethodNotFound: () => {
202
+ const msg = { jsonrpc: '2.0', method: 'resources/list', id: 10 };
203
+ const [resp] = step3(config)(initMsg)(initNotif)(msg);
204
+ assertEq(resp.error.code, -32601);
205
+ },
206
+ },
207
+ };