@gjermundgaraba/effect-actions 0.11.0 → 0.13.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 +102 -41
- package/dist/Action.d.mts +31 -117
- package/dist/Action.mjs +14 -53
- package/dist/ActionCli.d.mts +60 -28
- package/dist/ActionCli.mjs +54 -48
- package/dist/ActionHttp.d.mts +18 -67
- package/dist/ActionHttp.mjs +20 -70
- package/dist/ActionMcp.d.mts +23 -29
- package/dist/ActionMcp.mjs +56 -114
- package/dist/ActionRpc.d.mts +147 -0
- package/dist/ActionRpc.mjs +113 -0
- package/dist/ActionToolkit.d.mts +28 -9
- package/dist/ActionToolkit.mjs +31 -4
- package/dist/Authentication.d.mts +15 -15
- package/dist/Authentication.mjs +42 -96
- package/dist/Testing.d.mts +30 -23
- package/dist/Testing.mjs +27 -44
- package/dist/{internal/authentication.d.mts → authentication/provider.d.mts} +41 -9
- package/dist/authentication/provider.mjs +12 -0
- package/dist/authentication/refusal.mjs +36 -0
- package/dist/{internal/cli.d.mts → cli/commands.d.mts} +16 -12
- package/dist/cli/commands.mjs +302 -0
- package/dist/{internal → contract}/call.d.mts +2 -2
- package/dist/contract/call.mjs +10 -0
- package/dist/{internal → contract}/errors.d.mts +13 -10
- package/dist/{internal → contract}/errors.mjs +9 -14
- package/dist/{internal → contract}/implementation.d.mts +37 -21
- package/dist/{internal → contract}/implementation.mjs +18 -49
- package/dist/contract/media.d.mts +13 -0
- package/dist/contract/media.mjs +18 -0
- package/dist/contract/rules.d.mts +25 -0
- package/dist/{internal/actions.mjs → contract/rules.mjs} +6 -42
- package/dist/{internal → http}/client.d.mts +7 -9
- package/dist/http/client.mjs +17 -0
- package/dist/http/declared.mjs +20 -0
- package/dist/http/schema-errors.mjs +9 -0
- package/dist/mcp/protocol.d.mts +18 -0
- package/dist/{internal/mcp.mjs → mcp/protocol.mjs} +29 -62
- package/dist/stdio/console.mjs +64 -0
- package/dist/{internal → testing}/memory.d.mts +1 -1
- package/dist/{internal → testing}/memory.mjs +5 -17
- package/dist/toolkit/tools.mjs +29 -0
- package/docs/Action-failures.md +47 -0
- package/docs/Action-in-process.md +93 -0
- package/docs/Action.md +211 -426
- package/docs/ActionCli-failures.md +104 -0
- package/docs/ActionCli.md +208 -293
- package/docs/ActionHttp-failures.md +83 -0
- package/docs/ActionHttp.md +205 -282
- package/docs/ActionMcp-failures.md +31 -0
- package/docs/ActionMcp.md +222 -271
- package/docs/ActionRpc-failures.md +21 -0
- package/docs/ActionRpc.md +179 -0
- package/docs/ActionToolkit-failures.md +15 -0
- package/docs/ActionToolkit.md +103 -119
- package/docs/Authentication-advanced.md +139 -0
- package/docs/Authentication-failures.md +99 -0
- package/docs/Authentication.md +143 -392
- package/docs/CONTEXT.md +40 -24
- package/docs/README.md +75 -35
- package/docs/Testing-failures.md +21 -0
- package/docs/Testing.md +193 -151
- package/docs/guarantees.md +35 -96
- package/docs/setup-failures.md +37 -0
- package/docs/setup.md +90 -92
- package/examples/README.md +68 -0
- package/examples/assistant/README.md +92 -0
- package/examples/assistant/src/agent.ts +92 -0
- package/examples/assistant/src/cli.ts +34 -0
- package/examples/assistant/src/commands.ts +25 -0
- package/examples/assistant/src/identity/Identity.ts +33 -0
- package/examples/assistant/src/identity/authorize.ts +24 -0
- package/examples/assistant/src/identity/handlers.ts +10 -0
- package/examples/assistant/src/identity/members.ts +9 -0
- package/examples/assistant/src/identity/permissions.ts +23 -0
- package/examples/assistant/src/scripted.ts +23 -0
- package/examples/assistant/src/tasks/Tasks.ts +63 -0
- package/examples/assistant/src/tasks/handlers.ts +25 -0
- package/examples/assistant/src/tasks/store.ts +96 -0
- package/examples/assistant/src/toolkit.ts +30 -0
- package/examples/assistant/test/commands.test.ts +57 -0
- package/examples/assistant/test/identity/identity.test.ts +43 -0
- package/examples/assistant/test/tasks/store.test.ts +48 -0
- package/examples/assistant/test/tasks/tasks.test.ts +75 -0
- package/examples/assistant/test/toolkit.test.ts +121 -0
- package/examples/kitchen-sink/README.md +318 -0
- package/examples/kitchen-sink/src/Actions.ts +19 -0
- package/examples/kitchen-sink/src/Api.ts +24 -0
- package/examples/kitchen-sink/src/Rpc.ts +12 -0
- package/examples/kitchen-sink/src/admin/Admin.ts +59 -0
- package/examples/kitchen-sink/src/admin/authenticate.ts +18 -0
- package/examples/kitchen-sink/src/admin/authorize.ts +27 -0
- package/examples/kitchen-sink/src/admin/grants.ts +22 -0
- package/examples/kitchen-sink/src/admin/handlers.ts +21 -0
- package/examples/kitchen-sink/src/admin/keys.ts +8 -0
- package/examples/kitchen-sink/src/agent.ts +47 -0
- package/examples/kitchen-sink/src/assistant/Assistant.ts +82 -0
- package/examples/kitchen-sink/src/assistant/conversations.ts +71 -0
- package/examples/kitchen-sink/src/assistant/demo-model.ts +33 -0
- package/examples/kitchen-sink/src/assistant/handlers.ts +126 -0
- package/examples/kitchen-sink/src/attachments/Attachments.ts +48 -0
- package/examples/kitchen-sink/src/attachments/download.ts +49 -0
- package/examples/kitchen-sink/src/attachments/handlers.ts +38 -0
- package/examples/kitchen-sink/src/attachments/store.ts +58 -0
- package/examples/kitchen-sink/src/browser.ts +33 -0
- package/examples/kitchen-sink/src/cli.ts +85 -0
- package/examples/kitchen-sink/src/config.ts +18 -0
- package/examples/kitchen-sink/src/identity/Identity.ts +61 -0
- package/examples/kitchen-sink/src/identity/authenticate.ts +87 -0
- package/examples/kitchen-sink/src/identity/authorize.ts +16 -0
- package/examples/kitchen-sink/src/identity/directory.ts +52 -0
- package/examples/kitchen-sink/src/identity/handlers.ts +9 -0
- package/examples/kitchen-sink/src/identity/members.ts +13 -0
- package/examples/kitchen-sink/src/identity/quota.ts +62 -0
- package/examples/kitchen-sink/src/issues/Issues.ts +111 -0
- package/examples/kitchen-sink/src/issues/assistant.ts +23 -0
- package/examples/kitchen-sink/src/issues/guide.ts +37 -0
- package/examples/kitchen-sink/src/issues/handlers.ts +37 -0
- package/examples/kitchen-sink/src/issues/store.ts +96 -0
- package/examples/kitchen-sink/src/issues/write-limit.ts +42 -0
- package/examples/kitchen-sink/src/memory.ts +30 -0
- package/examples/kitchen-sink/src/ops.ts +42 -0
- package/examples/kitchen-sink/src/request-policy.ts +63 -0
- package/examples/kitchen-sink/src/routes.ts +120 -0
- package/examples/kitchen-sink/src/sdk.ts +46 -0
- package/examples/kitchen-sink/src/seed.ts +65 -0
- package/examples/kitchen-sink/src/server.ts +62 -0
- package/examples/kitchen-sink/src/stdio.ts +34 -0
- package/examples/kitchen-sink/src/workspaces/Workspaces.ts +28 -0
- package/examples/kitchen-sink/src/workspaces/handlers.ts +23 -0
- package/examples/kitchen-sink/src/workspaces/registry.ts +46 -0
- package/examples/kitchen-sink/src/workspaces/tenant.ts +17 -0
- package/examples/kitchen-sink/test/admin/admin.test.ts +67 -0
- package/examples/kitchen-sink/test/assistant/assistant.test.ts +435 -0
- package/examples/kitchen-sink/test/attachments/attachments.test.ts +161 -0
- package/examples/kitchen-sink/test/identity/identity.test.ts +137 -0
- package/examples/kitchen-sink/test/identity/session.test.ts +110 -0
- package/examples/kitchen-sink/test/issues/assistant.test.ts +75 -0
- package/examples/kitchen-sink/test/issues/issues.test.ts +135 -0
- package/examples/kitchen-sink/test/request-policy.test.ts +125 -0
- package/examples/kitchen-sink/test/routes.test.ts +123 -0
- package/examples/kitchen-sink/test/support/scripted.ts +40 -0
- package/examples/live-rpc/README.md +107 -0
- package/examples/live-rpc/src/Rpc.ts +8 -0
- package/examples/live-rpc/src/browser.ts +98 -0
- package/examples/live-rpc/src/chat/Chat.ts +77 -0
- package/examples/live-rpc/src/chat/authorize.ts +16 -0
- package/examples/live-rpc/src/chat/handlers.ts +50 -0
- package/examples/live-rpc/src/chat/room.ts +23 -0
- package/examples/live-rpc/src/chat/store.ts +54 -0
- package/examples/live-rpc/src/client.ts +37 -0
- package/examples/live-rpc/src/identity/Identity.ts +19 -0
- package/examples/live-rpc/src/identity/members.ts +8 -0
- package/examples/live-rpc/src/identity/verifier.ts +23 -0
- package/examples/live-rpc/src/routes.ts +22 -0
- package/examples/live-rpc/src/server.ts +43 -0
- package/examples/live-rpc/test/browser.test.ts +105 -0
- package/examples/live-rpc/test/chat/chat.test.ts +69 -0
- package/examples/live-rpc/test/routes.test.ts +99 -0
- package/examples/live-rpc/test/server.test.ts +75 -0
- package/examples/mcp-tools/README.md +141 -0
- package/examples/mcp-tools/src/instructions.ts +12 -0
- package/examples/mcp-tools/src/printers/Printers.ts +121 -0
- package/examples/mcp-tools/src/printers/fake.ts +115 -0
- package/examples/mcp-tools/src/printers/farm.ts +136 -0
- package/examples/mcp-tools/src/printers/handlers.ts +29 -0
- package/examples/mcp-tools/src/routes.ts +52 -0
- package/examples/mcp-tools/src/server.ts +31 -0
- package/examples/mcp-tools/src/stdio.ts +26 -0
- package/examples/mcp-tools/test/printers/printers.test.ts +189 -0
- package/examples/mcp-tools/test/routes.test.ts +202 -0
- package/examples/notes-service/README.md +111 -0
- package/examples/notes-service/src/Api.ts +13 -0
- package/examples/notes-service/src/browser.ts +41 -0
- package/examples/notes-service/src/cli.ts +22 -0
- package/examples/notes-service/src/commands.ts +137 -0
- package/examples/notes-service/src/identity/Identity.ts +31 -0
- package/examples/notes-service/src/identity/authorize.ts +18 -0
- package/examples/notes-service/src/identity/handlers.ts +10 -0
- package/examples/notes-service/src/identity/users.ts +9 -0
- package/examples/notes-service/src/identity/verifier.ts +36 -0
- package/examples/notes-service/src/notes/Notes.ts +119 -0
- package/examples/notes-service/src/notes/handlers.ts +39 -0
- package/examples/notes-service/src/notes/store.ts +84 -0
- package/examples/notes-service/src/routes.ts +45 -0
- package/examples/notes-service/src/server.ts +26 -0
- package/examples/notes-service/src/status/Status.ts +10 -0
- package/examples/notes-service/src/status/handlers.ts +14 -0
- package/examples/notes-service/src/stdio.ts +25 -0
- package/examples/notes-service/test/commands.test.ts +288 -0
- package/examples/notes-service/test/notes/notes.test.ts +103 -0
- package/examples/notes-service/test/routes.test.ts +84 -0
- package/examples/quickstart/README.md +31 -0
- package/examples/quickstart/src/Api.ts +5 -0
- package/examples/quickstart/src/client.ts +13 -0
- package/examples/quickstart/src/greeting/Greeting.ts +12 -0
- package/examples/quickstart/src/greeting/handlers.ts +6 -0
- package/examples/quickstart/src/routes.ts +11 -0
- package/examples/quickstart/src/server.ts +16 -0
- package/examples/quickstart/test/greeting/greeting.test.ts +18 -0
- package/package.json +27 -9
- package/dist/internal/actions.d.mts +0 -9
- package/dist/internal/authentication.mjs +0 -11
- package/dist/internal/call.mjs +0 -12
- package/dist/internal/cli.mjs +0 -328
- package/dist/internal/client.mjs +0 -25
- package/dist/internal/console.mjs +0 -77
- package/dist/internal/declared.d.mts +0 -13
- package/dist/internal/declared.mjs +0 -34
- package/dist/internal/mcp.d.mts +0 -14
- package/dist/internal/media.d.mts +0 -8
- package/dist/internal/media.mjs +0 -26
- package/dist/internal/refusal.mjs +0 -67
- package/dist/internal/schema-errors.mjs +0 -20
- package/dist/internal/tools.mjs +0 -44
package/dist/ActionMcp.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ActionOf, BuildError, BuildServices, Holding, Known, Member, Protected, SelectedOf, Selection, Served, ServedRequest } from "./
|
|
1
|
+
import { ActionOf, BuildError, BuildServices, Holding, Known, Member, Protected, SelectedOf, Selection, Served, ServedRequest } from "./contract/implementation.mjs";
|
|
2
2
|
import { Action_d_exports, Any } from "./Action.mjs";
|
|
3
|
-
import { Any as Any$1, Matching, ProviderOf, RemoteRequest, Required } from "./
|
|
3
|
+
import { Any as Any$1, Matching, ProviderOf, RemoteRequest, Required, ServerBuildServices } from "./authentication/provider.mjs";
|
|
4
4
|
import { Cause, Effect, Layer, Stdio } from "effect";
|
|
5
|
-
import { HttpRouter } from "effect/http";
|
|
6
5
|
import { McpSchema, McpServer } from "effect/ai";
|
|
7
|
-
|
|
6
|
+
import { HttpRouter } from "effect/http";
|
|
7
|
+
//#region src/mcp/ActionMcp.d.ts
|
|
8
8
|
/**
|
|
9
9
|
* An MCP server, over HTTP or stdio: every native `McpServer.layerStdio` option except
|
|
10
10
|
* `protocols`, the server information, `instructions` and `extensions`, the actions `A` that
|
|
@@ -24,53 +24,47 @@ export interface Options<E = never, R = never, A extends Any = Any> extends Omit
|
|
|
24
24
|
readonly features?: Layer.Layer<never, E, R> | undefined;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* One Streamable HTTP MCP endpoint
|
|
28
|
-
*
|
|
27
|
+
* One Streamable HTTP MCP endpoint, speaking MCP 2026-07-28, 2025-11-25 or 2025-06-18, as the
|
|
28
|
+
* client negotiates: `Options`, and every native `McpServer.layerHttp` option, `allowedOrigins`
|
|
29
|
+
* among them, except `protocols` and `allowSessionTermination`, which are fixed, with `path`
|
|
30
|
+
* defaulting to `/mcp`. A client of a 2025 revision opens a session with `initialize`, which
|
|
31
|
+
* lasts until that client ends it with a DELETE, answered 204, or the process stops.
|
|
29
32
|
*/
|
|
30
|
-
export interface LayerHttpOptions<E = never, R = never, A extends Any = Any> extends Options<E, R, A>, Omit<Parameters<typeof McpServer.layerHttp>[0], "protocols" | "path"> {
|
|
33
|
+
export interface LayerHttpOptions<E = never, R = never, A extends Any = Any> extends Options<E, R, A>, Omit<Parameters<typeof McpServer.layerHttp>[0], "protocols" | "path" | "allowSessionTermination"> {
|
|
31
34
|
/** The endpoint's route; defaults to `/mcp`. */
|
|
32
35
|
readonly path?: HttpRouter.PathInput;
|
|
33
36
|
readonly authentication?: Any$1;
|
|
34
37
|
}
|
|
35
|
-
/**
|
|
36
|
-
* The native server supplies its own request context to every tool call, and the registry
|
|
37
|
-
* it registers the tools on, the endpoint's own; over HTTP the router its own, such as the
|
|
38
|
-
* request.
|
|
39
|
-
*/
|
|
40
38
|
type ToolRequestContext<R> = Exclude<R, McpSchema.McpRequestContext | McpServer.McpServer>;
|
|
41
39
|
type HttpToolRequestContext<R> = Exclude<ToolRequestContext<R>, HttpRouter.Provided>;
|
|
42
|
-
/**
|
|
43
|
-
* What the native features owe, `R`, beside the registry, which the endpoint provides them,
|
|
44
|
-
* so one registering through `McpServer.registerResource` owes it no host.
|
|
45
|
-
*/
|
|
46
40
|
type Features<R> = Exclude<R, McpServer.McpServer>;
|
|
47
|
-
/**
|
|
48
|
-
* Where `D`, the descriptor given, is inferred, so the provider owed is that descriptor's.
|
|
49
|
-
* Without one, tools `A` all public take none, even past a `D` given explicitly; protected
|
|
50
|
-
* ones state theirs in `RequiredAuthentication` alone, as an absent `authentication` and the
|
|
51
|
-
* one they require would intersect to `never`, reading as every option being wrong.
|
|
52
|
-
*/
|
|
53
41
|
type Inferring<D, A extends Any> = [D] extends [undefined] ? [Protected<A>] extends [never] ? {
|
|
54
42
|
readonly authentication?: undefined;
|
|
55
43
|
} : unknown : {
|
|
56
44
|
readonly authentication?: D;
|
|
57
45
|
};
|
|
58
46
|
/**
|
|
59
|
-
* Serve MCP tools over one Streamable HTTP endpoint, speaking MCP 2026-07-28
|
|
47
|
+
* Serve MCP tools over one Streamable HTTP endpoint, speaking MCP 2026-07-28, 2025-11-25 or
|
|
48
|
+
* 2025-06-18, as the client negotiates. A 2026-07-28 request stands alone; a client of a 2025
|
|
49
|
+
* revision opens a session with `initialize`, which holds what it sent there and no identity,
|
|
50
|
+
* and lasts until that client ends it with a DELETE, answered 204, or the process stops.
|
|
60
51
|
*
|
|
61
52
|
* Protected tools need the `authentication` descriptor their contracts name, and the
|
|
62
53
|
* provider `Authentication.layer` builds from it. On an endpoint of protected tools only,
|
|
63
54
|
* every request authenticates before it is decoded. On a mixed endpoint, anyone may discover
|
|
64
|
-
* the server, list its tools, cancel, and call a public tool
|
|
65
|
-
* first,
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
55
|
+
* the server, list its tools, cancel, and call a public tool in a 2026-07-28 request;
|
|
56
|
+
* everything else authenticates first, a session's every request included, so a protected
|
|
57
|
+
* tool's description is listed to anyone but run only for its caller.
|
|
58
|
+
* On a Bearer endpoint, a protected tool's call refused with `Unauthenticated`, or with
|
|
59
|
+
* `Forbidden` naming scopes, is answered with its HTTP status and challenge, 401 or 403, as MCP
|
|
60
|
+
* authorization requires, unless its response has started; any other failure is a tool result. Claude Code connects with
|
|
61
|
+
* `claude mcp add --transport http <name> <url>`. In a tool call, what middleware provides per request wins over
|
|
69
62
|
* what the endpoint was built with; native `features` read only what they were built with,
|
|
70
63
|
* never a request's. Never provide an identity at startup: a tool call takes its identity
|
|
71
64
|
* only from the request it authenticated.
|
|
65
|
+
* Guide: `docs/ActionMcp.md` in this package; in an application: `examples/mcp-tools/`.
|
|
72
66
|
*/
|
|
73
|
-
export declare function layerHttp<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>>> = {}, E = never, R = never, const D extends Any$1 | undefined = undefined>(implementations: Apps, options: LayerHttpOptions<E, R, ActionOf<Member<Apps>>> & O & NoInfer<Known<O, LayerHttpOptions<E, R, Any>>> & NoInfer<Required<SelectedOf<O, Apps>>> & Matching<NoInfer<SelectedOf<O, Apps>>, NoInfer<D>> & Inferring<D, NoInfer<SelectedOf<O, Apps>>>): Layer.Layer<never, BuildError<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | Cause.IllegalArgumentError | E,
|
|
67
|
+
export declare function layerHttp<const Apps extends Served, const O extends Selection<ActionOf<Member<Apps>>> = {}, E = never, R = never, const D extends Any$1 | undefined = undefined>(implementations: Apps, options: LayerHttpOptions<E, R, ActionOf<Member<Apps>>> & O & NoInfer<Known<O, LayerHttpOptions<E, R, Any>>> & NoInfer<Required<SelectedOf<O, Apps>>> & Matching<NoInfer<SelectedOf<O, Apps>>, NoInfer<D>> & Inferring<D, NoInfer<SelectedOf<O, Apps>>>): Layer.Layer<never, BuildError<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | Cause.IllegalArgumentError | E, ServerBuildServices<Holding<Member<Apps>, SelectedOf<O, Apps>>, SelectedOf<O, Apps>> | HttpRouter.HttpRouter | HttpRouter.Request.From<"Requires", HttpToolRequestContext<RemoteRequest<Member<Apps>, SelectedOf<O, Apps>>>> | ProviderOf<D> | Features<R>>;
|
|
74
68
|
/**
|
|
75
69
|
* Serve MCP tools through newline-delimited JSON-RPC on standard I/O, speaking MCP
|
|
76
70
|
* 2026-07-28, 2025-11-25 or 2025-06-18, as the host negotiates: the whole
|
package/dist/ActionMcp.mjs
CHANGED
|
@@ -1,44 +1,20 @@
|
|
|
1
|
-
import { Anyone } from "./
|
|
2
|
-
import { provideHandlers, select, toList } from "./
|
|
3
|
-
import {
|
|
4
|
-
import { fieldKey, httpProtocol, isMisplaced, liftOf, liftedFrom } from "./
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { Anyone } from "./contract/rules.mjs";
|
|
2
|
+
import { provideHandlers, select, toList } from "./contract/implementation.mjs";
|
|
3
|
+
import { bindTools } from "./toolkit/tools.mjs";
|
|
4
|
+
import { fieldKey, httpProtocol, isMisplaced, liftOf, liftedFrom, protocols } from "./mcp/protocol.mjs";
|
|
5
|
+
import { assertAuthentication, promote, providerOf } from "./authentication/provider.mjs";
|
|
6
|
+
import { recordStepUp } from "./authentication/refusal.mjs";
|
|
7
|
+
import { withStderrConsole } from "./stdio/console.mjs";
|
|
8
8
|
import { Cause, Context, Effect, ErrorReporter, Exit, Fiber, JsonPointer, Layer, Option, Predicate, References, Result, Schema, Stream } from "effect";
|
|
9
|
+
import { AiError, McpSchema, McpServer, Tool, Toolkit } from "effect/ai";
|
|
9
10
|
import { HttpRouter, HttpServerRequest } from "effect/http";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The revisions served over stdio: 2026-07-28 and the stateful revisions hosts open stdio
|
|
14
|
-
* with, newest first; a host asking for another is offered 2025-11-25. A success is sent as it
|
|
15
|
-
* is on each. 2026-07-28 carries any success as `structuredContent`; the adapters of 2025-11-25
|
|
16
|
-
* and 2025-06-18 carry only an object there, and list only an object-rooted `outputSchema`. A
|
|
17
|
-
* success they do not structure is text alone, a string as itself.
|
|
18
|
-
*/
|
|
19
|
-
const stdioProtocols = [
|
|
20
|
-
McpProtocol.v2026_07_28,
|
|
21
|
-
McpProtocol.v2025_11_25,
|
|
22
|
-
McpProtocol.v2025_06_18
|
|
23
|
-
];
|
|
24
|
-
/**
|
|
25
|
-
* MCP has a JSON-only wire contract. Success is the encoded success as structured content,
|
|
26
|
-
* and its JSON as text, but for its media, each lifted into a block of its own; declared
|
|
27
|
-
* failures are returned as JSON text. The native server refuses undeclared arguments and
|
|
28
|
-
* publishes closed input schemas.
|
|
29
|
-
*/
|
|
30
|
-
const projection = {
|
|
11
|
+
//#region src/mcp/ActionMcp.ts
|
|
12
|
+
const strictToolProjection = {
|
|
31
13
|
label: "MCP tool",
|
|
32
14
|
tool: (tool) => tool.annotate(Tool.Strict, true)
|
|
33
15
|
};
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
/**
|
|
37
|
-
* The JSON Schema of `schema`, a JSON codec, as the native server lists it for a tool: a
|
|
38
|
-
* `$ref` at its root resolved, as MCP requires an object root, and its definitions as `$defs`.
|
|
39
|
-
* A strict one refuses undeclared keys.
|
|
40
|
-
*/
|
|
41
|
-
const toolJsonSchema = (schema, strict) => {
|
|
16
|
+
const isToolArgumentsSchema = Schema.is(McpSchema.ToolJson);
|
|
17
|
+
const nativeToolJsonSchema = (schema, strict) => {
|
|
42
18
|
const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: strict ? "error" : "ignore" });
|
|
43
19
|
const [scope, key] = Predicate.isString(document.schema.$ref) ? JsonPointer.parseUriFragment(document.schema.$ref) ?? [] : [];
|
|
44
20
|
const root = scope === "$defs" && key !== void 0 ? document.definitions[key] : document.schema;
|
|
@@ -47,58 +23,33 @@ const toolJsonSchema = (schema, strict) => {
|
|
|
47
23
|
$defs: document.definitions
|
|
48
24
|
};
|
|
49
25
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* tool's arguments must be, naming every one, reading the JSON Schema the native
|
|
53
|
-
* server reads: not a union, an array, a scalar, nor `Schema.Struct({})`, which takes any value
|
|
54
|
-
* but `null`, where the native server dies when the layer builds, suggesting
|
|
55
|
-
* `Tool.EmptyParams`. Then refuse those holding media where no tool lifts it, as each action's
|
|
56
|
-
* `Lift` says.
|
|
57
|
-
*/
|
|
58
|
-
const assertShapes = (lifted) => {
|
|
59
|
-
const inputs = lifted.map(([action]) => action).filter((action) => !isToolJson(toolJsonSchema(Schema.toCodecJson(action.input), true))).map(({ name }) => name);
|
|
26
|
+
const assertToolShapes = (lifted) => {
|
|
27
|
+
const inputs = lifted.map(([action]) => action).filter((action) => !isToolArgumentsSchema(nativeToolJsonSchema(Schema.toCodecJson(action.input), true))).map(({ name }) => name);
|
|
60
28
|
if (inputs.length > 0) throw new Error(`MCP tool input must be one object with keys, such as a struct: ${inputs.join(", ")}`);
|
|
61
29
|
const misplaced = lifted.filter(([action, lift]) => isMisplaced(action, lift)).map(([{ name }]) => name);
|
|
62
30
|
if (misplaced.length > 0) throw new Error(`MCP media must be the success or an array of it, or a top-level field of a struct success, one, optional or a required array: ${misplaced.join(", ")}`);
|
|
63
31
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* defect, an invalid success or error, or an unknown tool.
|
|
67
|
-
*/
|
|
68
|
-
const internalError = "Tool execution failed due to an internal server error.";
|
|
69
|
-
/** A tool's error result, `text` its one text block. */
|
|
70
|
-
const errorResult = (text) => new McpSchema.CallToolResult({
|
|
32
|
+
const nativeInternalErrorText = "Tool execution failed due to an internal server error.";
|
|
33
|
+
const textErrorResult = (text) => new McpSchema.CallToolResult({
|
|
71
34
|
isError: true,
|
|
72
35
|
content: [{
|
|
73
36
|
type: "text",
|
|
74
37
|
text
|
|
75
38
|
}]
|
|
76
39
|
});
|
|
77
|
-
|
|
78
|
-
const textOf = (encoded) => encoded === void 0 ? [] : [{
|
|
40
|
+
const nativeJsonTextOf = (encoded) => encoded === void 0 ? [] : [{
|
|
79
41
|
type: "text",
|
|
80
42
|
text: JSON.stringify(encoded)
|
|
81
43
|
}];
|
|
82
|
-
|
|
83
|
-
const blockOf = ({ kind, field, value }) => ({
|
|
44
|
+
const contentBlockOf = ({ kind, field, value }) => ({
|
|
84
45
|
type: kind,
|
|
85
46
|
data: value.data,
|
|
86
47
|
mimeType: value.mimeType,
|
|
87
48
|
...field === void 0 ? {} : { _meta: { [fieldKey]: field } }
|
|
88
49
|
});
|
|
89
|
-
|
|
90
|
-
const invalidParameters = (error) => AiError.isAiError(error) && Predicate.isTagged(error.reason, "ToolParameterValidationError") ? error.reason : void 0;
|
|
50
|
+
const parameterValidationErrorOf = (error) => AiError.isAiError(error) && Predicate.isTagged(error.reason, "ToolParameterValidationError") ? error.reason : void 0;
|
|
91
51
|
const omitRequestServices = Context.omit(McpSchema.McpRequestContext, McpSchema.McpServerClient, HttpServerRequest.HttpServerRequest, References.CurrentLogLevel);
|
|
92
|
-
|
|
93
|
-
* Register the tools of `toolkit` on the endpoint's registry, as `McpServer.registerToolkit`
|
|
94
|
-
* does, lifting the media `lifts` finds in a tool's success into blocks of their own. The native
|
|
95
|
-
* registration sends every success as structured content and one JSON text block, and takes
|
|
96
|
-
* no option, so this one follows it: its decode options, failure classification, defect
|
|
97
|
-
* scrubbing and listing, and the request services it leaves out of what it retains. A tool
|
|
98
|
-
* whose action holds no media is registered as the native one would be: a test compares their
|
|
99
|
-
* listings and results, not the services each call runs with.
|
|
100
|
-
*/
|
|
101
|
-
const registerTools = Effect.fnUntraced(function* (toolkit, lifts) {
|
|
52
|
+
const registerToolsLiftingMedia = Effect.fnUntraced(function* (toolkit, lifts) {
|
|
102
53
|
const registry = yield* McpServer.McpServer;
|
|
103
54
|
const built = yield* toolkit.pipe(Effect.updateContext((context) => Context.merge(context, Context.mergeAll(...Object.values(toolkit.tools).flatMap((tool) => {
|
|
104
55
|
const key = Context.Service(tool.id);
|
|
@@ -107,19 +58,19 @@ const registerTools = Effect.fnUntraced(function* (toolkit, lifts) {
|
|
|
107
58
|
context: omitRequestServices(handler.context)
|
|
108
59
|
}));
|
|
109
60
|
})))));
|
|
110
|
-
const
|
|
111
|
-
const
|
|
61
|
+
const retainedServices = omitRequestServices(yield* Effect.context());
|
|
62
|
+
const propagateInterruptOrReportInternal = (cause) => {
|
|
112
63
|
const failure = Cause.findFail(cause);
|
|
113
|
-
return Result.isFailure(failure) && !Cause.hasDies(cause) ? Effect.failCause(failure.failure) : Effect.logError(cause).pipe(Effect.andThen(Effect.provideContext(ErrorReporter.report(cause),
|
|
64
|
+
return Result.isFailure(failure) && !Cause.hasDies(cause) ? Effect.failCause(failure.failure) : Effect.logError(cause).pipe(Effect.andThen(Effect.provideContext(ErrorReporter.report(cause), retainedServices)), Effect.as(textErrorResult(nativeInternalErrorText)));
|
|
114
65
|
};
|
|
115
|
-
const
|
|
66
|
+
const invalidParamsOrInternal = (cause) => {
|
|
116
67
|
const failure = Cause.findFail(cause);
|
|
117
68
|
if (Result.isSuccess(failure)) {
|
|
118
69
|
const { error } = failure.success;
|
|
119
|
-
const invalid = Context.get(Cause.reasonAnnotations(failure.success), Toolkit.FailureOrigin) === "parameters" ?
|
|
70
|
+
const invalid = Context.get(Cause.reasonAnnotations(failure.success), Toolkit.FailureOrigin) === "parameters" ? parameterValidationErrorOf(error) : void 0;
|
|
120
71
|
if (invalid !== void 0) return Effect.fail(new McpSchema.InvalidParams({ message: invalid.message }));
|
|
121
72
|
}
|
|
122
|
-
return
|
|
73
|
+
return propagateInterruptOrReportInternal(cause);
|
|
123
74
|
};
|
|
124
75
|
const registrations = yield* Effect.forEach(Object.entries(built.tools), ([name, tool]) => Effect.gen(function* () {
|
|
125
76
|
const strict = Tool.getStrictMode(tool) === true;
|
|
@@ -130,25 +81,25 @@ const registerTools = Effect.fnUntraced(function* (toolkit, lifts) {
|
|
|
130
81
|
const lift = lifts.get(name);
|
|
131
82
|
const media = lift === void 0 || lift.fields.length === 0 ? void 0 : lift;
|
|
132
83
|
const structured = media === void 0 ? tool.successSchema : media.rest === void 0 ? void 0 : Schema.toCodecJson(media.rest);
|
|
133
|
-
const outputSchema = structured === void 0 ? void 0 : yield* Schema.decodeUnknownEffect(McpSchema.ToolOutputJson)(
|
|
134
|
-
const inputSchema = yield* Schema.decodeUnknownEffect(McpSchema.ToolJson)(
|
|
135
|
-
const
|
|
136
|
-
const
|
|
84
|
+
const outputSchema = structured === void 0 ? void 0 : yield* Schema.decodeUnknownEffect(McpSchema.ToolOutputJson)(nativeToolJsonSchema(structured, false)).pipe(Effect.orDie);
|
|
85
|
+
const inputSchema = yield* Schema.decodeUnknownEffect(McpSchema.ToolJson)(nativeToolJsonSchema(tool.parametersSchema, strict)).pipe(Effect.orDie);
|
|
86
|
+
const liftedFieldNames = new Set(media?.fields.map(({ name }) => name));
|
|
87
|
+
const resultWithLiftedMedia = ({ encodedResult, result }) => {
|
|
137
88
|
if (media === void 0) return new McpSchema.CallToolResult({
|
|
138
89
|
isError: false,
|
|
139
90
|
structuredContent: encodedResult,
|
|
140
|
-
content:
|
|
91
|
+
content: nativeJsonTextOf(encodedResult)
|
|
141
92
|
});
|
|
142
|
-
const blocks = liftedFrom(media, result).map(
|
|
93
|
+
const blocks = liftedFrom(media, result).map(contentBlockOf);
|
|
143
94
|
if (structured === void 0) return new McpSchema.CallToolResult({
|
|
144
95
|
isError: false,
|
|
145
96
|
content: blocks
|
|
146
97
|
});
|
|
147
|
-
const
|
|
98
|
+
const encodedRestOfStruct = Predicate.isObject(encodedResult) ? Object.fromEntries(Object.entries(encodedResult).filter(([key]) => !liftedFieldNames.has(key))) : encodedResult;
|
|
148
99
|
return new McpSchema.CallToolResult({
|
|
149
100
|
isError: false,
|
|
150
|
-
structuredContent:
|
|
151
|
-
content: [...
|
|
101
|
+
structuredContent: encodedRestOfStruct,
|
|
102
|
+
content: [...nativeJsonTextOf(encodedRestOfStruct), ...blocks]
|
|
152
103
|
});
|
|
153
104
|
};
|
|
154
105
|
return {
|
|
@@ -167,63 +118,53 @@ const registerTools = Effect.fnUntraced(function* (toolkit, lifts) {
|
|
|
167
118
|
_meta: Context.getOrUndefined(tool.annotations, Tool.Meta)
|
|
168
119
|
}),
|
|
169
120
|
annotations: tool.annotations,
|
|
170
|
-
handle: (payload) => built.handle(name, payload ?? {}, void 0, decodeOptions).pipe(Stream.unwrap, Stream.runLast, Effect.flatMap(Effect.fromOption), Effect.flatMap((result) => !result.isFailure ? Effect.succeed(
|
|
121
|
+
handle: (payload) => built.handle(name, payload ?? {}, void 0, decodeOptions).pipe(Stream.unwrap, Stream.runLast, Effect.flatMap(Effect.fromOption), Effect.flatMap((result) => !result.isFailure ? Effect.succeed(resultWithLiftedMedia(result)) : result.failureOrigin === "handler" ? Effect.succeed(new McpSchema.CallToolResult({
|
|
171
122
|
isError: true,
|
|
172
|
-
content:
|
|
173
|
-
})) : Effect.failCause(Cause.annotate(Cause.fail(result.result), Context.make(Toolkit.FailureOrigin, result.failureOrigin ?? "result")))), Effect.catchCause(
|
|
123
|
+
content: nativeJsonTextOf(result.encodedResult)
|
|
124
|
+
})) : Effect.failCause(Cause.annotate(Cause.fail(result.result), Context.make(Toolkit.FailureOrigin, result.failureOrigin ?? "result")))), Effect.catchCause(invalidParamsOrInternal), Effect.provideContext(retainedServices))
|
|
174
125
|
};
|
|
175
126
|
}));
|
|
176
127
|
for (const registration of registrations) yield* registry.addTool(registration);
|
|
177
128
|
});
|
|
178
129
|
const server = (apps, transport, features = Layer.empty, authentication) => {
|
|
179
|
-
const binding = bindTools(apps, authentication === void 0 ?
|
|
180
|
-
...
|
|
130
|
+
const binding = bindTools(apps, authentication === void 0 ? strictToolProjection : {
|
|
131
|
+
...strictToolProjection,
|
|
181
132
|
handler: (run, action) => (input) => action.caller === Anyone ? run(input) : recordStepUp(promote(authentication, run(input)))
|
|
182
133
|
});
|
|
183
134
|
const lifted = apps.flatMap((app) => app.actions.map((action) => [action, liftOf(action.success)]));
|
|
184
|
-
|
|
135
|
+
assertToolShapes(lifted);
|
|
185
136
|
const lifts = new Map(lifted.map(([{ name }, lift]) => [name, lift]));
|
|
186
137
|
const register = Effect.gen(function* () {
|
|
187
138
|
const registry = yield* McpServer.McpServer;
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
139
|
+
const namesClaimedByFeatures = registry.tools.flatMap(({ tool }) => lifts.has(tool.name) ? [tool.name] : []);
|
|
140
|
+
if (namesClaimedByFeatures.length > 0) return yield* Effect.die(/* @__PURE__ */ new Error(`Duplicate MCP tool: ${namesClaimedByFeatures.join(", ")}, claimed by an action and a native feature`));
|
|
190
141
|
const handlers = yield* Layer.build(binding.layer);
|
|
191
|
-
yield*
|
|
142
|
+
yield* registerToolsLiftingMedia(binding.toolkit, lifts).pipe(Effect.setContext(Context.add(handlers, McpServer.McpServer, registry)));
|
|
192
143
|
});
|
|
193
144
|
return Layer.effectDiscard(register).pipe(Layer.provide(features), Layer.provide(transport), Layer.fresh, provideHandlers(apps));
|
|
194
145
|
};
|
|
195
|
-
|
|
196
|
-
* What an anonymous caller of a mixed endpoint may send besides a public tool's call:
|
|
197
|
-
* discovering the server and its tools, which the contracts describe, and cancelling.
|
|
198
|
-
* Everything else, a native feature's listing, completion or subscription among them, and
|
|
199
|
-
* any method a later revision adds, authenticates.
|
|
200
|
-
*/
|
|
201
|
-
const anonymous = /* @__PURE__ */ new Set([
|
|
146
|
+
const anonymousMethods = /* @__PURE__ */ new Set([
|
|
202
147
|
"server/discover",
|
|
203
148
|
"tools/list",
|
|
204
149
|
"notifications/cancelled"
|
|
205
150
|
]);
|
|
206
|
-
/**
|
|
207
|
-
* Whether a request to an endpoint serving `actions` must authenticate before the native
|
|
208
|
-
* server reads its body. An endpoint whose every tool is protected authenticates every
|
|
209
|
-
* request, so a host signs in when it connects. Otherwise the request's routing headers
|
|
210
|
-
* decide, which the native server refuses when they disagree with the body: discovery and a
|
|
211
|
-
* call of a public tool authenticate only a caller presenting credentials, and everything
|
|
212
|
-
* else authenticates, so native features are protected too. A request whose headers name
|
|
213
|
-
* no public tool as it is authenticates: it fails closed.
|
|
214
|
-
*/
|
|
215
151
|
const requiresAuthentication = (actions) => {
|
|
216
152
|
const open = new Set(actions.filter(({ caller }) => caller === Anyone).map(({ name }) => name));
|
|
217
153
|
return (headers) => {
|
|
218
154
|
if (open.size === 0) return true;
|
|
155
|
+
if (headers["mcp-protocol-version"] !== httpProtocol.protocolVersion) return true;
|
|
219
156
|
const method = headers["mcp-method"];
|
|
220
157
|
if (method === void 0) return true;
|
|
221
|
-
if (
|
|
158
|
+
if (anonymousMethods.has(method)) return false;
|
|
222
159
|
if (method !== "tools/call") return true;
|
|
223
160
|
const name = headers["mcp-name"];
|
|
224
161
|
return name === void 0 || !open.has(name);
|
|
225
162
|
};
|
|
226
163
|
};
|
|
164
|
+
const namesItsMethod = (request) => Effect.match(request.json, {
|
|
165
|
+
onFailure: () => true,
|
|
166
|
+
onSuccess: (body) => !Predicate.hasProperty(body, "method") || body.method === request.headers["mcp-method"]
|
|
167
|
+
});
|
|
227
168
|
function layerHttp(apps, { actions, features, path, authentication, ...options }) {
|
|
228
169
|
const selected = select(toList(apps), actions);
|
|
229
170
|
const served = selected.flatMap((app) => app.actions);
|
|
@@ -231,19 +172,20 @@ function layerHttp(apps, { actions, features, path, authentication, ...options }
|
|
|
231
172
|
const transport = server(selected, McpServer.layerHttp({
|
|
232
173
|
...options,
|
|
233
174
|
path: path ?? "/mcp",
|
|
234
|
-
protocols
|
|
175
|
+
protocols,
|
|
176
|
+
allowSessionTermination: true
|
|
235
177
|
}), features, authentication);
|
|
236
178
|
if (authentication === void 0) return transport;
|
|
237
179
|
const required = requiresAuthentication(served);
|
|
238
|
-
const middleware = HttpRouter.middleware(Effect.map(authentication
|
|
180
|
+
const middleware = HttpRouter.middleware(Effect.map(providerOf(authentication), (runtime) => (route) => Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => request.method !== "POST" || required(request.headers) ? runtime.http(route, false) : Effect.flatMap(namesItsMethod(request), (named) => runtime.http(route, named)))));
|
|
239
181
|
return transport.pipe(Layer.provide(middleware.layer));
|
|
240
182
|
}
|
|
241
183
|
function runStdio(apps, { actions, features, ...options }) {
|
|
242
184
|
const transport = server(select(toList(apps), actions), McpServer.layerStdio({
|
|
243
185
|
...options,
|
|
244
|
-
protocols
|
|
186
|
+
protocols
|
|
245
187
|
}), features);
|
|
246
|
-
return Layer.launch(transport).pipe(
|
|
188
|
+
return Layer.launch(transport).pipe(withStderrConsole, Effect.forkChild, Effect.flatMap(Fiber.await), Effect.flatMap((exit) => Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause) ? Effect.failCause(exit.cause) : Effect.void));
|
|
247
189
|
}
|
|
248
190
|
//#endregion
|
|
249
191
|
export { layerHttp, runStdio };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Anyone, Certain, ErrorsOf } from "./contract/rules.mjs";
|
|
2
|
+
import { BuiltIn, BuiltIns } from "./contract/errors.mjs";
|
|
3
|
+
import { BuildError, Holding, Known, Member, MiddlewareOf, Protected, Selected, Served, Serving } from "./contract/implementation.mjs";
|
|
4
|
+
import { Call } from "./contract/call.mjs";
|
|
5
|
+
import { Action_d_exports, Any as Any$1, Errors } from "./Action.mjs";
|
|
6
|
+
import { Any as Any$2, DescriptorOf, Identity, Matching, PublicOnly, RemoteRequest, Required, ServedProvider, ServerBuildServices, VerifierError } from "./authentication/provider.mjs";
|
|
7
|
+
import { Context, Effect, Layer, Schema, Scope, Types } from "effect";
|
|
8
|
+
import { HttpRouter } from "effect/http";
|
|
9
|
+
import { Rpc, RpcClient, RpcClientError, RpcGroup, RpcMiddleware, RpcServer } from "effect/rpc";
|
|
10
|
+
//#region src/rpc/ActionRpc.d.ts
|
|
11
|
+
type BindingError = Errors[number];
|
|
12
|
+
type ErrorOf<A extends Any$1, E extends Errors, D> = Schema.Union<ReadonlyArray<A["error"][number] | E[number] | VerifierError<A, D> | BuiltIns>>;
|
|
13
|
+
type RpcOf<A extends Any$1, E extends Errors, D> = A extends Any$1 ? Rpc.Rpc<A["name"], A["input"], A["success"], ErrorOf<A, E, D>> : never;
|
|
14
|
+
/**
|
|
15
|
+
* An RPC binding: actions, the errors every rpc declares, and the descriptor authenticating
|
|
16
|
+
* its protected ones. Plain data, so a copy of it, or one made by another installed copy of
|
|
17
|
+
* this package, serves the same; `layer` serves it and `client` calls it.
|
|
18
|
+
*/
|
|
19
|
+
export interface Binding<Actions extends ReadonlyArray<Any$1>, E extends Errors = [], D extends Any$2 | undefined = undefined> {
|
|
20
|
+
readonly authentication: D;
|
|
21
|
+
/** The exact actions bound to this binding. */
|
|
22
|
+
readonly actions: Actions;
|
|
23
|
+
/** The errors every rpc declares besides its action's own, as a list. */
|
|
24
|
+
readonly error: E;
|
|
25
|
+
/**
|
|
26
|
+
* The native group of one rpc per action, named after it, as clients see it:
|
|
27
|
+
* `RpcClient.make(Rpc.group)` is the native client.
|
|
28
|
+
*/
|
|
29
|
+
readonly group: RpcGroup.RpcGroup<RpcOf<Actions[number], E, D>>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Any RPC binding. Its group is any native group here, since native groups are invariant in
|
|
33
|
+
* their rpcs; `actions` carries the types.
|
|
34
|
+
*/
|
|
35
|
+
export interface Any {
|
|
36
|
+
readonly authentication?: Any$2 | undefined;
|
|
37
|
+
readonly actions: ReadonlyArray<Any$1>;
|
|
38
|
+
readonly error: Errors;
|
|
39
|
+
readonly group: RpcGroup.Any;
|
|
40
|
+
}
|
|
41
|
+
/** Contract-level configuration shared by servers and clients. */
|
|
42
|
+
export interface Options<E extends Errors = Errors> {
|
|
43
|
+
/** Layer middleware's errors, which every rpc declares: one schema, or a list. */
|
|
44
|
+
readonly error?: E | E[number];
|
|
45
|
+
readonly authentication?: Any$2;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Bind actions once, for servers and clients alike, each as an rpc named after it, taking
|
|
49
|
+
* its action's input and answering its success. Every rpc declares its action's errors, the
|
|
50
|
+
* binding's `error`, and the built-in `InvalidInput`, `Unauthenticated` and `Forbidden`, so
|
|
51
|
+
* clients decode each as a typed failure; a protected one also declares what its descriptor's
|
|
52
|
+
* verifier fails with. Browser-safe, as an HTTP binding is.
|
|
53
|
+
* Guide: `docs/ActionRpc.md` in this package; in an application: `examples/live-rpc/`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function make<const Actions extends ReadonlyArray<Any$1 & {
|
|
56
|
+
readonly caller: typeof Anyone;
|
|
57
|
+
}>>(actions: Actions): Binding<Actions, [], undefined>;
|
|
58
|
+
export declare function make<const Actions extends ReadonlyArray<Any$1>, const O extends Options = {}>(actions: Actions, options: O & Required<Actions[number]> & Matching<Actions[number], NoInfer<DescriptorOf<O>>> & NoInfer<Known<O, Options>>): Binding<Actions, ErrorsOf<O>, DescriptorOf<O>>;
|
|
59
|
+
export declare function make<const Actions extends ReadonlyArray<Any$1>>(actions: Actions & PublicOnly<Actions>): Binding<Actions, [], undefined>;
|
|
60
|
+
type Middleware = ReadonlyArray<RpcMiddleware.AnyService>;
|
|
61
|
+
type IdOf<K> = K extends Context.Key<infer I extends RpcMiddleware.AnyId, unknown> ? I : never;
|
|
62
|
+
type Through<M extends Middleware, R> = number extends M["length"] ? R | RpcMiddleware.Requires<IdOf<M[number]>> : M extends readonly [infer K, ...infer Rest extends Middleware] ? Through<Rest, true extends Types.IsUnion<IdOf<K>> ? R | RpcMiddleware.Requires<IdOf<K>> : RpcMiddleware.ApplyServices<IdOf<K>, R>> : R;
|
|
63
|
+
type ForClient<K> = K extends {
|
|
64
|
+
readonly requiredForClient: infer F;
|
|
65
|
+
} ? true extends F ? K : never : never;
|
|
66
|
+
type ServerOnly<M extends Middleware, E extends Errors> = [Exclude<RpcMiddleware.Error<IdOf<M[number]>>, Extract<Certain<E>, BindingError>["Type"] | BuiltIn> | ForClient<M[number]>] extends [never] ? unknown : {
|
|
67
|
+
readonly "Layer middleware fails only with the binding's errors and needs no client": never;
|
|
68
|
+
};
|
|
69
|
+
type LayerRequest<App, A extends Any$1, M extends Middleware> = Exclude<Through<M, RemoteRequest<App, A>>, [Extract<Serving<App, A>, {
|
|
70
|
+
readonly caller: typeof Anyone;
|
|
71
|
+
}>] extends [never] ? Identity<Protected<Serving<App, A>>> : never>;
|
|
72
|
+
type RpcLayer<B extends Any, Apps extends Served, M extends Middleware, A extends Any$1 = B["actions"][number]> = Layer.Layer<never, BuildError<Holding<Member<Apps>, A>, A>, ServerBuildServices<Holding<Member<Apps>, A>, A> | RpcServer.Protocol | HttpRouter.Request.From<"Requires", LayerRequest<Member<Apps>, A, M>> | ServedProvider<Member<Apps>, A, B extends {
|
|
73
|
+
readonly authentication: infer D;
|
|
74
|
+
} ? D : never> | IdOf<M[number]>>;
|
|
75
|
+
/** `ActionRpc.layer`'s options. */
|
|
76
|
+
export interface LayerOptions<M extends Middleware = [], A extends Any$1 = never> {
|
|
77
|
+
/**
|
|
78
|
+
* The actions it serves, among the binding's actions the implementations hold: `[GetUser]`.
|
|
79
|
+
* Defaults to every one of them. A layer whose middleware requires the identity lists
|
|
80
|
+
* protected actions only.
|
|
81
|
+
*/
|
|
82
|
+
readonly actions?: ReadonlyArray<A> | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Native rpc middleware every served rpc runs, the first innermost: inside the
|
|
85
|
+
* authentication of a protected rpc, outside its decoding. Server-only: the binding's group,
|
|
86
|
+
* which clients use, carries none. A middleware requiring the identity needs a layer
|
|
87
|
+
* serving only protected actions.
|
|
88
|
+
*/
|
|
89
|
+
readonly middleware?: M;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Serve the binding's actions among `implementations` with Effect's own `RpcServer`, or those
|
|
93
|
+
* its `actions` lists, as `ActionHttp.layer` selects them. The host provides the protocol,
|
|
94
|
+
* such as `RpcServer.layerProtocolWebsocket` or `layerProtocolHttp` and its JSON serialization:
|
|
95
|
+
* the layer refuses to build under a protocol that does not speak JSON. A protected rpc
|
|
96
|
+
* authenticates each message from its headers with the binding's descriptor, outermost; then
|
|
97
|
+
* the layer's `middleware` runs, then the input is decoded, a failure answering `InvalidInput`,
|
|
98
|
+
* then the implementation's `authorize` and the handler, as on every surface.
|
|
99
|
+
*
|
|
100
|
+
* The server serves its own copy of the binding's group, whose payloads, successes and errors
|
|
101
|
+
* are the actions' JSON and which carries no native middleware: the protocol decodes a payload before
|
|
102
|
+
* any middleware runs, and answers one that does not decode with a defect, so each handler
|
|
103
|
+
* runs the authentication and the middleware itself, then decodes the input. A defect is
|
|
104
|
+
* logged with its cause, reported to the call's `ErrorReporter`s, and answered as its
|
|
105
|
+
* request's generic failure, never failing the connection's other calls; a declared failure is
|
|
106
|
+
* the caller's answer, and not reported.
|
|
107
|
+
*
|
|
108
|
+
* The layer fails as the builders do, needs at startup what they need, and per request what
|
|
109
|
+
* each implementation's authorization and the handlers of the actions it serves need, as
|
|
110
|
+
* router request markers, until its `middleware` provides them: over a router-mounted
|
|
111
|
+
* protocol, the route's request context does too. The identity of a protected action is
|
|
112
|
+
* provided by its authentication alone.
|
|
113
|
+
*/
|
|
114
|
+
export declare function layer<const B extends Any, const Apps extends Served>(binding: B, implementations: Apps): RpcLayer<B, Apps, []>;
|
|
115
|
+
export declare function layer<const B extends Any, const Apps extends Served, const O extends LayerOptions<Middleware, B["actions"][number]>>(binding: B, implementations: Apps, options: O & ServerOnly<MiddlewareOf<O, Middleware>, B["error"]> & NoInfer<Known<O, LayerOptions<Middleware>>>): RpcLayer<B, Apps, MiddlewareOf<O, Middleware>, Selected<O, B["actions"][number]>>;
|
|
116
|
+
/**
|
|
117
|
+
* What one call of an action `A` fails with, beside the binding's errors `E`: its declared and
|
|
118
|
+
* built-in errors, and Effect's own `RpcClientError`. Input that does not encode is
|
|
119
|
+
* `InvalidInput`, and nothing is sent; an answer that does not decode is a defect, as the
|
|
120
|
+
* native client makes it.
|
|
121
|
+
*/
|
|
122
|
+
export type MethodError<A extends Any$1, E extends BindingError = never> = A["error"][number]["Type"] | E["Type"] | BuiltIns["Type"] | RpcClientError.RpcClientError;
|
|
123
|
+
type Method<A extends Any$1, E extends BindingError> = Call<A, Effect.Effect<A["success"]["Type"], MethodError<A, E>>>;
|
|
124
|
+
/**
|
|
125
|
+
* Every action of the binding `B`, as `client.<action>(input)`: a protected one may also fail
|
|
126
|
+
* with what its descriptor's verifier declares.
|
|
127
|
+
*/
|
|
128
|
+
export type Client<B extends Any> = { readonly [A in B["actions"][number] as A["name"]]: Method<A, B["error"][number] | VerifierError<A, B["authentication"]>>; };
|
|
129
|
+
/**
|
|
130
|
+
* The native `RpcClient.make` options `client` takes: `spanPrefix`, `spanAttributes`,
|
|
131
|
+
* `generateRequestId` and `disableTracing`. `flatten` changes the client's shape, which the
|
|
132
|
+
* method types cannot follow.
|
|
133
|
+
*/
|
|
134
|
+
export type ClientOptions = Omit<NonNullable<Parameters<typeof RpcClient.make>[1]>, "flatten">;
|
|
135
|
+
/**
|
|
136
|
+
* Effect's native `RpcClient` for a binding, one method per action taking the action's input
|
|
137
|
+
* directly: `client.greet({ name })`, as `ActionHttp.client`'s. The argument may be omitted
|
|
138
|
+
* when `{}` is a valid input. Requires the native client `Protocol` and a `Scope`, as
|
|
139
|
+
* `RpcClient.make` does; headers set with `RpcClient.withHeaders` around a call go with its
|
|
140
|
+
* message. The native client itself stays available: `RpcClient.make(Rpc.group)`.
|
|
141
|
+
*
|
|
142
|
+
* A call fails with a declared error value (the action's own, the binding's, a built-in
|
|
143
|
+
* `InvalidInput`, `Unauthenticated` or `Forbidden`, or on a protected rpc its descriptor's),
|
|
144
|
+
* or with Effect's own `RpcClientError` when the server could not be reached.
|
|
145
|
+
*/
|
|
146
|
+
export declare function client<const B extends Any>(binding: B, options?: ClientOptions): Effect.Effect<Client<B>, never, RpcClient.Protocol | Scope.Scope>;
|
|
147
|
+
//#endregion
|