functionalscript 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/fjs/asn.1/module.f.mjs +8 -1
- package/fjs/asn.1/proof.f.d.mts +10 -0
- package/fjs/asn.1/proof.f.mjs +16 -0
- package/fjs/basen/base128/module.f.mjs +22 -5
- package/fjs/bnf/data/module.f.d.mts +32 -6
- package/fjs/bnf/data/module.f.mjs +112 -7
- package/fjs/bnf/data/proof.f.d.mts +2 -0
- package/fjs/bnf/data/proof.f.mjs +74 -3
- package/fjs/bnf/data/types.d.ts +20 -2
- package/fjs/bnf/descent/module.f.d.mts +28 -14
- package/fjs/bnf/descent/module.f.mjs +122 -77
- package/fjs/bnf/descent/proof.f.d.mts +2 -0
- package/fjs/bnf/descent/proof.f.mjs +117 -31
- package/fjs/bnf/descent/types.d.ts +12 -14
- package/fjs/bnf/ll1/module.f.d.mts +64 -22
- package/fjs/bnf/ll1/module.f.mjs +214 -154
- package/fjs/bnf/ll1/proof.f.d.mts +15 -2
- package/fjs/bnf/ll1/proof.f.mjs +323 -149
- package/fjs/bnf/ll1/types.d.ts +16 -24
- package/fjs/bnf/matcher/module.f.d.mts +66 -0
- package/fjs/bnf/matcher/module.f.mjs +81 -0
- package/fjs/bnf/matcher/proof.f.d.mts +10 -0
- package/fjs/bnf/matcher/proof.f.mjs +79 -0
- package/fjs/bnf/matcher/types.d.ts +54 -0
- package/fjs/bnf/testlib.f.d.mts +31 -0
- package/fjs/bnf/testlib.f.mjs +80 -0
- package/fjs/cas/cli/module.f.d.mts +1 -1
- package/fjs/cas/cli/module.f.mjs +14 -20
- package/fjs/cas/cli/proof.f.d.mts +1 -3
- package/fjs/cas/cli/proof.f.mjs +44 -33
- package/fjs/cas/evo/module.f.d.mts +64 -18
- package/fjs/cas/evo/module.f.mjs +148 -70
- package/fjs/cas/evo/proof.f.d.mts +10 -1
- package/fjs/cas/evo/proof.f.mjs +305 -223
- package/fjs/cas/evo/types.d.ts +45 -9
- package/fjs/cas/module.f.d.mts +18 -32
- package/fjs/cas/module.f.mjs +129 -128
- package/fjs/cas/proof.f.d.mts +11 -8
- package/fjs/cas/proof.f.mjs +259 -147
- package/fjs/cas/types.d.ts +24 -10
- package/fjs/ci/config/module.f.d.mts +2 -2
- package/fjs/ci/config/module.f.mjs +2 -2
- package/fjs/ci/module.f.d.mts +5 -5
- package/fjs/ci/module.f.mjs +8 -7
- package/fjs/ci/nix/module.f.d.mts +7 -5
- package/fjs/ci/nix/module.f.mjs +13 -12
- package/fjs/ci/nix/proof.f.mjs +2 -2
- package/fjs/ci/proof.f.mjs +9 -5
- package/fjs/cli/module.f.d.mts +4 -6
- package/fjs/cli/module.f.mjs +4 -8
- package/fjs/cli/proof.f.mjs +17 -16
- package/fjs/cli/types.d.ts +2 -3
- package/fjs/common/monoid/types.d.ts +1 -1
- package/fjs/crypto/hmac/module.f.mjs +2 -2
- package/fjs/crypto/sha2/module.f.mjs +3 -1
- package/fjs/crypto/sha2/proof.f.d.mts +1 -0
- package/fjs/crypto/sha2/proof.f.mjs +24 -0
- package/fjs/crypto/sha2/types.d.ts +11 -0
- package/fjs/crypto/sign/module.f.mjs +2 -2
- package/fjs/dev/module.f.d.mts +13 -4
- package/fjs/dev/module.f.mjs +56 -27
- package/fjs/dev/update/module.f.d.mts +9 -4
- package/fjs/dev/update/module.f.mjs +14 -10
- package/fjs/dev/update/proof.f.d.mts +1 -3
- package/fjs/dev/update/proof.f.mjs +10 -5
- package/fjs/djs/module.f.d.mts +13 -5
- package/fjs/djs/module.f.mjs +31 -16
- package/fjs/djs/parser/module.f.d.mts +13 -3
- package/fjs/djs/parser/module.f.mjs +117 -16
- package/fjs/djs/parser/proof.f.d.mts +5 -1
- package/fjs/djs/parser/proof.f.mjs +274 -12
- package/fjs/djs/parser/types.d.ts +7 -1
- package/fjs/djs/proof.f.d.mts +18 -2
- package/fjs/djs/proof.f.mjs +187 -12
- package/fjs/djs/serializer/module.f.d.mts +25 -8
- package/fjs/djs/serializer/module.f.mjs +61 -15
- package/fjs/djs/serializer/proof.f.d.mts +5 -0
- package/fjs/djs/serializer/proof.f.mjs +23 -1
- package/fjs/djs/tokenizer/module.f.d.mts +17 -5
- package/fjs/djs/tokenizer/module.f.mjs +99 -60
- package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
- package/fjs/djs/tokenizer/proof.f.mjs +74 -67
- package/fjs/djs/transpiler/module.f.d.mts +12 -7
- package/fjs/djs/transpiler/module.f.mjs +82 -56
- package/fjs/djs/transpiler/types.d.ts +7 -3
- package/fjs/djs/types.d.ts +7 -1
- package/fjs/effects/list/module.f.d.mts +14 -11
- package/fjs/effects/list/module.f.mjs +13 -11
- package/fjs/effects/list/types.d.ts +27 -7
- package/fjs/effects/memory/module.f.d.mts +2 -1
- package/fjs/effects/memory/module.f.mjs +3 -4
- package/fjs/effects/memory/proof.f.mjs +13 -7
- package/fjs/effects/memory/types.d.ts +4 -3
- package/fjs/effects/mock/module.f.d.mts +20 -4
- package/fjs/effects/mock/module.f.mjs +37 -5
- package/fjs/effects/mock/types.d.ts +10 -1
- package/fjs/effects/module.d.mts +4 -2
- package/fjs/effects/module.f.d.mts +466 -280
- package/fjs/effects/module.f.mjs +537 -299
- package/fjs/effects/module.mjs +2 -1
- package/fjs/effects/node/memory/module.d.mts +4 -2
- package/fjs/effects/node/memory/module.mjs +6 -3
- package/fjs/effects/node/memory/proof.mjs +10 -4
- package/fjs/effects/node/module.d.mts +4 -3
- package/fjs/effects/node/module.f.d.mts +168 -33
- package/fjs/effects/node/module.f.mjs +256 -52
- package/fjs/effects/node/module.mjs +57 -34
- package/fjs/effects/node/proof.f.d.mts +28 -2
- package/fjs/effects/node/proof.f.mjs +161 -42
- package/fjs/effects/node/types.d.ts +106 -18
- package/fjs/effects/node/virtual/module.f.d.mts +18 -4
- package/fjs/effects/node/virtual/module.f.mjs +110 -68
- package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
- package/fjs/effects/node/virtual/proof.f.mjs +190 -9
- package/fjs/effects/proof.f.d.mts +69 -37
- package/fjs/effects/proof.f.mjs +410 -130
- package/fjs/effects/types.d.ts +161 -33
- package/fjs/emergent_testing/module.f.d.mts +19 -12
- package/fjs/emergent_testing/module.f.mjs +93 -33
- package/fjs/emergent_testing/proof.f.d.mts +21 -7
- package/fjs/emergent_testing/proof.f.mjs +166 -32
- package/fjs/emergent_testing/types.d.ts +22 -4
- package/fjs/fsm/module.f.d.mts +14 -4
- package/fjs/fsm/module.f.mjs +54 -37
- package/fjs/fsm/proof.f.d.mts +2 -0
- package/fjs/fsm/proof.f.mjs +83 -114
- package/fjs/js/keywords/module.f.d.mts +52 -0
- package/fjs/js/keywords/module.f.mjs +72 -0
- package/fjs/js/keywords/proof.f.d.mts +3 -0
- package/fjs/js/keywords/proof.f.mjs +13 -0
- package/fjs/js/tokenizer/module.f.d.mts +26 -6
- package/fjs/js/tokenizer/module.f.mjs +145 -159
- package/fjs/js/tokenizer/proof.f.d.mts +1 -0
- package/fjs/js/tokenizer/proof.f.mjs +54 -24
- package/fjs/js/tokenizer/types.d.ts +33 -24
- package/fjs/mcp/cas/module.f.d.mts +1 -6
- package/fjs/mcp/cas/module.f.mjs +55 -51
- package/fjs/mcp/cas/proof.f.d.mts +15 -0
- package/fjs/mcp/cas/proof.f.mjs +174 -0
- package/fjs/mcp/evo/module.f.d.mts +19 -10
- package/fjs/mcp/evo/module.f.mjs +48 -27
- package/fjs/mcp/evo/proof.f.d.mts +6 -1
- package/fjs/mcp/evo/proof.f.mjs +115 -31
- package/fjs/mcp/module.f.d.mts +4 -4
- package/fjs/mcp/module.f.mjs +6 -6
- package/fjs/mcp/proof.f.d.mts +5 -3
- package/fjs/mcp/proof.f.mjs +112 -46
- package/fjs/media/html/module.f.mjs +1 -1
- package/fjs/media/json/extended/module.f.d.mts +82 -0
- package/fjs/media/json/extended/module.f.mjs +153 -0
- package/fjs/media/json/extended/proof.f.d.mts +42 -0
- package/fjs/media/json/extended/proof.f.mjs +127 -0
- package/fjs/media/json/extended/types.d.ts +23 -0
- package/fjs/media/json/module.f.d.mts +8 -2
- package/fjs/media/json/module.f.mjs +43 -41
- package/fjs/media/json/number/module.f.d.mts +59 -0
- package/fjs/media/json/number/module.f.mjs +136 -0
- package/fjs/media/json/number/proof.f.d.mts +24 -0
- package/fjs/media/json/number/proof.f.mjs +86 -0
- package/fjs/media/json/number/types.d.ts +28 -0
- package/fjs/media/json/parser/module.f.d.mts +25 -13
- package/fjs/media/json/parser/module.f.mjs +114 -70
- package/fjs/media/json/parser/proof.f.d.mts +5 -0
- package/fjs/media/json/parser/proof.f.mjs +31 -1
- package/fjs/media/json/parser/types.d.ts +33 -14
- package/fjs/media/json/rtti/module.f.d.mts +1 -1
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/rtti/proof.f.mjs +9 -9
- package/fjs/media/json/schema/module.f.mjs +3 -13
- package/fjs/media/json/schema/proof.f.d.mts +0 -1
- package/fjs/media/json/schema/proof.f.mjs +1 -2
- package/fjs/media/json/serializer/module.f.d.mts +32 -1
- package/fjs/media/json/serializer/module.f.mjs +64 -2
- package/fjs/media/json/tokenizer/module.f.mjs +7 -3
- package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
- package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
- package/fjs/media/json/types.d.ts +36 -10
- package/fjs/media/lock/module.f.d.mts +100 -0
- package/fjs/media/lock/module.f.mjs +125 -0
- package/fjs/media/lock/proof.f.d.mts +33 -0
- package/fjs/media/lock/proof.f.mjs +196 -0
- package/fjs/media/lock/types.d.ts +15 -0
- package/fjs/media/module.f.d.mts +6 -5
- package/fjs/media/module.f.mjs +8 -7
- package/fjs/media/note/module.f.d.mts +121 -0
- package/fjs/media/note/module.f.mjs +131 -0
- package/fjs/media/note/proof.f.d.mts +29 -0
- package/fjs/media/note/proof.f.mjs +150 -0
- package/fjs/media/note/types.d.ts +10 -0
- package/fjs/media/proof.f.d.mts +4 -1
- package/fjs/media/proof.f.mjs +40 -21
- package/fjs/media/revision/module.f.d.mts +78 -7
- package/fjs/media/revision/module.f.mjs +119 -12
- package/fjs/media/revision/proof.f.d.mts +10 -0
- package/fjs/media/revision/proof.f.mjs +88 -0
- package/fjs/media/revision/types.d.ts +34 -5
- package/fjs/media/type/module.f.d.mts +33 -15
- package/fjs/media/type/module.f.mjs +35 -29
- package/fjs/media/type/proof.f.d.mts +2 -1
- package/fjs/media/type/proof.f.mjs +30 -10
- package/fjs/module.f.mjs +29 -8
- package/fjs/nanvm/proof.f.mjs +3 -3
- package/fjs/nanvm/rust/module.f.mjs +1 -1
- package/fjs/nanvm/update/module.f.d.mts +4 -4
- package/fjs/nanvm/update/module.f.mjs +8 -9
- package/fjs/nanvm/update/proof.f.mjs +4 -3
- package/fjs/proof.f.d.mts +3 -3
- package/fjs/proof.f.mjs +36 -10
- package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
- package/fjs/protocol/json_rpc/module.f.mjs +3 -3
- package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
- package/fjs/protocol/mcp/module.f.d.mts +10 -13
- package/fjs/protocol/mcp/module.f.mjs +74 -58
- package/fjs/protocol/mcp/proof.f.d.mts +13 -4
- package/fjs/protocol/mcp/proof.f.mjs +193 -61
- package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
- package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
- package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
- package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
- package/fjs/protocol/mcp/types.d.ts +17 -6
- package/fjs/sul/id/module.f.d.mts +0 -1
- package/fjs/sul/id/module.f.mjs +2 -3
- package/fjs/sul/level/hash/module.f.mjs +2 -1
- package/fjs/sul/level/hash/proof.f.mjs +2 -2
- package/fjs/sul/module.f.mjs +18 -13
- package/fjs/text/code_point/module.f.d.mts +8 -0
- package/fjs/text/code_point/module.f.mjs +8 -1
- package/fjs/text/code_point/proof.f.d.mts +1 -0
- package/fjs/text/code_point/proof.f.mjs +11 -0
- package/fjs/text/sgr/module.f.d.mts +4 -6
- package/fjs/text/sgr/module.f.mjs +6 -7
- package/fjs/text/utf16/module.f.mjs +7 -2
- package/fjs/text/utf16/proof.f.mjs +3 -3
- package/fjs/text/utf8/module.f.mjs +4 -2
- package/fjs/types/array/module.f.mjs +14 -2
- package/fjs/types/bit_vec/module.f.d.mts +0 -2
- package/fjs/types/bit_vec/module.f.mjs +46 -32
- package/fjs/types/bit_vec/proof.f.mjs +2 -2
- package/fjs/types/btree/remove/module.f.mjs +1 -1
- package/fjs/types/btree/set/module.f.mjs +9 -11
- package/fjs/types/btree/set/proof.f.mjs +12 -0
- package/fjs/types/byte_set/module.f.d.mts +11 -4
- package/fjs/types/byte_set/module.f.mjs +14 -6
- package/fjs/types/byte_set/proof.f.mjs +7 -7
- package/fjs/types/function/compare/module.f.mjs +10 -3
- package/fjs/types/list/module.f.d.mts +1 -1
- package/fjs/types/list/module.f.mjs +1 -1
- package/fjs/types/nullable/module.f.d.mts +18 -4
- package/fjs/types/nullable/module.f.mjs +21 -4
- package/fjs/types/nullable/proof.f.d.mts +4 -0
- package/fjs/types/nullable/proof.f.mjs +15 -0
- package/fjs/types/object/module.f.d.mts +12 -2
- package/fjs/types/object/module.f.mjs +11 -1
- package/fjs/types/patricia_trie/module.f.mjs +26 -11
- package/fjs/types/result/module.f.d.mts +3 -3
- package/fjs/types/result/module.f.mjs +3 -3
- package/fjs/types/rtti/common/module.f.d.mts +25 -28
- package/fjs/types/rtti/common/module.f.mjs +36 -32
- package/fjs/types/rtti/common/proof.f.mjs +5 -5
- package/fjs/types/rtti/data/module.f.d.mts +14 -0
- package/fjs/types/rtti/data/module.f.mjs +25 -2
- package/fjs/types/rtti/data/proof.f.d.mts +1 -0
- package/fjs/types/rtti/data/proof.f.mjs +25 -2
- package/fjs/types/rtti/parse/module.f.d.mts +24 -14
- package/fjs/types/rtti/parse/module.f.mjs +37 -28
- package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
- package/fjs/types/rtti/parse/proof.f.mjs +33 -14
- package/fjs/types/rtti/proof.f.mjs +3 -1
- package/fjs/types/rtti/ts/module.f.mjs +13 -17
- package/fjs/types/rtti/ts/types.d.ts +14 -1
- package/fjs/types/sorted_set/module.f.d.mts +18 -0
- package/fjs/types/sorted_set/module.f.mjs +22 -0
- package/fjs/types/sorted_set/proof.f.d.mts +1 -0
- package/fjs/types/sorted_set/proof.f.mjs +16 -1
- package/fjs/types/uint8array/module.f.d.mts +1 -1
- package/fjs/types/uint8array/module.f.mjs +1 -1
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +4 -7
- package/fjs/website/proof.f.mjs +2 -1
- package/package.json +2 -2
- package/fjs/dev/package_json/module.f.d.mts +0 -39
- package/fjs/dev/package_json/module.f.mjs +0 -40
- package/fjs/dev/package_json/proof.f.d.mts +0 -6
- package/fjs/dev/package_json/proof.f.mjs +0 -32
- package/fjs/effects/eff/module.f.d.mts +0 -20
- package/fjs/effects/eff/module.f.mjs +0 -70
- package/fjs/effects/eff/proof.f.d.mts +0 -15
- package/fjs/effects/eff/proof.f.mjs +0 -69
- package/fjs/effects/eff/types.d.ts +0 -71
- package/fjs/types/rtti/validate/module.f.d.mts +0 -41
- package/fjs/types/rtti/validate/module.f.mjs +0 -171
- package/fjs/types/rtti/validate/proof.f.d.mts +0 -125
- package/fjs/types/rtti/validate/proof.f.mjs +0 -437
- package/fjs/types/rtti/validate/types.d.ts +0 -6
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @import { Unknown } from '../../media/json/types.ts'
|
|
3
|
-
* @import {
|
|
3
|
+
* @import { Operation } from '../../effects/types.ts'
|
|
4
|
+
* @import { Effect, NotImplemented } from '../../effects/types.ts'
|
|
4
5
|
* @import { MemOperationMap } from '../../effects/mock/types.ts'
|
|
5
6
|
* @import { Key, MemOp } from '../../effects/memory/types.ts'
|
|
6
|
-
* @import { Ts } from '../../types/rtti/ts/types.ts'
|
|
7
7
|
* @import {
|
|
8
|
-
* ToolsListParams,
|
|
9
|
-
*
|
|
8
|
+
* ToolsListParams,
|
|
9
|
+
* ToolsCallParams,
|
|
10
|
+
* McpHandlers,
|
|
11
|
+
* McpConfig,
|
|
12
|
+
* McpSessionState,
|
|
10
13
|
* } from './types.ts'
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
import { assert, assertEq } from '../../asserts/module.f.mjs'
|
|
14
|
-
import {
|
|
17
|
+
import { runPure } from '../../effects/module.f.mjs'
|
|
18
|
+
import { history, historyStep, mapStep, pureOk, step } from '../../effects/module.f.mjs'
|
|
19
|
+
import { error, ok, unwrap as unwrapResult } from '../../types/result/module.f.mjs'
|
|
15
20
|
import { run } from '../../effects/mock/module.f.mjs'
|
|
21
|
+
import { internalError } from '../json_rpc/module.f.mjs'
|
|
16
22
|
import { asBase, asNominal, create, read } from '../../effects/memory/module.f.mjs'
|
|
17
23
|
import {
|
|
18
24
|
uninitializedState, mcpStep, notInitialized, fromRegistry, toolEntry, okResult,
|
|
@@ -34,12 +40,12 @@ const mock = {
|
|
|
34
40
|
const id = `k${state.next}`
|
|
35
41
|
/** @type {Key<unknown>} */
|
|
36
42
|
const key = asNominal(id)
|
|
37
|
-
return [{ next: state.next + 1, values: { ...state.values, [id]: value } }, key]
|
|
43
|
+
return [{ next: state.next + 1, values: { ...state.values, [id]: value } }, ok(key)]
|
|
38
44
|
},
|
|
39
|
-
memRead: key => state => [state, state.values[asBase(key)]],
|
|
45
|
+
memRead: key => state => [state, ok(state.values[asBase(key)])],
|
|
40
46
|
memWrite: (key, value) => state => {
|
|
41
47
|
const id = asBase(key)
|
|
42
|
-
return [{ ...state, values: { ...state.values, [id]: value } }, undefined]
|
|
48
|
+
return [{ ...state, values: { ...state.values, [id]: value } }, ok(undefined)]
|
|
43
49
|
},
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -59,66 +65,149 @@ const configNoTools = { ...config, capabilities: {} }
|
|
|
59
65
|
const handlers = {
|
|
60
66
|
// Echoes a received cursor as `nextCursor` so tests can observe pagination params.
|
|
61
67
|
toolsList: (/** @type {ToolsListParams} */ p) =>
|
|
62
|
-
|
|
63
|
-
(pure(p.cursor === undefined
|
|
68
|
+
pureOk(p.cursor === undefined
|
|
64
69
|
? { tools: [{ name: 'greet', inputSchema: {} }] }
|
|
65
|
-
: { tools: [], nextCursor: p.cursor })
|
|
70
|
+
: { tools: [], nextCursor: p.cursor }),
|
|
66
71
|
toolsCall: (/** @type {ToolsCallParams} */ _p) =>
|
|
67
|
-
|
|
68
|
-
(pure({ content: [{ type: 'text', text: 'hello' }] })),
|
|
72
|
+
pureOk({ content: [{ type: 'text', text: 'hello' }] }),
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/** @typedef {readonly [unknown, McpSessionState]} _StepResult */
|
|
72
76
|
|
|
73
|
-
// Run a memory effect against the mock
|
|
74
|
-
|
|
77
|
+
// Run a memory effect against the mock and unwrap what it answered. The
|
|
78
|
+
// channel stays generic because nothing here interprets it: a proof has nobody
|
|
79
|
+
// to report a failure to, so an `error` is a panic and the tests read the `ok`.
|
|
80
|
+
/** @type {<T, E>(effect: Effect<MemOp, T, E>) => T} */
|
|
75
81
|
const runMem = effect =>
|
|
76
|
-
run(mock)(initial)(effect)[1]
|
|
82
|
+
unwrapResult(run(mock)(initial)(effect)[1])
|
|
77
83
|
|
|
78
84
|
// TypeScript infers O = Operation (the upper bound) rather than O = never when
|
|
79
85
|
// O flows through McpHandlers<never>, so we cast the widened type down to MemOp.
|
|
80
|
-
/** @type {<T>(e: Effect<Operation, T>) => Effect<MemOp, T>} */
|
|
81
|
-
const asMemEffect = e => /** @type {Effect<MemOp, any>} */ (
|
|
86
|
+
/** @type {<T, E>(e: Effect<Operation, T, E>) => Effect<MemOp, T, E>} */
|
|
87
|
+
const asMemEffect = e => /** @type {Effect<MemOp, any, any>} */ (e)
|
|
82
88
|
|
|
83
89
|
// Pairs the last step's response with the session state read back afterwards.
|
|
84
90
|
// The response is still needed after the read, so it is carried forward in a
|
|
85
91
|
// history rather than closed over by a nested continuation.
|
|
86
|
-
/** @type {(key: Key<McpSessionState>) => (e: Effect<Operation, unknown>) => Effect<Operation, _StepResult>} */
|
|
92
|
+
/** @type {(key: Key<McpSessionState>) => (e: Effect<Operation, unknown, never>) => Effect<Operation, _StepResult, NotImplemented>} */
|
|
87
93
|
const withState = key => e => {
|
|
88
94
|
const read0 = historyStep(history(e), () => read(key))
|
|
95
|
+
// A history holds `ok` values, so `resp` is the response itself rather
|
|
96
|
+
// than a `Result` around it: a failed link short-circuits, contributing
|
|
97
|
+
// nothing to the tuple and skipping this projection with it.
|
|
89
98
|
return mapStep(read0, ([state, resp]) => /** @type {const} */ ([resp, state]))
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
// Run one step from uninitializedState, return [response, newState].
|
|
93
|
-
/** @type {(cfg: McpConfig) => (msg:
|
|
102
|
+
/** @type {(cfg: McpConfig) => (msg: Unknown) => _StepResult} */
|
|
94
103
|
const step1 = cfg => msg =>
|
|
95
104
|
runMem(asMemEffect(step(
|
|
96
|
-
create(
|
|
97
|
-
key => withState(key)(mcpStep(cfg)(handlers)(key)(
|
|
105
|
+
create(uninitializedState),
|
|
106
|
+
key => withState(key)(mcpStep(cfg)(handlers)(key)(msg)))))
|
|
98
107
|
|
|
99
108
|
// Run initialize then a second step, return [response, newState] of the second.
|
|
100
|
-
/** @type {(cfg: McpConfig) => (msg1:
|
|
109
|
+
/** @type {(cfg: McpConfig) => (msg1: Unknown) => (msg2: Unknown) => _StepResult} */
|
|
101
110
|
const step2 = cfg => msg1 => msg2 =>
|
|
102
111
|
runMem(asMemEffect(step(
|
|
103
|
-
create(
|
|
112
|
+
create(uninitializedState),
|
|
104
113
|
key => {
|
|
105
|
-
const r1 = mcpStep(cfg)(handlers)(key)(
|
|
106
|
-
const r2 = step(r1, () => mcpStep(cfg)(handlers)(key)(
|
|
114
|
+
const r1 = mcpStep(cfg)(handlers)(key)(msg1)
|
|
115
|
+
const r2 = step(r1, () => mcpStep(cfg)(handlers)(key)(msg2))
|
|
107
116
|
return withState(key)(r2)
|
|
108
117
|
})))
|
|
109
118
|
|
|
110
119
|
// Run initialize, notifications/initialized, then a third step; return [response, newState] of the third.
|
|
111
|
-
/** @type {(cfg: McpConfig) => (msg1:
|
|
120
|
+
/** @type {(cfg: McpConfig) => (msg1: Unknown) => (msg2: Unknown) => (msg3: Unknown) => _StepResult} */
|
|
112
121
|
const step3 = cfg => msg1 => msg2 => msg3 =>
|
|
113
122
|
runMem(asMemEffect(step(
|
|
114
|
-
create(
|
|
123
|
+
create(uninitializedState),
|
|
115
124
|
key => {
|
|
116
|
-
const r1 = mcpStep(cfg)(handlers)(key)(
|
|
117
|
-
const r2 = step(r1, () => mcpStep(cfg)(handlers)(key)(
|
|
118
|
-
const r3 = step(r2, () => mcpStep(cfg)(handlers)(key)(
|
|
125
|
+
const r1 = mcpStep(cfg)(handlers)(key)(msg1)
|
|
126
|
+
const r2 = step(r1, () => mcpStep(cfg)(handlers)(key)(msg2))
|
|
127
|
+
const r3 = step(r2, () => mcpStep(cfg)(handlers)(key)(msg3))
|
|
119
128
|
return withState(key)(r3)
|
|
120
129
|
})))
|
|
121
130
|
|
|
131
|
+
// ── Response accessors ────────────────────────────────────────────────────────
|
|
132
|
+
//
|
|
133
|
+
// A step's response is `unknown`, and its shape is exactly what these proofs
|
|
134
|
+
// exist to check — so reading it through a cast would assume the thing being
|
|
135
|
+
// proved. These accessors check their way in instead, and fail at the read
|
|
136
|
+
// rather than somewhere downstream.
|
|
137
|
+
|
|
138
|
+
/** Whether `resp` is an object carrying an `error` member. */
|
|
139
|
+
/** @type {(resp: unknown) => boolean} */
|
|
140
|
+
const hasError = resp =>
|
|
141
|
+
typeof resp === 'object' && resp !== null && 'error' in resp
|
|
142
|
+
|
|
143
|
+
/** @type {(resp: unknown) => number} */
|
|
144
|
+
const errorCode = resp => {
|
|
145
|
+
assert(hasError(resp) && typeof resp === 'object' && resp !== null && 'error' in resp, resp)
|
|
146
|
+
const { error } = resp
|
|
147
|
+
assert(typeof error === 'object' && error !== null && 'code' in error, error)
|
|
148
|
+
const { code } = error
|
|
149
|
+
assert(typeof code === 'number', code)
|
|
150
|
+
return code
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** @type {(resp: unknown) => unknown} */
|
|
154
|
+
const errorId = resp => {
|
|
155
|
+
assert(typeof resp === 'object' && resp !== null && 'id' in resp, resp)
|
|
156
|
+
return resp.id
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** @type {(resp: unknown) => object} */
|
|
160
|
+
const resultOf = resp => {
|
|
161
|
+
assert(typeof resp === 'object' && resp !== null && 'result' in resp, resp)
|
|
162
|
+
const { result } = resp
|
|
163
|
+
assert(typeof result === 'object' && result !== null, result)
|
|
164
|
+
return result
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** @type {(resp: unknown) => string} */
|
|
168
|
+
const protocolVersion = resp => {
|
|
169
|
+
const result = resultOf(resp)
|
|
170
|
+
assert('protocolVersion' in result, result)
|
|
171
|
+
const { protocolVersion: v } = result
|
|
172
|
+
assert(typeof v === 'string', v)
|
|
173
|
+
return v
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @type {(resp: unknown) => readonly string[]} */
|
|
177
|
+
const toolNames = resp => {
|
|
178
|
+
const result = resultOf(resp)
|
|
179
|
+
assert('tools' in result, result)
|
|
180
|
+
const { tools } = result
|
|
181
|
+
assert(tools instanceof Array, tools)
|
|
182
|
+
return tools.map(t => {
|
|
183
|
+
assert(typeof t === 'object' && t !== null && 'name' in t, t)
|
|
184
|
+
const { name } = t
|
|
185
|
+
assert(typeof name === 'string', name)
|
|
186
|
+
return name
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** @type {(resp: unknown) => unknown} */
|
|
191
|
+
const nextCursor = resp => {
|
|
192
|
+
const result = resultOf(resp)
|
|
193
|
+
assert('nextCursor' in result, result)
|
|
194
|
+
return result.nextCursor
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The `text` of the first content item of a `tools/call` result. */
|
|
198
|
+
/** @type {(resp: unknown) => string} */
|
|
199
|
+
const firstText = resp => {
|
|
200
|
+
const result = resultOf(resp)
|
|
201
|
+
assert('content' in result, result)
|
|
202
|
+
const { content } = result
|
|
203
|
+
assert(content instanceof Array && content.length !== 0, content)
|
|
204
|
+
const [item] = content
|
|
205
|
+
assert(typeof item === 'object' && item !== null && 'text' in item, item)
|
|
206
|
+
const { text } = item
|
|
207
|
+
assert(typeof text === 'string', text)
|
|
208
|
+
return text
|
|
209
|
+
}
|
|
210
|
+
|
|
122
211
|
// ── Test messages ─────────────────────────────────────────────────────────────
|
|
123
212
|
|
|
124
213
|
const initMsg = { jsonrpc: '2.0', method: 'initialize', id: 1,
|
|
@@ -126,9 +215,53 @@ const initMsg = { jsonrpc: '2.0', method: 'initialize', id: 1,
|
|
|
126
215
|
|
|
127
216
|
const initNotif = { jsonrpc: '2.0', method: 'notifications/initialized' }
|
|
128
217
|
|
|
218
|
+
/** A memory handler that answers as a runner with no such operation. */
|
|
219
|
+
const memNotImplemented = () => (/** @type {_MemoryState} */ state) =>
|
|
220
|
+
/** @type {const} */ ([state, error(/** @type {const} */ (['notImplemented', 'memRead']))])
|
|
221
|
+
|
|
222
|
+
// Runs one step against a memory mock with `overrides` applied, from a session
|
|
223
|
+
// slot created before them so the slot itself always exists.
|
|
224
|
+
/** @type {(overrides: Partial<MemOperationMap<MemOp, _MemoryState>>) => (msg: Unknown) => unknown} */
|
|
225
|
+
const failingStep = overrides => msg => {
|
|
226
|
+
const [state, key] = run(mock)(initial)(create(uninitializedState))
|
|
227
|
+
const runner = run(/** @type {MemOperationMap<MemOp, _MemoryState>} */ ({ ...mock, ...overrides }))
|
|
228
|
+
// A `Handle` answers `Effect<…, Response | null, never>`, so the payload
|
|
229
|
+
// the runner hands back is the `ok` around the response and the unwrap is
|
|
230
|
+
// total — the failures these tests inject are the ones `mcpStep` itself
|
|
231
|
+
// absorbs into an error response.
|
|
232
|
+
return unwrapResult(runner(state)(asMemEffect(mcpStep(config)(handlers)(unwrapResult(key))(msg)))[1])
|
|
233
|
+
}
|
|
234
|
+
|
|
129
235
|
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
130
236
|
|
|
131
237
|
export const proof = {
|
|
238
|
+
// A session slot the runner cannot reach is `-32603`, not `-32002`.
|
|
239
|
+
// `notInitialized` tells the client to run the handshake it has already
|
|
240
|
+
// run, so it would run it forever; an internal error says the fault is the
|
|
241
|
+
// server's. Three sites answer it — the `initialize` read, the transition
|
|
242
|
+
// write, and the gate every other method passes through — and a
|
|
243
|
+
// notification, having no response frame at all, answers `null` instead.
|
|
244
|
+
sessionStateFailure: {
|
|
245
|
+
initializeRead: () => {
|
|
246
|
+
const resp = failingStep({ memRead: memNotImplemented })(initMsg)
|
|
247
|
+
assertEq(errorCode(resp), internalError.code)
|
|
248
|
+
},
|
|
249
|
+
initializeWrite: () => {
|
|
250
|
+
const resp = failingStep({ memWrite: memNotImplemented })(initMsg)
|
|
251
|
+
assertEq(errorCode(resp), internalError.code)
|
|
252
|
+
},
|
|
253
|
+
gatedMethod: () => {
|
|
254
|
+
const resp = failingStep({ memRead: memNotImplemented })(
|
|
255
|
+
{ jsonrpc: '2.0', method: 'tools/list', id: 3 })
|
|
256
|
+
assertEq(errorCode(resp), internalError.code)
|
|
257
|
+
},
|
|
258
|
+
// The notification path has nowhere to report, so it stays silent and
|
|
259
|
+
// leaves the session gated rather than claiming the transition.
|
|
260
|
+
initializedNotification: () => {
|
|
261
|
+
const resp = failingStep({ memRead: memNotImplemented })(initNotif)
|
|
262
|
+
assertEq(resp, null)
|
|
263
|
+
},
|
|
264
|
+
},
|
|
132
265
|
lifecycle: {
|
|
133
266
|
initialStateIsUninitialized: () => {
|
|
134
267
|
assert(uninitializedState[0] === 'uninitialized')
|
|
@@ -147,16 +280,14 @@ export const proof = {
|
|
|
147
280
|
|
|
148
281
|
initializeReturnsResult: () => {
|
|
149
282
|
const [resp] = step1(config)(initMsg)
|
|
150
|
-
|
|
151
|
-
const r = /** @type {{ result: { protocolVersion: string } }} */ (resp).result
|
|
152
|
-
assertEq(r.protocolVersion, '2024-11-05')
|
|
283
|
+
assertEq(protocolVersion(resp), '2024-11-05')
|
|
153
284
|
},
|
|
154
285
|
|
|
155
286
|
initializeWithBadParamsReturnsInvalidParams: () => {
|
|
156
287
|
const bad = { jsonrpc: '2.0', method: 'initialize', id: 2, params: { wrong: true } }
|
|
157
288
|
const [resp, newState] = step1(config)(bad)
|
|
158
289
|
assert(newState[0] === 'uninitialized')
|
|
159
|
-
assertEq(
|
|
290
|
+
assertEq(errorCode(resp), -32602)
|
|
160
291
|
},
|
|
161
292
|
|
|
162
293
|
notificationBeforeInitReturnNull: () => {
|
|
@@ -174,39 +305,39 @@ export const proof = {
|
|
|
174
305
|
doubleInitializeReturnsInvalidRequest: () => {
|
|
175
306
|
const [resp, newState] = step2(config)(initMsg)(initMsg)
|
|
176
307
|
assert(newState[0] === 'initializing')
|
|
177
|
-
assertEq(
|
|
308
|
+
assertEq(errorCode(resp), -32600)
|
|
178
309
|
},
|
|
179
310
|
|
|
180
311
|
pingBeforeInitSucceeds: () => {
|
|
181
312
|
const msg = { jsonrpc: '2.0', method: 'ping', id: 11 }
|
|
182
313
|
const [resp, newState] = step1(config)(msg)
|
|
183
314
|
assert(newState[0] === 'uninitialized')
|
|
184
|
-
assert(!(
|
|
315
|
+
assert(!hasError(resp), resp)
|
|
185
316
|
},
|
|
186
317
|
|
|
187
318
|
pingDuringInitializingSucceeds: () => {
|
|
188
319
|
const msg = { jsonrpc: '2.0', method: 'ping', id: 12 }
|
|
189
320
|
const [resp, newState] = step2(config)(initMsg)(msg)
|
|
190
321
|
assert(newState[0] === 'initializing')
|
|
191
|
-
assert(!(
|
|
322
|
+
assert(!hasError(resp), resp)
|
|
192
323
|
},
|
|
193
324
|
|
|
194
325
|
pingAfterInitSucceeds: () => {
|
|
195
326
|
const msg = { jsonrpc: '2.0', method: 'ping', id: 15 }
|
|
196
327
|
const [resp] = step3(config)(initMsg)(initNotif)(msg)
|
|
197
|
-
assert(!(
|
|
328
|
+
assert(!hasError(resp), resp)
|
|
198
329
|
},
|
|
199
330
|
|
|
200
331
|
pingWithObjectParamsSucceeds: () => {
|
|
201
332
|
const msg = { jsonrpc: '2.0', method: 'ping', id: 19, params: {} }
|
|
202
333
|
const [resp] = step1(config)(msg)
|
|
203
|
-
assert(!(
|
|
334
|
+
assert(!hasError(resp), resp)
|
|
204
335
|
},
|
|
205
336
|
|
|
206
337
|
pingInvalidParamsReturnsInvalidParams: () => {
|
|
207
338
|
const msg = { jsonrpc: '2.0', method: 'ping', id: 20, params: 1 }
|
|
208
339
|
const [resp] = step1(config)(msg)
|
|
209
|
-
assertEq(
|
|
340
|
+
assertEq(errorCode(resp), -32602)
|
|
210
341
|
},
|
|
211
342
|
|
|
212
343
|
initializedNotificationObjectParamsTransitions: () => {
|
|
@@ -227,21 +358,21 @@ export const proof = {
|
|
|
227
358
|
const msg = { jsonrpc: '2.0', method: 'tools/list', id: 3 }
|
|
228
359
|
const [resp, newState] = step1(config)(msg)
|
|
229
360
|
assert(newState[0] === 'uninitialized')
|
|
230
|
-
assertEq(
|
|
361
|
+
assertEq(errorCode(resp), notInitialized.code)
|
|
231
362
|
},
|
|
232
363
|
|
|
233
364
|
methodDuringInitializingReturnsNotInitialized: () => {
|
|
234
365
|
const msg = { jsonrpc: '2.0', method: 'tools/list', id: 16 }
|
|
235
366
|
const [resp, newState] = step2(config)(initMsg)(msg)
|
|
236
367
|
assert(newState[0] === 'initializing')
|
|
237
|
-
assertEq(
|
|
368
|
+
assertEq(errorCode(resp), notInitialized.code)
|
|
238
369
|
},
|
|
239
370
|
|
|
240
371
|
invalidEnvelopeReturnsInvalidRequest: () => {
|
|
241
372
|
const bad = { jsonrpc: '1.0', method: 'ping', id: 4 }
|
|
242
373
|
const [resp] = step1(config)(bad)
|
|
243
|
-
assertEq(
|
|
244
|
-
assertEq(
|
|
374
|
+
assertEq(errorCode(resp), -32600)
|
|
375
|
+
assertEq(errorId(resp), null)
|
|
245
376
|
},
|
|
246
377
|
},
|
|
247
378
|
|
|
@@ -249,68 +380,68 @@ export const proof = {
|
|
|
249
380
|
toolsListSucceeds: () => {
|
|
250
381
|
const msg = { jsonrpc: '2.0', method: 'tools/list', id: 5 }
|
|
251
382
|
const [resp] = step3(config)(initMsg)(initNotif)(msg)
|
|
252
|
-
assertEq(
|
|
253
|
-
assertEq(
|
|
383
|
+
assertEq(toolNames(resp).length, 1)
|
|
384
|
+
assertEq(toolNames(resp)[0], 'greet')
|
|
254
385
|
},
|
|
255
386
|
|
|
256
387
|
toolsListPassesCursorToHandler: () => {
|
|
257
388
|
const msg = { jsonrpc: '2.0', method: 'tools/list', id: 17,
|
|
258
389
|
params: { cursor: 'page-2' } }
|
|
259
390
|
const [resp] = step3(config)(initMsg)(initNotif)(msg)
|
|
260
|
-
assertEq(
|
|
391
|
+
assertEq(nextCursor(resp), 'page-2')
|
|
261
392
|
},
|
|
262
393
|
|
|
263
394
|
toolsListInvalidCursorReturnsInvalidParams: () => {
|
|
264
395
|
const msg = { jsonrpc: '2.0', method: 'tools/list', id: 18,
|
|
265
396
|
params: { cursor: 42 } }
|
|
266
397
|
const [resp] = step3(config)(initMsg)(initNotif)(msg)
|
|
267
|
-
assertEq(
|
|
398
|
+
assertEq(errorCode(resp), -32602)
|
|
268
399
|
},
|
|
269
400
|
|
|
270
401
|
toolsCallSucceeds: () => {
|
|
271
402
|
const msg = { jsonrpc: '2.0', method: 'tools/call', id: 6,
|
|
272
403
|
params: { name: 'greet', arguments: {} } }
|
|
273
404
|
const [resp] = step3(config)(initMsg)(initNotif)(msg)
|
|
274
|
-
assertEq(
|
|
405
|
+
assertEq(firstText(resp), 'hello')
|
|
275
406
|
},
|
|
276
407
|
|
|
277
408
|
toolsCallBadParamsReturnsInvalidParams: () => {
|
|
278
409
|
const msg = { jsonrpc: '2.0', method: 'tools/call', id: 7, params: { missing: true } }
|
|
279
410
|
const [resp] = step3(config)(initMsg)(initNotif)(msg)
|
|
280
|
-
assertEq(
|
|
411
|
+
assertEq(errorCode(resp), -32602)
|
|
281
412
|
},
|
|
282
413
|
|
|
283
414
|
toolsCallAbsentArgumentsSucceeds: () => {
|
|
284
415
|
const msg = { jsonrpc: '2.0', method: 'tools/call', id: 13,
|
|
285
416
|
params: { name: 'greet' } }
|
|
286
417
|
const [resp] = step3(config)(initMsg)(initNotif)(msg)
|
|
287
|
-
assertEq(
|
|
418
|
+
assertEq(firstText(resp), 'hello')
|
|
288
419
|
},
|
|
289
420
|
|
|
290
421
|
toolsCallNullArgumentsReturnsInvalidParams: () => {
|
|
291
422
|
const msg = { jsonrpc: '2.0', method: 'tools/call', id: 14,
|
|
292
423
|
params: { name: 'greet', arguments: null } }
|
|
293
424
|
const [resp] = step3(config)(initMsg)(initNotif)(msg)
|
|
294
|
-
assertEq(
|
|
425
|
+
assertEq(errorCode(resp), -32602)
|
|
295
426
|
},
|
|
296
427
|
|
|
297
428
|
toolsListWithoutCapabilityReturnsMethodNotFound: () => {
|
|
298
429
|
const msg = { jsonrpc: '2.0', method: 'tools/list', id: 8 }
|
|
299
430
|
const [resp] = step3(configNoTools)(initMsg)(initNotif)(msg)
|
|
300
|
-
assertEq(
|
|
431
|
+
assertEq(errorCode(resp), -32601)
|
|
301
432
|
},
|
|
302
433
|
|
|
303
434
|
toolsCallWithoutCapabilityReturnsMethodNotFound: () => {
|
|
304
435
|
const msg = { jsonrpc: '2.0', method: 'tools/call', id: 9,
|
|
305
436
|
params: { name: 'greet', arguments: {} } }
|
|
306
437
|
const [resp] = step3(configNoTools)(initMsg)(initNotif)(msg)
|
|
307
|
-
assertEq(
|
|
438
|
+
assertEq(errorCode(resp), -32601)
|
|
308
439
|
},
|
|
309
440
|
|
|
310
441
|
unknownMethodReturnsMethodNotFound: () => {
|
|
311
442
|
const msg = { jsonrpc: '2.0', method: 'resources/list', id: 10 }
|
|
312
443
|
const [resp] = step3(config)(initMsg)(initNotif)(msg)
|
|
313
|
-
assertEq(
|
|
444
|
+
assertEq(errorCode(resp), -32601)
|
|
314
445
|
},
|
|
315
446
|
},
|
|
316
447
|
|
|
@@ -324,12 +455,13 @@ export const proof = {
|
|
|
324
455
|
toolsCallAbsentArgumentsDefaultsToEmptyObject: () => {
|
|
325
456
|
const echoArgs = /** @type {const} */ ({})
|
|
326
457
|
const entry = toolEntry('echo', 'echoes', echoArgs,
|
|
327
|
-
|
|
328
|
-
(() => pure(okResult('ok'))))
|
|
458
|
+
() => pureOk(okResult('ok')))
|
|
329
459
|
const handlers = fromRegistry([entry])
|
|
330
|
-
const [
|
|
331
|
-
assert(
|
|
332
|
-
|
|
460
|
+
const [r] = runPure(handlers.toolsCall({ name: 'echo' }))
|
|
461
|
+
assert(r !== undefined && r[0] === 'ok', r)
|
|
462
|
+
const [item] = r[1].content
|
|
463
|
+
assert(item.type === 'text', item)
|
|
464
|
+
assertEq(item.text, 'ok')
|
|
333
465
|
},
|
|
334
466
|
},
|
|
335
467
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* stdio transport for JSON-RPC / MCP servers.
|
|
3
3
|
*
|
|
4
4
|
* `stdioTransport` wraps a step function — the `mcpStep`-shaped
|
|
5
|
-
* `(value) => Effect<O, Response | null>` from `fjs/protocol/mcp/module.f.mjs` — in the
|
|
5
|
+
* `(value) => Effect<O, Response | null, never>` from `fjs/protocol/mcp/module.f.mjs` — in the
|
|
6
6
|
* canonical read → parse → dispatch → write loop, expressed as a recursive
|
|
7
7
|
* effect so it stays in the pure effect model and is fully testable against a
|
|
8
8
|
* mock stdin / stdout (see `fjs/effects/node/virtual`) with no real process.
|
|
@@ -29,13 +29,15 @@
|
|
|
29
29
|
*
|
|
30
30
|
* @module
|
|
31
31
|
*
|
|
32
|
-
* @import {
|
|
33
|
-
* @import {
|
|
32
|
+
* @import { Operation } from '../../../effects/types.ts'
|
|
33
|
+
* @import { IoChannel, Read, Write } from '../../../effects/node/types.ts'
|
|
34
|
+
* @import { Effect } from '../../../effects/types.ts'
|
|
34
35
|
* @import { Response } from '../../json_rpc/types.ts'
|
|
35
36
|
* @import { Step } from './types.ts'
|
|
36
37
|
*/
|
|
37
|
-
import type {
|
|
38
|
-
import type { Read, Write } from '../../../effects/node/types.ts';
|
|
38
|
+
import type { Operation } from '../../../effects/types.ts';
|
|
39
|
+
import type { IoChannel, Read, Write } from '../../../effects/node/types.ts';
|
|
40
|
+
import type { Effect } from '../../../effects/types.ts';
|
|
39
41
|
import type { Step } from './types.ts';
|
|
40
42
|
/**
|
|
41
43
|
* Drives the read-parse-dispatch-write loop for `handler` over stdin/stdout.
|
|
@@ -43,8 +45,14 @@ import type { Step } from './types.ts';
|
|
|
43
45
|
* Recurses after each handled line; terminates (resolving to `void`) when
|
|
44
46
|
* `readLine` reports EOF.
|
|
45
47
|
*
|
|
48
|
+
* A transport that cannot read its own input has no fallback to choose, but it
|
|
49
|
+
* does not have to decide that here: the failure ends the loop and travels to
|
|
50
|
+
* whoever started the server, which for `fjs mcp` is a message on `stderr` and
|
|
51
|
+
* exit `1`. EOF is not that — `readLine` answers `ok(null)`, the loop ends, and
|
|
52
|
+
* the server has done its job.
|
|
53
|
+
*
|
|
46
54
|
* @template {Operation} O
|
|
47
55
|
* @param {Step<O>} handler
|
|
48
|
-
* @returns {Effect<Read | Write | O, void>}
|
|
56
|
+
* @returns {Effect<Read | Write | O, void, IoChannel>}
|
|
49
57
|
*/
|
|
50
|
-
export declare const stdioTransport: <O extends Operation>(handler: Step<O>) => Effect<Read | Write | O, void>;
|
|
58
|
+
export declare const stdioTransport: <O extends Operation>(handler: Step<O>) => Effect<Read | Write | O, void, IoChannel>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* stdio transport for JSON-RPC / MCP servers.
|
|
3
3
|
*
|
|
4
4
|
* `stdioTransport` wraps a step function — the `mcpStep`-shaped
|
|
5
|
-
* `(value) => Effect<O, Response | null>` from `fjs/protocol/mcp/module.f.mjs` — in the
|
|
5
|
+
* `(value) => Effect<O, Response | null, never>` from `fjs/protocol/mcp/module.f.mjs` — in the
|
|
6
6
|
* canonical read → parse → dispatch → write loop, expressed as a recursive
|
|
7
7
|
* effect so it stays in the pure effect model and is fully testable against a
|
|
8
8
|
* mock stdin / stdout (see `fjs/effects/node/virtual`) with no real process.
|
|
@@ -29,19 +29,20 @@
|
|
|
29
29
|
*
|
|
30
30
|
* @module
|
|
31
31
|
*
|
|
32
|
-
* @import {
|
|
33
|
-
* @import {
|
|
32
|
+
* @import { Operation } from '../../../effects/types.ts'
|
|
33
|
+
* @import { IoChannel, Read, Write } from '../../../effects/node/types.ts'
|
|
34
|
+
* @import { Effect } from '../../../effects/types.ts'
|
|
34
35
|
* @import { Response } from '../../json_rpc/types.ts'
|
|
35
36
|
* @import { Step } from './types.ts'
|
|
36
37
|
*/
|
|
37
38
|
|
|
38
|
-
import {
|
|
39
|
-
import { readLine, write } from '../../../effects/node/module.f.mjs'
|
|
39
|
+
import { pureError, pureOk, resultStep, step } from '../../../effects/module.f.mjs'
|
|
40
|
+
import { ioError, readLine, write } from '../../../effects/node/module.f.mjs'
|
|
40
41
|
import { tryUtf8 } from '../../../text/module.f.mjs'
|
|
41
42
|
import { parse, stringify } from '../../../media/json/module.f.mjs'
|
|
42
43
|
import { sort } from '../../../types/object/module.f.mjs'
|
|
43
44
|
import { internalError, jsonrpc, parseError } from '../../json_rpc/module.f.mjs'
|
|
44
|
-
import { error
|
|
45
|
+
import { error } from '../../../types/result/module.f.mjs'
|
|
45
46
|
|
|
46
47
|
const stringifyJson = stringify(sort)
|
|
47
48
|
|
|
@@ -55,16 +56,13 @@ const parseErrorResponse = { jsonrpc, error: parseError, id: null }
|
|
|
55
56
|
const internalErrorResponse = id => ({ jsonrpc, error: internalError, id })
|
|
56
57
|
|
|
57
58
|
/** Encodes a response as a newline-terminated UTF-8 line and writes it to `stdout`.
|
|
58
|
-
* @type {(resp: Response) => Effect<Write,
|
|
59
|
+
* @type {(resp: Response) => Effect<Write, void, IoChannel>}
|
|
59
60
|
*/
|
|
60
61
|
const writeResponse = resp => {
|
|
61
62
|
const v = tryUtf8(stringifyJson(resp) + '\n')
|
|
62
63
|
return v === null
|
|
63
|
-
?
|
|
64
|
-
:
|
|
65
|
-
write('stdout', v),
|
|
66
|
-
() => pure(ok(undefined)),
|
|
67
|
-
)
|
|
64
|
+
? pureError(ioError({ message: 'response does not encode as UTF-8' }))
|
|
65
|
+
: write('stdout', v)
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
/**
|
|
@@ -73,35 +71,48 @@ const writeResponse = resp => {
|
|
|
73
71
|
* Recurses after each handled line; terminates (resolving to `void`) when
|
|
74
72
|
* `readLine` reports EOF.
|
|
75
73
|
*
|
|
74
|
+
* A transport that cannot read its own input has no fallback to choose, but it
|
|
75
|
+
* does not have to decide that here: the failure ends the loop and travels to
|
|
76
|
+
* whoever started the server, which for `fjs mcp` is a message on `stderr` and
|
|
77
|
+
* exit `1`. EOF is not that — `readLine` answers `ok(null)`, the loop ends, and
|
|
78
|
+
* the server has done its job.
|
|
79
|
+
*
|
|
76
80
|
* @template {Operation} O
|
|
77
81
|
* @param {Step<O>} handler
|
|
78
|
-
* @returns {Effect<Read | Write | O, void>}
|
|
82
|
+
* @returns {Effect<Read | Write | O, void, IoChannel>}
|
|
79
83
|
*/
|
|
80
84
|
export const stdioTransport = handler =>
|
|
81
85
|
step(
|
|
82
86
|
readLine('stdin'),
|
|
83
87
|
line => line === null
|
|
84
|
-
?
|
|
88
|
+
? pureOk(undefined)
|
|
85
89
|
: handleLine(handler)(line),
|
|
86
90
|
)
|
|
87
91
|
|
|
88
92
|
/**
|
|
89
93
|
* @template {Operation} O
|
|
90
94
|
* @param {Step<O>} handler
|
|
91
|
-
* @returns {(line: string) => Effect<Read | Write | O, void>}
|
|
95
|
+
* @returns {(line: string) => Effect<Read | Write | O, void, IoChannel>}
|
|
92
96
|
*/
|
|
93
97
|
const handleLine = handler => line => {
|
|
94
98
|
const [t, value] = parse(line)
|
|
95
|
-
return
|
|
99
|
+
return resultStep(
|
|
96
100
|
t === 'error'
|
|
97
101
|
? writeResponse(parseErrorResponse)
|
|
102
|
+
// `step`, because a `Handle` answers `Effect<O, Response | null,
|
|
103
|
+
// never>` — it absorbs its own failures into the response body — so
|
|
104
|
+
// the continuation receives the response itself and there is no
|
|
105
|
+
// error branch left for this chain to consider.
|
|
98
106
|
: step(
|
|
99
107
|
handler(value),
|
|
100
108
|
resp => resp === null
|
|
101
|
-
?
|
|
102
|
-
:
|
|
109
|
+
? pureOk(undefined)
|
|
110
|
+
// `resultStep` from here down: each write's *failure* is
|
|
111
|
+
// what selects the next, smaller fallback, so these are the
|
|
112
|
+
// both-branches case rather than a chain to short-circuit.
|
|
113
|
+
: resultStep(
|
|
103
114
|
writeResponse(resp),
|
|
104
|
-
|
|
115
|
+
r2 => r2[0] === 'error'
|
|
105
116
|
// The real response didn't fit. Retry with a fixed, small
|
|
106
117
|
// internal-error body carrying `resp.id` — but a
|
|
107
118
|
// caller-controlled `id` (e.g. a very large string) can
|
|
@@ -109,17 +120,19 @@ const handleLine = handler => line => {
|
|
|
109
120
|
// that retry is bounded by one more: an `id: null`
|
|
110
121
|
// internal-error, whose fully-constant shape is the only
|
|
111
122
|
// line in this transport guaranteed to always encode.
|
|
112
|
-
?
|
|
123
|
+
? resultStep(
|
|
113
124
|
writeResponse(internalErrorResponse(resp.id)),
|
|
114
|
-
|
|
115
|
-
?
|
|
125
|
+
r3 => r3[0] === 'error'
|
|
126
|
+
? resultStep(
|
|
116
127
|
writeResponse(internalErrorResponse(null)),
|
|
117
|
-
() =>
|
|
128
|
+
() => pureOk(undefined),
|
|
118
129
|
)
|
|
119
|
-
:
|
|
130
|
+
: pureOk(undefined)
|
|
120
131
|
)
|
|
121
|
-
:
|
|
132
|
+
: pureOk(undefined)),
|
|
122
133
|
),
|
|
134
|
+
// The loop continues whatever the line's own handling answered: a
|
|
135
|
+
// transport that could not write one response still serves the next.
|
|
123
136
|
() => stdioTransport(handler),
|
|
124
137
|
)
|
|
125
138
|
}
|