@gjermundgaraba/effect-actions 0.20.0 → 0.21.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 +19 -41
- package/dist/Action.d.mts +140 -46
- package/dist/Action.mjs +87 -42
- package/dist/ActionCli.d.mts +69 -33
- package/dist/ActionCli.mjs +51 -32
- package/dist/ActionHttp.d.mts +69 -35
- package/dist/ActionHttp.mjs +75 -23
- package/dist/ActionMcp.d.mts +56 -22
- package/dist/ActionMcp.mjs +94 -69
- package/dist/ActionRpc.d.mts +222 -55
- package/dist/ActionRpc.mjs +299 -46
- package/dist/ActionToolkit.d.mts +22 -5
- package/dist/ActionToolkit.mjs +36 -12
- package/dist/Authentication.d.mts +42 -10
- package/dist/Authentication.mjs +37 -30
- package/dist/Event.d.mts +370 -0
- package/dist/Event.mjs +103 -0
- package/dist/Testing.mjs +1 -1
- package/dist/Webhook.d.mts +146 -0
- package/dist/Webhook.mjs +72 -0
- package/dist/authentication/provider.d.mts +35 -10
- package/dist/authentication/provider.mjs +1 -1
- package/dist/authentication/refusal.mjs +2 -2
- package/dist/cli/commands.d.mts +9 -5
- package/dist/cli/commands.mjs +84 -33
- package/dist/contract/ast.mjs +4 -1
- package/dist/contract/authorizer.d.mts +8 -0
- package/dist/contract/authorizer.mjs +20 -0
- package/dist/contract/call.d.mts +9 -6
- package/dist/contract/call.mjs +48 -6
- package/dist/contract/callback.d.mts +7 -0
- package/dist/contract/callback.mjs +27 -0
- package/dist/contract/caller.d.mts +13 -2
- package/dist/contract/caller.mjs +22 -3
- package/dist/contract/canonical.mjs +10 -0
- package/dist/contract/codec.d.mts +9 -0
- package/dist/contract/codec.mjs +9 -0
- package/dist/contract/codes.mjs +81 -0
- package/dist/contract/declared.mjs +25 -9
- package/dist/contract/digest.mjs +6 -0
- package/dist/contract/errors.d.mts +9 -3
- package/dist/contract/errors.mjs +1 -1
- package/dist/contract/feed.d.mts +127 -0
- package/dist/contract/feed.mjs +249 -0
- package/dist/contract/hub.d.mts +94 -0
- package/dist/contract/hub.mjs +133 -0
- package/dist/contract/idempotency.d.mts +47 -0
- package/dist/contract/idempotency.mjs +99 -0
- package/dist/contract/implementation.d.mts +73 -47
- package/dist/contract/implementation.mjs +92 -58
- package/dist/contract/input-check.d.mts +15 -0
- package/dist/contract/input-check.mjs +16 -0
- package/dist/contract/lease.mjs +28 -0
- package/dist/contract/listed.d.mts +4 -0
- package/dist/contract/listed.mjs +22 -0
- package/dist/contract/log.d.mts +41 -0
- package/dist/contract/log.mjs +144 -0
- package/dist/contract/names.mjs +14 -4
- package/dist/contract/occurrence.d.mts +29 -0
- package/dist/contract/occurrence.mjs +45 -0
- package/dist/contract/pages.d.mts +13 -0
- package/dist/contract/pages.mjs +39 -0
- package/dist/contract/progress.d.mts +26 -0
- package/dist/contract/progress.mjs +72 -0
- package/dist/contract/push.d.mts +20 -0
- package/dist/contract/push.mjs +96 -0
- package/dist/contract/receiving.d.mts +29 -0
- package/dist/contract/receiving.mjs +42 -0
- package/dist/contract/recent.mjs +12 -0
- package/dist/contract/reported.mjs +16 -0
- package/dist/contract/serving.mjs +63 -0
- package/dist/contract/status.mjs +14 -5
- package/dist/contract/stream.d.mts +34 -0
- package/dist/contract/stream.mjs +31 -0
- package/dist/contract/subscribe.d.mts +45 -0
- package/dist/contract/subscribe.mjs +50 -0
- package/dist/contract/transient.mjs +28 -0
- package/dist/contract/watch.d.mts +18 -0
- package/dist/contract/watch.mjs +105 -0
- package/dist/contract/webhooks.d.mts +42 -0
- package/dist/contract/webhooks.mjs +53 -0
- package/dist/http/client.d.mts +37 -12
- package/dist/http/client.mjs +51 -12
- package/dist/http/events.d.mts +56 -0
- package/dist/http/events.mjs +102 -0
- package/dist/http/placed.d.mts +36 -0
- package/dist/http/placed.mjs +71 -0
- package/dist/http/schema-errors.mjs +1 -1
- package/dist/http/streamed.d.mts +21 -0
- package/dist/http/streamed.mjs +94 -0
- package/dist/http/watch.mjs +50 -0
- package/dist/mcp/bridge.mjs +283 -0
- package/dist/mcp/client.d.mts +30 -97
- package/dist/mcp/client.mjs +59 -132
- package/dist/mcp/events.mjs +142 -0
- package/dist/mcp/progress.mjs +34 -0
- package/dist/mcp/protocol.mjs +39 -13
- package/dist/mcp/reply.d.mts +79 -0
- package/dist/mcp/reply.mjs +99 -0
- package/dist/mcp/request.d.mts +40 -0
- package/dist/mcp/request.mjs +51 -0
- package/dist/mcp/watch.mjs +115 -0
- package/dist/rpc/watch.mjs +39 -0
- package/dist/stdio/console.mjs +13 -5
- package/dist/testing/memory.d.mts +1 -1
- package/dist/testing/memory.mjs +3 -3
- package/dist/webhook/address.mjs +47 -0
- package/dist/webhook/attempt.mjs +65 -0
- package/dist/webhook/receiver.mjs +67 -0
- package/dist/webhook/signing.mjs +28 -0
- package/dist/webhook/stored.mjs +57 -0
- package/dist/webhook/subscriptions.mjs +342 -0
- package/docs/Action-failures.md +2 -2
- package/docs/Action-streams.md +115 -0
- package/docs/Action.md +58 -124
- package/docs/ActionCli-failures.md +1 -2
- package/docs/ActionCli.md +83 -88
- package/docs/ActionHttp-failures.md +3 -3
- package/docs/ActionHttp.md +90 -70
- package/docs/ActionMcp-failures.md +2 -1
- package/docs/ActionMcp.md +120 -92
- package/docs/ActionRpc-events.md +126 -0
- package/docs/ActionRpc-failures.md +1 -2
- package/docs/ActionRpc.md +108 -79
- package/docs/ActionToolkit-failures.md +1 -2
- package/docs/ActionToolkit.md +33 -90
- package/docs/Authentication-advanced.md +52 -60
- package/docs/Authentication-failures.md +5 -4
- package/docs/Authentication.md +23 -58
- package/docs/Event-failures.md +15 -0
- package/docs/Event.md +281 -0
- package/docs/README.md +51 -82
- package/docs/Testing-failures.md +1 -5
- package/docs/Testing.md +126 -86
- package/docs/Webhook-failures.md +8 -0
- package/docs/Webhook.md +319 -0
- package/docs/setup-failures.md +2 -3
- package/docs/setup.md +32 -52
- package/examples/README.md +12 -37
- package/examples/assistant/README.md +21 -26
- package/examples/assistant/src/agent.ts +66 -24
- package/examples/assistant/src/cli.ts +7 -4
- package/examples/assistant/src/commands.ts +9 -4
- package/examples/assistant/src/identity/Identity.ts +5 -4
- package/examples/assistant/src/identity/authorize.ts +13 -5
- package/examples/assistant/src/identity/handlers.ts +3 -2
- package/examples/assistant/src/identity/permissions.ts +9 -3
- package/examples/assistant/src/platform/tasks-file.ts +15 -11
- package/examples/assistant/src/scripted.ts +7 -4
- package/examples/assistant/src/tasks/Tasks.ts +20 -6
- package/examples/assistant/src/tasks/handlers.ts +9 -5
- package/examples/assistant/src/tasks/store.ts +38 -11
- package/examples/assistant/src/toolkit.ts +11 -7
- package/examples/assistant/test/commands.test.ts +53 -27
- package/examples/assistant/test/identity/identity.test.ts +27 -11
- package/examples/assistant/test/platform/tasks-file.test.ts +39 -23
- package/examples/assistant/test/tasks/tasks.test.ts +62 -34
- package/examples/assistant/test/toolkit.test.ts +104 -75
- package/examples/build-events/README.md +83 -0
- package/examples/build-events/src/Api.ts +27 -0
- package/examples/build-events/src/Rpc.ts +26 -0
- package/examples/build-events/src/builds/Builds.ts +108 -0
- package/examples/build-events/src/builds/events.ts +54 -0
- package/examples/build-events/src/builds/handlers.ts +58 -0
- package/examples/build-events/src/builds/log.ts +30 -0
- package/examples/build-events/src/builds/status.ts +28 -0
- package/examples/build-events/src/builds/store.ts +130 -0
- package/examples/build-events/src/cli.ts +18 -0
- package/examples/build-events/src/commands.ts +32 -0
- package/examples/build-events/src/deploys/Deploys.ts +44 -0
- package/examples/build-events/src/deploys/approvers.ts +55 -0
- package/examples/build-events/src/deploys/events.ts +32 -0
- package/examples/build-events/src/deploys/handlers.ts +38 -0
- package/examples/build-events/src/identity/Identity.ts +29 -0
- package/examples/build-events/src/identity/members.ts +7 -0
- package/examples/build-events/src/identity/verifier.ts +22 -0
- package/examples/build-events/src/notifier/Notices.ts +26 -0
- package/examples/build-events/src/notifier/routes.ts +71 -0
- package/examples/build-events/src/notifier.ts +48 -0
- package/examples/build-events/src/routes.ts +71 -0
- package/examples/build-events/src/server.ts +62 -0
- package/examples/build-events/test/builds/events.test.ts +68 -0
- package/examples/build-events/test/builds/log.test.ts +71 -0
- package/examples/build-events/test/builds/progress.test.ts +57 -0
- package/examples/build-events/test/builds/status.test.ts +42 -0
- package/examples/build-events/test/commands.test.ts +103 -0
- package/examples/build-events/test/deploys/events.test.ts +52 -0
- package/examples/build-events/test/http.test.ts +95 -0
- package/examples/build-events/test/notifier.test.ts +80 -0
- package/examples/build-events/test/routes.test.ts +96 -0
- package/examples/build-events/test/rpc.test.ts +84 -0
- package/examples/build-events/test/support/as.ts +8 -0
- package/examples/build-events/test/support/memory.ts +43 -0
- package/examples/build-events/test/webhooks.test.ts +143 -0
- package/examples/kitchen-sink/README.md +178 -166
- package/examples/kitchen-sink/src/Actions.ts +11 -3
- package/examples/kitchen-sink/src/Api.ts +12 -6
- package/examples/kitchen-sink/src/Rpc.ts +7 -5
- package/examples/kitchen-sink/src/admin/Admin.ts +21 -10
- package/examples/kitchen-sink/src/admin/authenticate.ts +8 -4
- package/examples/kitchen-sink/src/admin/authorize.ts +13 -5
- package/examples/kitchen-sink/src/admin/grants.ts +5 -3
- package/examples/kitchen-sink/src/admin/handlers.ts +9 -5
- package/examples/kitchen-sink/src/agent.ts +14 -6
- package/examples/kitchen-sink/src/assistant/Assistant.ts +21 -15
- package/examples/kitchen-sink/src/assistant/conversations.ts +14 -9
- package/examples/kitchen-sink/src/assistant/demo-model.ts +2 -2
- package/examples/kitchen-sink/src/assistant/handlers.ts +42 -22
- package/examples/kitchen-sink/src/attachments/Attachments.ts +6 -6
- package/examples/kitchen-sink/src/attachments/download.ts +7 -6
- package/examples/kitchen-sink/src/attachments/handlers.ts +9 -4
- package/examples/kitchen-sink/src/attachments/store.ts +25 -18
- package/examples/kitchen-sink/src/browser.ts +10 -4
- package/examples/kitchen-sink/src/cli.ts +28 -12
- package/examples/kitchen-sink/src/config.ts +1 -1
- package/examples/kitchen-sink/src/identity/Identity.ts +24 -10
- package/examples/kitchen-sink/src/identity/authenticate.ts +19 -11
- package/examples/kitchen-sink/src/identity/authorize.ts +14 -4
- package/examples/kitchen-sink/src/identity/directory.ts +16 -7
- package/examples/kitchen-sink/src/identity/handlers.ts +1 -0
- package/examples/kitchen-sink/src/identity/members.ts +7 -2
- package/examples/kitchen-sink/src/identity/quota.ts +23 -20
- package/examples/kitchen-sink/src/issues/Issues.ts +91 -15
- package/examples/kitchen-sink/src/issues/assistant.ts +4 -2
- package/examples/kitchen-sink/src/issues/events.ts +46 -0
- package/examples/kitchen-sink/src/issues/export.ts +35 -0
- package/examples/kitchen-sink/src/issues/guide.ts +4 -3
- package/examples/kitchen-sink/src/issues/handlers.ts +20 -7
- package/examples/kitchen-sink/src/issues/store.ts +156 -67
- package/examples/kitchen-sink/src/issues/write-limit.ts +44 -18
- package/examples/kitchen-sink/src/memory.ts +11 -2
- package/examples/kitchen-sink/src/ops.ts +10 -5
- package/examples/kitchen-sink/src/request-policy.ts +21 -9
- package/examples/kitchen-sink/src/routes.ts +67 -27
- package/examples/kitchen-sink/src/sdk.ts +22 -12
- package/examples/kitchen-sink/src/seed.ts +15 -9
- package/examples/kitchen-sink/src/server.ts +24 -10
- package/examples/kitchen-sink/src/stdio.ts +9 -3
- package/examples/kitchen-sink/src/workspaces/Workspaces.ts +10 -7
- package/examples/kitchen-sink/src/workspaces/handlers.ts +3 -2
- package/examples/kitchen-sink/src/workspaces/registry.ts +16 -10
- package/examples/kitchen-sink/src/workspaces/tenant.ts +12 -5
- package/examples/kitchen-sink/test/admin/admin.test.ts +38 -23
- package/examples/kitchen-sink/test/assistant/assistant.test.ts +471 -299
- package/examples/kitchen-sink/test/attachments/attachments.test.ts +135 -94
- package/examples/kitchen-sink/test/identity/identity.test.ts +110 -82
- package/examples/kitchen-sink/test/identity/session.test.ts +65 -43
- package/examples/kitchen-sink/test/issues/assistant.test.ts +50 -31
- package/examples/kitchen-sink/test/issues/events.test.ts +273 -0
- package/examples/kitchen-sink/test/issues/export.test.ts +120 -0
- package/examples/kitchen-sink/test/issues/issues.test.ts +105 -75
- package/examples/kitchen-sink/test/request-policy.test.ts +108 -47
- package/examples/kitchen-sink/test/routes.test.ts +102 -57
- package/examples/kitchen-sink/test/support/as.ts +9 -0
- package/examples/kitchen-sink/test/support/scripted.ts +6 -5
- package/examples/kitchen-sink/test/support/services.ts +17 -0
- package/examples/live-rpc/README.md +33 -35
- package/examples/live-rpc/src/Rpc.ts +9 -5
- package/examples/live-rpc/src/browser.ts +39 -11
- package/examples/live-rpc/src/chat/Chat.ts +30 -16
- package/examples/live-rpc/src/chat/authorize.ts +2 -1
- package/examples/live-rpc/src/chat/events.ts +74 -0
- package/examples/live-rpc/src/chat/handlers.ts +13 -5
- package/examples/live-rpc/src/chat/room.ts +9 -5
- package/examples/live-rpc/src/chat/store.ts +51 -14
- package/examples/live-rpc/src/client.ts +22 -5
- package/examples/live-rpc/src/identity/Identity.ts +3 -3
- package/examples/live-rpc/src/identity/verifier.ts +8 -4
- package/examples/live-rpc/src/page.ts +58 -12
- package/examples/live-rpc/src/routes.ts +14 -10
- package/examples/live-rpc/src/server.ts +15 -5
- package/examples/live-rpc/test/browser.test.ts +127 -50
- package/examples/live-rpc/test/chat/chat.test.ts +215 -23
- package/examples/live-rpc/test/routes.test.ts +130 -46
- package/examples/live-rpc/test/server.test.ts +94 -33
- package/examples/mcp-tools/README.md +24 -38
- package/examples/mcp-tools/src/platform/fake-farm.ts +63 -24
- package/examples/mcp-tools/src/platform/print-farm.ts +59 -29
- package/examples/mcp-tools/src/printers/Printers.ts +18 -7
- package/examples/mcp-tools/src/printers/farm.ts +15 -5
- package/examples/mcp-tools/src/printers/handlers.ts +12 -6
- package/examples/mcp-tools/src/routes.ts +8 -6
- package/examples/mcp-tools/src/server.ts +12 -5
- package/examples/mcp-tools/src/stdio.ts +3 -2
- package/examples/mcp-tools/test/printers/printers.test.ts +132 -89
- package/examples/mcp-tools/test/routes.test.ts +140 -81
- package/examples/notes-service/README.md +30 -45
- package/examples/notes-service/src/Api.ts +1 -0
- package/examples/notes-service/src/browser.ts +15 -14
- package/examples/notes-service/src/cli.ts +8 -3
- package/examples/notes-service/src/commands.ts +46 -20
- package/examples/notes-service/src/identity/Identity.ts +2 -2
- package/examples/notes-service/src/identity/authorize.ts +6 -2
- package/examples/notes-service/src/identity/handlers.ts +3 -2
- package/examples/notes-service/src/identity/users.ts +2 -1
- package/examples/notes-service/src/identity/verifier.ts +12 -6
- package/examples/notes-service/src/notes/Notes.ts +46 -22
- package/examples/notes-service/src/notes/handlers.ts +18 -9
- package/examples/notes-service/src/notes/store.ts +70 -43
- package/examples/notes-service/src/routes.ts +7 -5
- package/examples/notes-service/src/server.ts +12 -5
- package/examples/notes-service/src/status/Status.ts +1 -1
- package/examples/notes-service/src/status/handlers.ts +5 -3
- package/examples/notes-service/src/stdio.ts +7 -3
- package/examples/notes-service/src/worker.ts +6 -3
- package/examples/notes-service/test/commands.test.ts +164 -91
- package/examples/notes-service/test/notes/as.ts +3 -1
- package/examples/notes-service/test/notes/idempotency.test.ts +97 -67
- package/examples/notes-service/test/notes/in-process.test.ts +26 -15
- package/examples/notes-service/test/notes/notes.test.ts +49 -31
- package/examples/notes-service/test/routes.test.ts +106 -71
- package/examples/notes-service/test/worker.test.ts +21 -13
- package/examples/quickstart/README.md +2 -6
- package/examples/quickstart/src/Api.ts +1 -0
- package/examples/quickstart/src/client.ts +6 -3
- package/examples/quickstart/src/greeting/Greeting.ts +1 -1
- package/examples/quickstart/src/greeting/handlers.ts +5 -2
- package/examples/quickstart/src/routes.ts +3 -2
- package/examples/quickstart/src/server.ts +6 -2
- package/examples/quickstart/test/routes.test.ts +3 -2
- package/package.json +10 -4
package/dist/mcp/client.mjs
CHANGED
|
@@ -1,162 +1,89 @@
|
|
|
1
1
|
import { Anyone } from "../contract/caller.mjs";
|
|
2
2
|
import { Refusal } from "../contract/errors.mjs";
|
|
3
|
-
import { assertOnce } from "../contract/names.mjs";
|
|
4
3
|
import { projectedErrors } from "../contract/declared.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { assertOnce } from "../contract/names.mjs";
|
|
5
|
+
import { progressSink } from "../contract/progress.mjs";
|
|
6
|
+
import { elementOf } from "../contract/stream.mjs";
|
|
7
|
+
import { checked, checkedStream, sendingJson, watchInput } from "../contract/call.mjs";
|
|
8
|
+
import { split } from "../contract/listed.mjs";
|
|
9
|
+
import { watching } from "../contract/watch.mjs";
|
|
10
|
+
import { defaultPath, liftOf, unliftedFrom } from "./protocol.mjs";
|
|
11
|
+
import { NoReply, NoStructuredContent, ProtocolError, StatusError, UndeclaredError, bodyOf, callError, failWith } from "./reply.mjs";
|
|
12
|
+
import { request } from "./request.mjs";
|
|
13
|
+
import { transportOf } from "./watch.mjs";
|
|
14
|
+
import { Effect, Option, Schema, Stream } from "effect";
|
|
15
|
+
import { HttpClient } from "effect/http";
|
|
8
16
|
import { McpSchema } from "effect/ai";
|
|
9
|
-
import { HttpClient, HttpClientRequest } from "effect/http";
|
|
10
|
-
import { Sse } from "effect/encoding";
|
|
11
17
|
// src/mcp/client.ts
|
|
12
|
-
/** The routing headers and JSON-RPC body of the request `request` builds. */
|
|
13
|
-
const statelessRequest = (method, params) => {
|
|
14
|
-
const { protocolVersion } = httpProtocol;
|
|
15
|
-
const name = method === "resources/read" ? params["uri"] : params["name"];
|
|
16
|
-
return {
|
|
17
|
-
headers: {
|
|
18
|
-
accept: "application/json, text/event-stream",
|
|
19
|
-
"mcp-protocol-version": protocolVersion,
|
|
20
|
-
"mcp-method": method,
|
|
21
|
-
...Predicate.isString(name) ? { "mcp-name": name } : {}
|
|
22
|
-
},
|
|
23
|
-
body: {
|
|
24
|
-
jsonrpc: "2.0",
|
|
25
|
-
id: 1,
|
|
26
|
-
method,
|
|
27
|
-
params: {
|
|
28
|
-
...params,
|
|
29
|
-
_meta: {
|
|
30
|
-
"io.modelcontextprotocol/clientCapabilities": {},
|
|
31
|
-
"io.modelcontextprotocol/clientInfo": {
|
|
32
|
-
name: "effect-actions",
|
|
33
|
-
version: "0"
|
|
34
|
-
},
|
|
35
|
-
...params._meta,
|
|
36
|
-
"io.modelcontextprotocol/protocolVersion": protocolVersion
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
/** The endpoint answered with an HTTP status other than 200, and a body no declared error decodes. */
|
|
43
|
-
var StatusError = class extends Schema.TaggedError()("StatusError", {
|
|
44
|
-
status: Schema.Int,
|
|
45
|
-
body: Schema.String
|
|
46
|
-
}) {};
|
|
47
|
-
/**
|
|
48
|
-
* The endpoint answered with a JSON-RPC error, such as the native server's for an unknown tool.
|
|
49
|
-
*/
|
|
50
|
-
var ProtocolError = class extends Schema.TaggedError()("ProtocolError", {
|
|
51
|
-
code: Schema.Finite,
|
|
52
|
-
message: Schema.String
|
|
53
|
-
}) {};
|
|
54
|
-
/** The endpoint answered 200 without a JSON-RPC reply to read, as JSON or as an event stream. */
|
|
55
|
-
var NoReply = class extends Schema.TaggedError()("NoReply", { body: Schema.String }) {};
|
|
56
|
-
/** The tool returned an error whose text decodes as none of the action's declared errors. */
|
|
57
|
-
var UndeclaredError = class extends Schema.TaggedError()("UndeclaredError", { text: Schema.String }) {};
|
|
58
|
-
/** The tool returned a result without the structured content its action's success needs. */
|
|
59
|
-
var NoStructuredContent = class extends Schema.TaggedError()("NoStructuredContent", { body: Schema.String }) {};
|
|
60
|
-
const Reason = Schema.Union([
|
|
61
|
-
StatusError,
|
|
62
|
-
ProtocolError,
|
|
63
|
-
NoReply,
|
|
64
|
-
UndeclaredError,
|
|
65
|
-
NoStructuredContent
|
|
66
|
-
]);
|
|
67
|
-
const sentenceOf = Match.typeTags()({
|
|
68
|
-
StatusError: ({ status, body }) => `answered ${status}: ${body}`,
|
|
69
|
-
ProtocolError: ({ code, message }) => `failed with ${code}: ${message}`,
|
|
70
|
-
NoReply: ({ body }) => `had no reply: ${body}`,
|
|
71
|
-
UndeclaredError: ({ text }) => `returned an error: ${text}`,
|
|
72
|
-
NoStructuredContent: ({ body }) => `returned no structured content: ${body}`
|
|
73
|
-
});
|
|
74
|
-
/**
|
|
75
|
-
* An answer a client method cannot read as the action's success or a declared error: `reason`
|
|
76
|
-
* says which, with what the endpoint sent, so a caller branches on it with
|
|
77
|
-
* `Effect.catchReason("McpCallError", "StatusError", ...)`. `message` names the tool and the
|
|
78
|
-
* reason. Structured content that does not decode as the success is a `SchemaError` instead,
|
|
79
|
-
* and a request that fails an `HttpClientError`.
|
|
80
|
-
*
|
|
81
|
-
* **Gotchas**
|
|
82
|
-
*
|
|
83
|
-
* - `returned an error: Invalid parameters for tool …`, an `UndeclaredError`, from a call its types allowed: the endpoint serves another contract under that name; give `client` the served contract value.
|
|
84
|
-
*/
|
|
85
|
-
var McpCallError = class extends Schema.TaggedError()("McpCallError", {
|
|
86
|
-
tool: Schema.String,
|
|
87
|
-
reason: Reason,
|
|
88
|
-
message: Schema.String
|
|
89
|
-
}) {};
|
|
90
|
-
const callError = (tool, reason) => new McpCallError({
|
|
91
|
-
tool,
|
|
92
|
-
reason,
|
|
93
|
-
message: `MCP tools/call "${tool}" ${sentenceOf(reason)}`
|
|
94
|
-
});
|
|
95
18
|
const textOf = (block) => block?.type === "text" ? block.text : void 0;
|
|
96
19
|
const ToolCallReply = Schema.Union([Schema.Struct({ result: McpSchema.CallToolResult }), Schema.Struct({ error: Schema.Struct({
|
|
97
20
|
code: Schema.Finite,
|
|
98
21
|
message: Schema.String
|
|
99
22
|
}) })]);
|
|
100
23
|
const decodeReply = Schema.decodeUnknownOption(Schema.fromJsonString(ToolCallReply));
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
24
|
+
/** `onProgress` given each `notifications/progress` value decoded, one that does not logged and dropped. */
|
|
25
|
+
const decodedSink = (progress, onProgress) => {
|
|
26
|
+
const sink = progressSink(onProgress);
|
|
27
|
+
if (progress === void 0 || sink === void 0) return;
|
|
28
|
+
const decode = Schema.decodeUnknownEffect(Schema.toCodecJson(progress));
|
|
29
|
+
return { progress: (json) => Effect.matchEffect(decode(json), {
|
|
30
|
+
onFailure: (error) => Effect.logWarning("A progress value did not decode", error),
|
|
31
|
+
onSuccess: sink.progress
|
|
32
|
+
}) };
|
|
108
33
|
};
|
|
109
|
-
const
|
|
110
|
-
const callTool = (client, url, action, lift, refusedWith, input) => {
|
|
34
|
+
const callTool = (client, url, action, success, refusedWith, input, sink) => {
|
|
111
35
|
const { name } = action;
|
|
36
|
+
const lift = liftOf(success);
|
|
37
|
+
const method = "tools/call";
|
|
112
38
|
return Effect.gen(function* () {
|
|
113
|
-
const
|
|
114
|
-
const response = yield* client.execute(request("tools/call", {
|
|
39
|
+
const response = yield* client.execute(request(method, {
|
|
115
40
|
name,
|
|
116
|
-
arguments:
|
|
41
|
+
arguments: input,
|
|
42
|
+
...sink === void 0 ? {} : { _meta: { progressToken: "progress" } }
|
|
117
43
|
}, { url }));
|
|
118
|
-
const
|
|
119
|
-
|
|
44
|
+
const messages = yield* bodyOf(method, name, response, sink);
|
|
45
|
+
const text = messages.join("\n");
|
|
46
|
+
if (response.status !== 200) return yield* failWith(refusedWith, text, callError(method, name, new StatusError({
|
|
120
47
|
status: response.status,
|
|
121
48
|
body: text
|
|
122
49
|
})));
|
|
123
|
-
const reply =
|
|
124
|
-
if (Option.isNone(reply)) return yield*
|
|
125
|
-
if ("error" in reply.value) return yield*
|
|
50
|
+
const reply = Option.fromNullishOr(messages.flatMap((message) => Option.toArray(decodeReply(message))).at(-1));
|
|
51
|
+
if (Option.isNone(reply)) return yield* callError(method, name, new NoReply({ body: text }));
|
|
52
|
+
if ("error" in reply.value) return yield* callError(method, name, new ProtocolError(reply.value.error));
|
|
126
53
|
const { result } = reply.value;
|
|
127
54
|
if (result.isError === true) {
|
|
128
55
|
const error = textOf(result.content.find((block) => block.type === "text")) ?? "";
|
|
129
|
-
return yield* failWith(projectedErrors(action), error, callError(name, new UndeclaredError({ text: error })));
|
|
56
|
+
return yield* failWith(projectedErrors(action), error, callError(method, name, new UndeclaredError({ text: error })));
|
|
130
57
|
}
|
|
131
|
-
if (result.structuredContent === void 0 && lift.rest !== void 0) return yield*
|
|
132
|
-
return yield* Schema.decodeUnknownEffect(Schema.toCodecJson(
|
|
58
|
+
if (result.structuredContent === void 0 && lift.rest !== void 0) return yield* callError(method, name, new NoStructuredContent({ body: text }));
|
|
59
|
+
return yield* Schema.decodeUnknownEffect(Schema.toCodecJson(success))(unliftedFrom(lift, result));
|
|
133
60
|
});
|
|
134
61
|
};
|
|
135
|
-
function client(
|
|
62
|
+
function client(contracts, { url = defaultPath, transformClient, authentication } = {}) {
|
|
63
|
+
const { actions, events } = split(contracts);
|
|
136
64
|
assertOnce("action", actions);
|
|
65
|
+
assertOnce("event", events);
|
|
66
|
+
const refusedWith = (contract) => [...Refusal.members, ...contract.caller === Anyone ? [] : authentication?.error ?? []];
|
|
137
67
|
return Effect.map(HttpClient.HttpClient, (native) => {
|
|
138
68
|
const http = transformClient === void 0 ? native : transformClient(native);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
69
|
+
const watch = (event, input, options) => Stream.unwrap(Effect.map(watchInput(event, input), ({ json }) => watching(transportOf({
|
|
70
|
+
http,
|
|
71
|
+
url,
|
|
72
|
+
event,
|
|
73
|
+
refusedWith: refusedWith(event)
|
|
74
|
+
}, json), options)));
|
|
75
|
+
return {
|
|
76
|
+
...Object.fromEntries(actions.map((action) => {
|
|
77
|
+
const call = (success, input, onProgress) => callTool(http, url, action, success, refusedWith(action), input, decodedSink(action.progress, onProgress));
|
|
78
|
+
const element = elementOf(action.success);
|
|
79
|
+
if (element !== void 0) {
|
|
80
|
+
const elements = Schema.Array(element);
|
|
81
|
+
return [action.name, checkedStream(action, sendingJson, (input, onProgress) => Stream.fromIterableEffect(call(elements, input, onProgress)))];
|
|
82
|
+
}
|
|
83
|
+
return [action.name, checked(action, sendingJson, (input, onProgress) => call(action.success, input, onProgress))];
|
|
84
|
+
})),
|
|
85
|
+
...events.length === 0 ? {} : { watch }
|
|
86
|
+
};
|
|
144
87
|
});
|
|
145
88
|
}
|
|
146
|
-
|
|
147
|
-
* One stateless MCP request of `method` with `params`, as `layerHttp` serves it,
|
|
148
|
-
* as the native request value: the JSON-RPC envelope, the 2026-07-28 headers (`Accept`,
|
|
149
|
-
* `MCP-Protocol-Version`, `MCP-Method`), `mcp-name` from
|
|
150
|
-
* `params.uri` for `resources/read` and `params.name` otherwise, and the client metadata in
|
|
151
|
-
* `_meta` are filled in, under any `_meta` given, such as a `progressToken`; the protocol
|
|
152
|
-
* version is always the request's own. The test sends it: `HttpClient.execute` answers the
|
|
153
|
-
* response as the endpoint sent it, whatever its status, and `HttpClientRequest.toWebResult`
|
|
154
|
-
* gives the web `Request` a web handler or `fetch` takes, once `url` is absolute. It is for a test
|
|
155
|
-
* asserting on what `client` decodes away, such as `tools/list`, a refusal's challenge, or
|
|
156
|
-
* a call its types would not send.
|
|
157
|
-
*/
|
|
158
|
-
const request = (method, params = {}, { headers = {}, url = defaultPath } = {}) => {
|
|
159
|
-
const { headers: routingHeaders, body } = statelessRequest(method, params);
|
|
160
|
-
return HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.setHeaders(routingHeaders), HttpClientRequest.bodyJsonUnsafe(body));
|
|
161
|
-
};
|
|
162
|
-
export { McpCallError, client, request, statelessRequest };
|
|
89
|
+
export { client };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { eventCodes } from "../contract/codes.mjs";
|
|
2
|
+
import { Subscribed, WebhookCallback, WebhookTarget, webhookFields } from "../contract/subscribe.mjs";
|
|
3
|
+
import { ErrorReporter, Schema, Struct } from "effect";
|
|
4
|
+
import { McpSchema } from "effect/ai";
|
|
5
|
+
import { Rpc, RpcGroup } from "effect/rpc";
|
|
6
|
+
// src/mcp/events.ts
|
|
7
|
+
/** The extension's identifier, its key in `capabilities.extensions`. */
|
|
8
|
+
const extension = "io.modelcontextprotocol/events";
|
|
9
|
+
/** The `_meta` key a push stream's notifications carry its request id under. */
|
|
10
|
+
const subscriptionIdKey = "io.modelcontextprotocol/subscriptionId";
|
|
11
|
+
const codes = {
|
|
12
|
+
headerMismatch: -32020,
|
|
13
|
+
...eventCodes,
|
|
14
|
+
invalidParams: -32602,
|
|
15
|
+
internal: -32603
|
|
16
|
+
};
|
|
17
|
+
const Meta = Schema.optional(Schema.Record(Schema.String, Schema.Json));
|
|
18
|
+
const Arguments = Schema.optional(Schema.Record(Schema.String, Schema.Json));
|
|
19
|
+
const Cursor = Schema.optional(Schema.NullOr(Schema.String));
|
|
20
|
+
/** A JSON-RPC error as an `events/*` request fails with, and a termination carries. */
|
|
21
|
+
const WireError = Schema.Struct({
|
|
22
|
+
code: Schema.Int,
|
|
23
|
+
message: Schema.String,
|
|
24
|
+
data: Schema.optional(Schema.Json)
|
|
25
|
+
});
|
|
26
|
+
/** What an `events/*` handler fails with: the JSON-RPC error it answers. */
|
|
27
|
+
var EventsError = class extends Schema.Error("effect-actions/McpEventsError")(WireError.fields) {
|
|
28
|
+
[ErrorReporter.ignore] = true;
|
|
29
|
+
};
|
|
30
|
+
/** One event type as `events/list` lists it. */
|
|
31
|
+
const EventDescriptor = Schema.Struct({
|
|
32
|
+
name: Schema.String,
|
|
33
|
+
description: Schema.String,
|
|
34
|
+
delivery: Schema.Array(Schema.Literals([
|
|
35
|
+
"poll",
|
|
36
|
+
"push",
|
|
37
|
+
"webhook"
|
|
38
|
+
])),
|
|
39
|
+
inputSchema: McpSchema.ToolJson,
|
|
40
|
+
payloadSchema: McpSchema.ToolOutputJson
|
|
41
|
+
});
|
|
42
|
+
/** An occurrence on the wire: in a poll's `events`, and the params of an event notification. */
|
|
43
|
+
const WireOccurrence = Schema.Struct({
|
|
44
|
+
eventId: Schema.String,
|
|
45
|
+
name: Schema.String,
|
|
46
|
+
timestamp: Schema.String,
|
|
47
|
+
data: Schema.Json,
|
|
48
|
+
cursor: Cursor
|
|
49
|
+
});
|
|
50
|
+
const subscription = {
|
|
51
|
+
_meta: Meta,
|
|
52
|
+
name: Schema.String,
|
|
53
|
+
arguments: Arguments,
|
|
54
|
+
cursor: Cursor,
|
|
55
|
+
maxAgeMs: Schema.optional(Schema.Finite)
|
|
56
|
+
};
|
|
57
|
+
var ListEvents = class extends Rpc.make("events/list", {
|
|
58
|
+
payload: {
|
|
59
|
+
_meta: Meta,
|
|
60
|
+
cursor: Schema.optional(Schema.String)
|
|
61
|
+
},
|
|
62
|
+
success: Schema.Struct({
|
|
63
|
+
resultType: Schema.Literal("complete"),
|
|
64
|
+
events: Schema.Array(EventDescriptor),
|
|
65
|
+
_meta: Meta
|
|
66
|
+
}),
|
|
67
|
+
error: EventsError
|
|
68
|
+
}) {};
|
|
69
|
+
var PollEvents = class extends Rpc.make("events/poll", {
|
|
70
|
+
payload: {
|
|
71
|
+
...subscription,
|
|
72
|
+
maxEvents: Schema.optional(Schema.Finite)
|
|
73
|
+
},
|
|
74
|
+
success: Schema.Struct({
|
|
75
|
+
resultType: Schema.Literal("complete"),
|
|
76
|
+
events: Schema.Array(WireOccurrence),
|
|
77
|
+
cursor: Cursor,
|
|
78
|
+
truncated: Schema.optional(Schema.Boolean),
|
|
79
|
+
hasMore: Schema.optional(Schema.Boolean),
|
|
80
|
+
nextPollMs: Schema.optional(Schema.Finite),
|
|
81
|
+
_meta: Meta
|
|
82
|
+
}),
|
|
83
|
+
error: EventsError
|
|
84
|
+
}) {};
|
|
85
|
+
var StreamEvents = class extends Rpc.make("events/stream", {
|
|
86
|
+
payload: subscription,
|
|
87
|
+
success: Schema.Struct({
|
|
88
|
+
resultType: Schema.Literal("complete"),
|
|
89
|
+
_meta: Meta
|
|
90
|
+
}),
|
|
91
|
+
error: EventsError
|
|
92
|
+
}) {};
|
|
93
|
+
var SubscribeEvents = class extends Rpc.make("events/subscribe", {
|
|
94
|
+
payload: {
|
|
95
|
+
...subscription,
|
|
96
|
+
delivery: WebhookCallback.mapFields(Struct.assign({ mode: Schema.Literal("webhook") })),
|
|
97
|
+
ttlMs: webhookFields.ttlMs
|
|
98
|
+
},
|
|
99
|
+
success: Schema.Struct({
|
|
100
|
+
resultType: Schema.Literal("complete"),
|
|
101
|
+
...Subscribed.fields,
|
|
102
|
+
_meta: Meta
|
|
103
|
+
}),
|
|
104
|
+
error: EventsError
|
|
105
|
+
}) {};
|
|
106
|
+
var UnsubscribeEvents = class extends Rpc.make("events/unsubscribe", {
|
|
107
|
+
payload: {
|
|
108
|
+
_meta: Meta,
|
|
109
|
+
name: Schema.String,
|
|
110
|
+
arguments: Arguments,
|
|
111
|
+
delivery: WebhookTarget
|
|
112
|
+
},
|
|
113
|
+
success: Schema.Struct({
|
|
114
|
+
resultType: Schema.Literal("complete"),
|
|
115
|
+
_meta: Meta
|
|
116
|
+
}),
|
|
117
|
+
error: EventsError
|
|
118
|
+
}) {};
|
|
119
|
+
const Active = Rpc.make("notifications/events/active", { payload: {
|
|
120
|
+
_meta: Meta,
|
|
121
|
+
cursor: Cursor,
|
|
122
|
+
truncated: Schema.optional(Schema.Boolean)
|
|
123
|
+
} });
|
|
124
|
+
const Occurred = Rpc.make("notifications/events/event", { payload: {
|
|
125
|
+
...WireOccurrence.fields,
|
|
126
|
+
_meta: Meta
|
|
127
|
+
} });
|
|
128
|
+
const Heartbeat = Rpc.make("notifications/events/heartbeat", { payload: {
|
|
129
|
+
_meta: Meta,
|
|
130
|
+
cursor: Cursor
|
|
131
|
+
} });
|
|
132
|
+
const Failed = Rpc.make("notifications/events/error", { payload: {
|
|
133
|
+
_meta: Meta,
|
|
134
|
+
error: WireError
|
|
135
|
+
} });
|
|
136
|
+
const Terminated = Rpc.make("notifications/events/terminated", { payload: {
|
|
137
|
+
_meta: Meta,
|
|
138
|
+
error: WireError
|
|
139
|
+
} });
|
|
140
|
+
const Requests = RpcGroup.make(ListEvents, PollEvents, StreamEvents, SubscribeEvents, UnsubscribeEvents);
|
|
141
|
+
const Notifications = RpcGroup.make(Active, Occurred, Heartbeat, Failed, Terminated);
|
|
142
|
+
export { Active, EventDescriptor, EventsError, Failed, Heartbeat, ListEvents, Notifications, Occurred, PollEvents, Requests, StreamEvents, SubscribeEvents, Terminated, UnsubscribeEvents, WireError, WireOccurrence, codes, extension, subscriptionIdKey };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
2
|
+
import { McpSchema } from "effect/ai";
|
|
3
|
+
// src/mcp/progress.ts
|
|
4
|
+
/** The `_meta` key a `notifications/progress` carries its value under, as JSON. */
|
|
5
|
+
const progressKey = "effect-actions/progress";
|
|
6
|
+
const tokenOf = Schema.decodeUnknownOption(McpSchema.ProgressToken);
|
|
7
|
+
/**
|
|
8
|
+
* Where a tool call's progress goes: a `notifications/progress` for each value, on the
|
|
9
|
+
* `progressToken` its request carried, `progress` counting from 1, `message` the value's JSON and
|
|
10
|
+
* `_meta` the value; none for a request without a token, so the values are checked and dropped.
|
|
11
|
+
*/
|
|
12
|
+
const progressNotifier = (notifications, progress) => Effect.map(Effect.service(McpSchema.McpRequestContext), ({ requestMetadata }) => {
|
|
13
|
+
const token = tokenOf(requestMetadata?.progressToken);
|
|
14
|
+
if (Option.isNone(token)) return;
|
|
15
|
+
const encode = Schema.encodeUnknownEffect(Schema.toCodecJson(progress));
|
|
16
|
+
let sent = 0;
|
|
17
|
+
return { progress: (value) => Effect.flatMap(Effect.orDie(encode(value)), (json) => {
|
|
18
|
+
sent += 1;
|
|
19
|
+
return notifications["notifications/progress"]({
|
|
20
|
+
progressToken: token.value,
|
|
21
|
+
progress: sent,
|
|
22
|
+
message: JSON.stringify(json),
|
|
23
|
+
_meta: { [progressKey]: json }
|
|
24
|
+
});
|
|
25
|
+
}) };
|
|
26
|
+
});
|
|
27
|
+
const Notified = Schema.fromJsonString(Schema.Struct({
|
|
28
|
+
method: Schema.Literal("notifications/progress"),
|
|
29
|
+
params: Schema.Struct({ _meta: Schema.Struct({ [progressKey]: Schema.Json }) })
|
|
30
|
+
}));
|
|
31
|
+
const notifiedOf = Schema.decodeUnknownOption(Notified);
|
|
32
|
+
/** The value a frame's `notifications/progress` carries, or none for another frame. */
|
|
33
|
+
const progressOf = (data) => Option.map(notifiedOf(data), (notified) => notified.params._meta[progressKey]);
|
|
34
|
+
export { progressKey, progressNotifier, progressOf };
|
package/dist/mcp/protocol.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { unsuspended } from "../contract/ast.mjs";
|
|
2
2
|
import { Media, isMarked, kindOf } from "../contract/media.mjs";
|
|
3
|
-
import { Predicate, Schema, SchemaAST } from "effect";
|
|
4
|
-
import { McpProtocol } from "effect/ai";
|
|
3
|
+
import { JsonPointer, Predicate, Schema, SchemaAST } from "effect";
|
|
5
4
|
import { Base64 } from "effect/encoding";
|
|
5
|
+
import { McpProtocol, McpSchema } from "effect/ai";
|
|
6
6
|
// src/mcp/protocol.ts
|
|
7
7
|
const httpProtocol = McpProtocol.v2026_07_28;
|
|
8
8
|
const protocols = [
|
|
@@ -11,6 +11,31 @@ const protocols = [
|
|
|
11
11
|
McpProtocol.v2025_06_18
|
|
12
12
|
];
|
|
13
13
|
const defaultPath = "/mcp";
|
|
14
|
+
const isObjectSchema = Schema.is(McpSchema.ToolJson);
|
|
15
|
+
const isJson = Schema.is(Schema.Json);
|
|
16
|
+
const definitionsReferenced = (value) => {
|
|
17
|
+
if (value === null || Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) return [];
|
|
18
|
+
if (Array.isArray(value)) return value.flatMap(definitionsReferenced);
|
|
19
|
+
return Object.entries(value).flatMap(([field, nested]) => {
|
|
20
|
+
if (field !== "$ref") return definitionsReferenced(nested);
|
|
21
|
+
const [scope, key] = Predicate.isString(nested) ? JsonPointer.parseUriFragment(nested) ?? [] : [];
|
|
22
|
+
return scope === "$defs" && key !== void 0 ? [key] : [];
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const toolJsonSchema = (schema, strict) => {
|
|
26
|
+
const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: strict ? "error" : "ignore" });
|
|
27
|
+
const [scope, key] = Predicate.isString(document.schema["$ref"]) ? JsonPointer.parseUriFragment(document.schema["$ref"]) ?? [] : [];
|
|
28
|
+
const inlined = scope === "$defs" && key !== void 0 ? key : void 0;
|
|
29
|
+
const root = inlined === void 0 ? document.schema : document.definitions[inlined];
|
|
30
|
+
if (root === void 0) return;
|
|
31
|
+
const definitions = inlined !== void 0 && ![root, ...Object.values(document.definitions)].flatMap((definition) => isJson(definition) ? definitionsReferenced(definition) : []).includes(inlined) ? Object.fromEntries(Object.entries(document.definitions).filter(([name]) => name !== inlined)) : document.definitions;
|
|
32
|
+
return Object.keys(definitions).length === 0 ? root : {
|
|
33
|
+
...root,
|
|
34
|
+
$defs: definitions
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/** The forms an MCP routing header may carry `name` in: as sent, or base64-encoded in its sentinel. */
|
|
38
|
+
const routingFormsOf = (name) => [name, `=?base64?${Base64.encode(name)}?=`];
|
|
14
39
|
const fieldKey = "effect-actions/field";
|
|
15
40
|
const isMedia = Schema.is(Media);
|
|
16
41
|
const isMediaArray = Schema.is(Schema.Array(Media));
|
|
@@ -22,7 +47,7 @@ const wholeSuccessMediaOf = (suspended) => {
|
|
|
22
47
|
kind: one,
|
|
23
48
|
many: false
|
|
24
49
|
};
|
|
25
|
-
if (!SchemaAST.isArrays(ast) || ast.encoding !== void 0) return
|
|
50
|
+
if (!SchemaAST.isArrays(ast) || ast.encoding !== void 0) return;
|
|
26
51
|
const [kind, ...others] = new Set([...ast.elements, ...ast.rest].map(kindOfUnsuspended));
|
|
27
52
|
return kind === void 0 || others.length > 0 ? void 0 : {
|
|
28
53
|
kind,
|
|
@@ -95,16 +120,17 @@ const holdsMediaAnywhere = (ast) => {
|
|
|
95
120
|
}
|
|
96
121
|
return false;
|
|
97
122
|
};
|
|
98
|
-
const isMisplaced = (action, { rest }) =>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
].some(({ ast }) => holdsMediaAnywhere(ast));
|
|
104
|
-
};
|
|
123
|
+
const isMisplaced = (action, { rest }) => [
|
|
124
|
+
action.input,
|
|
125
|
+
...action.error,
|
|
126
|
+
...rest === void 0 ? [] : [rest]
|
|
127
|
+
].some(({ ast }) => holdsMediaAnywhere(ast));
|
|
105
128
|
const liftedFrom = (lift, success) => lift.fields.flatMap(({ name, kind, many }) => {
|
|
106
|
-
const
|
|
107
|
-
|
|
129
|
+
const field = name !== void 0 && Predicate.hasProperty(success, name) ? success[name] : void 0;
|
|
130
|
+
const value = name === void 0 ? success : field;
|
|
131
|
+
const listed = isMediaArray(value) ? value : [];
|
|
132
|
+
const single = isMedia(value) ? [value] : [];
|
|
133
|
+
return (many ? listed : single).map((media) => ({
|
|
108
134
|
kind,
|
|
109
135
|
field: name,
|
|
110
136
|
value: media
|
|
@@ -133,4 +159,4 @@ const unliftedFrom = (lift, result) => {
|
|
|
133
159
|
return field.name === void 0 || value === void 0 ? [] : [[field.name, value]];
|
|
134
160
|
})]);
|
|
135
161
|
};
|
|
136
|
-
export { defaultPath, fieldKey, httpProtocol, isMisplaced, liftOf, liftedFrom, protocols, unliftedFrom };
|
|
162
|
+
export { defaultPath, fieldKey, httpProtocol, isMisplaced, isObjectSchema, liftOf, liftedFrom, protocols, routingFormsOf, toolJsonSchema, unliftedFrom };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import "../contract/progress.mjs";
|
|
2
|
+
import "../Action.mjs";
|
|
3
|
+
import { Effect, Schema, Stream } from "effect";
|
|
4
|
+
import { HttpClientError } from "effect/http";
|
|
5
|
+
declare const StatusError_base: Schema.Class<StatusError, Schema.TaggedStruct<"StatusError", {
|
|
6
|
+
readonly status: Schema.Int;
|
|
7
|
+
readonly body: Schema.String;
|
|
8
|
+
}>, import("effect/Cause").YieldableError & {
|
|
9
|
+
readonly brand: unique symbol;
|
|
10
|
+
}>;
|
|
11
|
+
/** The endpoint answered with an HTTP status other than 200, and a body no declared error decodes. */
|
|
12
|
+
export declare class StatusError extends StatusError_base {}
|
|
13
|
+
declare const ProtocolError_base: Schema.Class<ProtocolError, Schema.TaggedStruct<"ProtocolError", {
|
|
14
|
+
readonly code: Schema.Finite;
|
|
15
|
+
readonly message: Schema.String;
|
|
16
|
+
}>, import("effect/Cause").YieldableError & {
|
|
17
|
+
readonly brand: unique symbol;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* The endpoint answered with a JSON-RPC error, such as the native server's for an unknown tool.
|
|
21
|
+
*/
|
|
22
|
+
export declare class ProtocolError extends ProtocolError_base {}
|
|
23
|
+
declare const NoReply_base: Schema.Class<NoReply, Schema.TaggedStruct<"NoReply", {
|
|
24
|
+
readonly body: Schema.String;
|
|
25
|
+
}>, import("effect/Cause").YieldableError & {
|
|
26
|
+
readonly brand: unique symbol;
|
|
27
|
+
}>;
|
|
28
|
+
/** The endpoint answered 200 without a JSON-RPC reply to read, as JSON or as an event stream. */
|
|
29
|
+
export declare class NoReply extends NoReply_base {}
|
|
30
|
+
declare const UndeclaredError_base: Schema.Class<UndeclaredError, Schema.TaggedStruct<"UndeclaredError", {
|
|
31
|
+
readonly text: Schema.String;
|
|
32
|
+
}>, import("effect/Cause").YieldableError & {
|
|
33
|
+
readonly brand: unique symbol;
|
|
34
|
+
}>;
|
|
35
|
+
/** The tool returned an error whose text decodes as none of the action's declared errors. */
|
|
36
|
+
export declare class UndeclaredError extends UndeclaredError_base {}
|
|
37
|
+
declare const NoStructuredContent_base: Schema.Class<NoStructuredContent, Schema.TaggedStruct<"NoStructuredContent", {
|
|
38
|
+
readonly body: Schema.String;
|
|
39
|
+
}>, import("effect/Cause").YieldableError & {
|
|
40
|
+
readonly brand: unique symbol;
|
|
41
|
+
}>;
|
|
42
|
+
/** The tool returned a result without the structured content its action's success needs. */
|
|
43
|
+
export declare class NoStructuredContent extends NoStructuredContent_base {}
|
|
44
|
+
declare const FrameTooLarge_base: Schema.Class<FrameTooLarge, Schema.TaggedStruct<"FrameTooLarge", {
|
|
45
|
+
readonly maxEventSize: Schema.Int;
|
|
46
|
+
}>, import("effect/Cause").YieldableError & {
|
|
47
|
+
readonly brand: unique symbol;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* The endpoint's event stream held more than `maxEventSize` characters with no frame ending, the
|
|
51
|
+
* most a client holds while it waits for one.
|
|
52
|
+
*
|
|
53
|
+
* **Gotchas**
|
|
54
|
+
*
|
|
55
|
+
* - The limit is on what is held unended, as Effect's SSE parser checks it after each chunk: a frame larger than it that ends within the chunk carrying it is read.
|
|
56
|
+
*/
|
|
57
|
+
export declare class FrameTooLarge extends FrameTooLarge_base {}
|
|
58
|
+
declare const McpCallError_base: Schema.Class<McpCallError, Schema.TaggedStruct<"McpCallError", {
|
|
59
|
+
readonly method: Schema.String;
|
|
60
|
+
readonly target: Schema.String;
|
|
61
|
+
readonly reason: Schema.Union<readonly [typeof StatusError, typeof ProtocolError, typeof NoReply, typeof UndeclaredError, typeof NoStructuredContent, typeof FrameTooLarge]>;
|
|
62
|
+
readonly message: Schema.String;
|
|
63
|
+
}>, import("effect/Cause").YieldableError & {
|
|
64
|
+
readonly brand: unique symbol;
|
|
65
|
+
}>;
|
|
66
|
+
/**
|
|
67
|
+
* An answer a client cannot read as the contract's: `method` is the request, `tools/call` or an
|
|
68
|
+
* `events/*` one, `target` the tool or event it named, and `reason` says what was wrong, with what
|
|
69
|
+
* the endpoint sent, so a caller branches on it with
|
|
70
|
+
* `Effect.catchReason("McpCallError", "StatusError", ...)`. `message` names all three.
|
|
71
|
+
* Structured content that does not decode as the success is a `SchemaError` instead, and a
|
|
72
|
+
* request that fails an `HttpClientError`.
|
|
73
|
+
*
|
|
74
|
+
* **Gotchas**
|
|
75
|
+
*
|
|
76
|
+
* - `returned an error: Invalid parameters for tool …`, an `UndeclaredError`, from a call its types allowed: the endpoint serves another contract under that name; give `client` the served contract value.
|
|
77
|
+
*/
|
|
78
|
+
export declare class McpCallError extends McpCallError_base {}
|
|
79
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { progressOf } from "./progress.mjs";
|
|
2
|
+
import { Effect, Match, Option, Predicate, Schema, Stream } from "effect";
|
|
3
|
+
import { Sse } from "effect/encoding";
|
|
4
|
+
// src/mcp/reply.ts
|
|
5
|
+
/** The endpoint answered with an HTTP status other than 200, and a body no declared error decodes. */
|
|
6
|
+
var StatusError = class extends Schema.TaggedError()("StatusError", {
|
|
7
|
+
status: Schema.Int,
|
|
8
|
+
body: Schema.String
|
|
9
|
+
}) {};
|
|
10
|
+
/**
|
|
11
|
+
* The endpoint answered with a JSON-RPC error, such as the native server's for an unknown tool.
|
|
12
|
+
*/
|
|
13
|
+
var ProtocolError = class extends Schema.TaggedError()("ProtocolError", {
|
|
14
|
+
code: Schema.Finite,
|
|
15
|
+
message: Schema.String
|
|
16
|
+
}) {};
|
|
17
|
+
/** The endpoint answered 200 without a JSON-RPC reply to read, as JSON or as an event stream. */
|
|
18
|
+
var NoReply = class extends Schema.TaggedError()("NoReply", { body: Schema.String }) {};
|
|
19
|
+
/** The tool returned an error whose text decodes as none of the action's declared errors. */
|
|
20
|
+
var UndeclaredError = class extends Schema.TaggedError()("UndeclaredError", { text: Schema.String }) {};
|
|
21
|
+
/** The tool returned a result without the structured content its action's success needs. */
|
|
22
|
+
var NoStructuredContent = class extends Schema.TaggedError()("NoStructuredContent", { body: Schema.String }) {};
|
|
23
|
+
/**
|
|
24
|
+
* The endpoint's event stream held more than `maxEventSize` characters with no frame ending, the
|
|
25
|
+
* most a client holds while it waits for one.
|
|
26
|
+
*
|
|
27
|
+
* **Gotchas**
|
|
28
|
+
*
|
|
29
|
+
* - The limit is on what is held unended, as Effect's SSE parser checks it after each chunk: a frame larger than it that ends within the chunk carrying it is read.
|
|
30
|
+
*/
|
|
31
|
+
var FrameTooLarge = class extends Schema.TaggedError()("FrameTooLarge", { maxEventSize: Schema.Int }) {};
|
|
32
|
+
const Reason = Schema.Union([
|
|
33
|
+
StatusError,
|
|
34
|
+
ProtocolError,
|
|
35
|
+
NoReply,
|
|
36
|
+
UndeclaredError,
|
|
37
|
+
NoStructuredContent,
|
|
38
|
+
FrameTooLarge
|
|
39
|
+
]);
|
|
40
|
+
const sentenceOf = Match.typeTags()({
|
|
41
|
+
StatusError: ({ status, body }) => `answered ${status}: ${body}`,
|
|
42
|
+
ProtocolError: ({ code, message }) => `failed with ${code}: ${message}`,
|
|
43
|
+
NoReply: ({ body }) => `had no reply: ${body}`,
|
|
44
|
+
UndeclaredError: ({ text }) => `returned an error: ${text}`,
|
|
45
|
+
NoStructuredContent: ({ body }) => `returned no structured content: ${body}`,
|
|
46
|
+
FrameTooLarge: ({ maxEventSize }) => `sent more than ${maxEventSize} characters of an event stream with no frame ending`
|
|
47
|
+
});
|
|
48
|
+
/**
|
|
49
|
+
* An answer a client cannot read as the contract's: `method` is the request, `tools/call` or an
|
|
50
|
+
* `events/*` one, `target` the tool or event it named, and `reason` says what was wrong, with what
|
|
51
|
+
* the endpoint sent, so a caller branches on it with
|
|
52
|
+
* `Effect.catchReason("McpCallError", "StatusError", ...)`. `message` names all three.
|
|
53
|
+
* Structured content that does not decode as the success is a `SchemaError` instead, and a
|
|
54
|
+
* request that fails an `HttpClientError`.
|
|
55
|
+
*
|
|
56
|
+
* **Gotchas**
|
|
57
|
+
*
|
|
58
|
+
* - `returned an error: Invalid parameters for tool …`, an `UndeclaredError`, from a call its types allowed: the endpoint serves another contract under that name; give `client` the served contract value.
|
|
59
|
+
*/
|
|
60
|
+
var McpCallError = class extends Schema.TaggedError()("McpCallError", {
|
|
61
|
+
method: Schema.String,
|
|
62
|
+
target: Schema.String,
|
|
63
|
+
reason: Reason,
|
|
64
|
+
message: Schema.String
|
|
65
|
+
}) {};
|
|
66
|
+
const callError = (method, target, reason) => new McpCallError({
|
|
67
|
+
method,
|
|
68
|
+
target,
|
|
69
|
+
reason,
|
|
70
|
+
message: `MCP ${method} "${target}" ${sentenceOf(reason)}`
|
|
71
|
+
});
|
|
72
|
+
const isEventStream = (response) => (response.headers["content-type"] ?? "").startsWith("text/event-stream");
|
|
73
|
+
/**
|
|
74
|
+
* The data of each frame of the event stream `response` is, as it arrives: more than the parser
|
|
75
|
+
* holds with no frame ending fails `method` on `name` with `FrameTooLarge`.
|
|
76
|
+
*/
|
|
77
|
+
const framesOf = (method, name, response) => Stream.suspend(() => {
|
|
78
|
+
const data = [];
|
|
79
|
+
const parser = Sse.makeParser((event) => {
|
|
80
|
+
if (Predicate.isTagged(event, "Event")) data.push(event.data);
|
|
81
|
+
});
|
|
82
|
+
return Stream.decodeText(response.stream).pipe(Stream.mapEffect((chunk) => {
|
|
83
|
+
const error = parser.feed(chunk);
|
|
84
|
+
return error === void 0 ? Effect.succeed(data.splice(0)) : Effect.fail(callError(method, name, new FrameTooLarge({ maxEventSize: error.reason.maxEventSize })));
|
|
85
|
+
}), Stream.flattenIterable);
|
|
86
|
+
});
|
|
87
|
+
/**
|
|
88
|
+
* The messages of `response`, read as it arrives: the data of each frame of an event stream, or
|
|
89
|
+
* the body itself as one. Each `notifications/progress` frame hands its value to `sink` and is
|
|
90
|
+
* left out, so a long call holds the frames its reply is read from, not every value it reported.
|
|
91
|
+
* A stateless request's notifications are its own.
|
|
92
|
+
*/
|
|
93
|
+
const bodyOf = (method, name, response, sink) => isEventStream(response) ? Stream.runCollect(Stream.filterEffect(framesOf(method, name, response), (data) => Option.match(progressOf(data), {
|
|
94
|
+
onNone: () => Effect.succeed(true),
|
|
95
|
+
onSome: (value) => sink === void 0 ? Effect.succeed(false) : Effect.as(sink.progress(value), false)
|
|
96
|
+
}))) : Effect.map(response.text, (text) => [text]);
|
|
97
|
+
/** Fails with the error of `errors` that `text` decodes as, or with `otherwise`. */
|
|
98
|
+
const failWith = (errors, text, otherwise) => Schema.decodeEffect(Schema.fromJsonString(Schema.toCodecJson(Schema.Union(errors))))(text).pipe(Effect.mapError(() => otherwise), Effect.flatMap(Effect.fail));
|
|
99
|
+
export { FrameTooLarge, McpCallError, NoReply, NoStructuredContent, ProtocolError, StatusError, UndeclaredError, bodyOf, callError, failWith, framesOf, isEventStream };
|