@gjermundgaraba/effect-actions 0.4.0 → 0.5.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.
@@ -2,46 +2,28 @@
2
2
 
3
3
  Two helpers for hosts that own identity: router middleware that provides a request-scoped
4
4
  identity service, and RFC 9728 protected-resource discovery with bearer challenges. Token
5
- verification, login, and consent stay in the application. Authorization is a group's
6
- pre-handler hook ([ActionGroup.md](ActionGroup.md)), not part of this module.
5
+ verification, login, and consent stay in the application. Authorization belongs in each adapter's
6
+ `before` hook ([guarantees.md](guarantees.md)), not in this module or the group.
7
7
 
8
8
  ## API
9
9
 
10
- ```ts
11
- import * as Authentication from "@gjermundgaraba/effect-actions/Authentication";
10
+ Import `@gjermundgaraba/effect-actions/Authentication`.
12
11
 
13
- /** Provide `service` on every request, or send the response `authenticate` fails with. */
14
- const middleware: <I, A, R>(
15
- service: Context.Key<I, A>,
16
- authenticate: Effect.Effect<A, HttpServerResponse.HttpServerResponse, R>,
17
- ) => HttpRouter.Middleware<{
18
- provides: I;
19
- handles: never;
20
- error: never;
21
- requires: Exclude<R, HttpRouter.Provided>;
22
- layerError: never;
23
- layerRequires: never;
24
- }>;
25
-
26
- const protectedResource: (options: ProtectedResourceOptions) => {
27
- layer: Layer.Layer<never, never, HttpRouter.HttpRouter>; // GET/HEAD /.well-known/oauth-protected-resource<path>
28
- metadataUrl: string;
29
- challenge: (challenge?: BearerChallengeOptions) => string; // WWW-Authenticate value
30
- };
31
-
32
- interface ProtectedResourceOptions {
33
- readonly resource: string; // exact OAuth resource identifier; its path and query select the discovery path
34
- readonly authorizationServers: NonEmptyReadonlyArray<string>;
35
- readonly scopesSupported?: ReadonlyArray<string>;
36
- readonly resourceName?: string;
37
- }
12
+ | API | Purpose |
13
+ | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
14
+ | `middleware(service, authenticate)` | Native router middleware: provide an identity service per request, or return the response authentication fails with. |
15
+ | `protectedResource(options)` | Public RFC 9728 discovery routes, metadata URL and bearer challenge builder. |
16
+ | `discovery.layer`, `discovery.metadataUrl` | Router layer and public discovery URL. |
17
+ | `discovery.challenge(options?)` | Escaped `WWW-Authenticate` header value. |
38
18
 
39
- interface BearerChallengeOptions {
40
- readonly error?: "invalid_token" | "insufficient_scope";
41
- readonly errorDescription?: string;
42
- readonly scope?: string; // scopes needed for this request, space-separated
43
- }
44
- ```
19
+ `authenticate` is an Effect producing the identity or failing with `HttpServerResponse`.
20
+ Its dependencies remain request requirements. Provide the returned middleware's `.layer`
21
+ to routes requiring that identity.
22
+
23
+ | Option type | Fields |
24
+ | -------------------------- | ------------------------------------------------------------------------------------------------------------ |
25
+ | `ProtectedResourceOptions` | Required `resource` and nonempty `authorizationServers`; optional `scopesSupported`, `resourceName`. |
26
+ | `BearerChallengeOptions` | Optional `error` (`invalid_token` or `insufficient_scope`), `errorDescription`, and space-separated `scope`. |
45
27
 
46
28
  ## Canonical
47
29
 
@@ -88,7 +70,7 @@ export const authentication = Authentication.middleware(
88
70
 
89
71
  export const routes = Layer.mergeAll(
90
72
  discovery.layer, // public
91
- Http.layer(UserApp).pipe(Layer.provide(authentication.layer)),
73
+ Http.layer([UserApp]).pipe(Layer.provide(authentication.layer)),
92
74
  mcp.pipe(Layer.provide(authentication.layer)),
93
75
  );
94
76
  ```
package/docs/CONTEXT.md CHANGED
@@ -12,13 +12,13 @@ use these words with these meanings.
12
12
  - **Handler**: a function from decoded input to an Effect of decoded success, failing only with the action's declared errors, possibly requiring services.
13
13
  - **Implementation**: a nominal binding of a complete handler record to a group, made by `group.implement`. Carries build-time error and requirement channels. Adapters accept implementations; `ActionCatalog` accepts contracts.
14
14
  - **Builder**: the record or Effect passed to `implement`. Runs once per adapter layer that serves the implementation.
15
- - **Pre-handler hook**: the `before` function an adapter binds. Runs once per invocation of that surface, with the selected action contract, before its handler. Fails with the surface's own errors. Its services are request-time requirements.
15
+ - **Pre-handler hook**: the `before` function an adapter binds. Runs once after successful input decoding, with the selected action contract, before its handler. Invalid input skips the hook. Fails with the surface's own errors. Its services are request-time requirements.
16
16
  - **Build-time requirement**: a service yielded in the builder. Resolved when an adapter layer is built.
17
17
  - **Request-time requirement**: a service yielded inside a handler. Supplied per invocation: by router middleware for HTTP-hosted adapters, by the host for Toolkit, CLI, and stdio.
18
18
  - **Adapter** (also **projection**): a module that maps implementations or contracts onto one surface: `ActionHttp`, `ActionMcp`, `ActionToolkit`, `ActionCli`, `ActionCliClient`, `ActionCatalog`. Each adapter reads only what it serves and validates only its own namespace.
19
19
  - **Binding**: the value an adapter returns before anything runs. `ActionHttp.make` returns the HTTP binding (`groups`, `api`, `layer`); `ActionToolkit.make` returns the Toolkit binding (`toolkit`, `layer`).
20
20
  - **Contract map**: the record `ActionGroup.contracts` returns, keyed `<group>.<action>`, with each action's exact type. The shared identity of an action across routes, tools and commands.
21
- - **Declared error**: an error schema listed on an action or inherited from its group. Handlers may fail with it. Encoded on every transport with its `httpApiStatus`.
21
+ - **Declared error**: an error schema listed on an action or inherited from its group. Handlers may fail with it. HTTP encodes it with its `httpApiStatus`; MCP returns encoded error text in an `isError` result. Toolkit returns a native failure result, and CLI fails its command Effect without serializing the error.
22
22
  - **Schema-error policy**: a group option mapping Effect's `HttpApiSchemaError` (decode or encode failure) to a declared policy error with a status. HTTP only.
23
23
  - **Policy error**: an error a schema-error policy may answer with. Part of the transport contract, never returnable by a handler.
24
24
  - **Surface error**: an error declared on an adapter binding rather than on an action. Produced by middleware around the surface or by its pre-handler hook, never by a handler; declared so typed callers decode it.
@@ -26,7 +26,7 @@ use these words with these meanings.
26
26
  - **Tool**: the MCP or Toolkit projection of an MCP-enabled action, named by `mcp.name` and carrying its hints.
27
27
  - **Endpoint**: one `ActionMcp.layerHttp` mount. One route, one middleware set, one tool registry.
28
28
  - **Document**: the OpenAPI output of `Http.api`, produced by Effect (`OpenApi.fromApi`).
29
- - **Catalog**: the offline JSON description of groups produced by `ActionCatalog.make`. Descriptive only.
29
+ - **Catalog**: the offline JSON description of groups produced by `ActionCatalog.make`, with standalone JSON Schemas for encoded values. Descriptive only.
30
30
  - **Identity**: the per-request principal, provided by `Authentication.middleware` under an application-owned tag. Never provided at startup.
31
31
 
32
32
  ## Seams
package/docs/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # effect-actions reference
2
2
 
3
3
  Reference for `@gjermundgaraba/effect-actions`, written for coding agents. Every page is a
4
- card with the same sections: **API** (signatures), **Canonical** (the one right way to write
4
+ card with the same sections: **API** (inventory and options), **Canonical** (the one right way to write
5
5
  it), **Rules** (must and never), **Failure modes** (what you see when it is wrong, and the fix).
6
- Read the card for a module before writing code that uses it. Vocabulary is defined in
6
+ Read the card for a module before writing code that uses it. Exported TypeScript declarations are the exact signature reference. Vocabulary is defined in
7
7
  [CONTEXT.md](CONTEXT.md); the pages use those terms exactly.
8
8
 
9
9
  Package facts that apply everywhere:
@@ -66,11 +66,13 @@ const app = Actions.implement({
66
66
  });
67
67
 
68
68
  export const routes = Layer.mergeAll(
69
- Http.layer({}, app),
70
- ActionMcp.layerHttp(
71
- { protocols: [McpProtocol.v2026_07_28], name: "greetings", version: "1.0.0", path: "/mcp" },
72
- app,
73
- ),
69
+ Http.layer([app]),
70
+ ActionMcp.layerHttp([app], {
71
+ protocols: [McpProtocol.v2026_07_28],
72
+ name: "greetings",
73
+ version: "1.0.0",
74
+ path: "/mcp",
75
+ }),
74
76
  );
75
77
  ```
76
78
 
@@ -83,3 +85,7 @@ Repository directory `examples/` ([on GitHub](https://github.com/gjermundgaraba/
83
85
  - `quickstart.ts`, `quickstart-client.ts`: the minimal program and its typed client.
84
86
  - `contracts.ts`, `handlers.ts`, `app.ts`, `server.ts`: an authenticated application with three groups, per-group middleware, two MCP endpoints, OpenAPI and Swagger.
85
87
  - `toolkit.ts`, `catalog.ts`, `cli.ts`, `cli-client.ts`, `mcp-stdio.ts`: one file per other projection.
88
+
89
+ - `mcp-browser.ts`: a stateless MCP endpoint with a separate browser CORS policy.
90
+ - `toolkit-authorized.ts`: native Toolkit invocation with authorization and correctly scoped identity.
91
+ - `testing.ts`: in-memory HTTP and MCP calls with cleanup.
package/docs/Testing.md CHANGED
@@ -5,43 +5,28 @@ loads the optional `@modelcontextprotocol/client` peer and nothing else does.
5
5
 
6
6
  ## API
7
7
 
8
- ```ts
9
- import * as Testing from "@gjermundgaraba/effect-actions/Testing";
10
- import * as TestingClient from "@gjermundgaraba/effect-actions/TestingClient";
8
+ Import `@gjermundgaraba/effect-actions/Testing`; official-client helpers are in the separate
9
+ `@gjermundgaraba/effect-actions/TestingClient` subpath.
11
10
 
12
- type Handler = (request: Request) => Promise<Response>; // HttpRouter.toWebHandler(routes).handler
11
+ | API | Purpose |
12
+ | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
13
+ | `Testing.httpClient(api, handler, options?)` | Native grouped `HttpApiClient` over an in-memory web handler; retains client middleware requirements. |
14
+ | `Testing.mcpRequest(options)` | Build a stateless MCP JSON-RPC `Request`. |
15
+ | `TestingClient.withMcpClient(options, run)` | Connect the official client, run an asynchronous callback, then close it. |
13
16
 
14
- /** Native grouped HttpApiClient calling `handler` in memory. baseUrl defaults to http://localhost. */
15
- const httpClient: <Api extends HttpApi.HttpApi<any, any>>(
16
- api: Api,
17
- handler: Handler,
18
- options?: Parameters<typeof HttpApiClient.make>[1],
19
- ) => Effect.Effect<HttpApiClient.Client<Api>>;
17
+ `httpClient` takes native client options; `baseUrl` defaults to `http://localhost`.
18
+ Its exported `Handler` type is a web request to response Promise.
20
19
 
21
- /** One stateless 2026-07-28 JSON-RPC request with client metadata defaulted. */
22
- const mcpRequest: (options: McpRequestOptions) => Request;
20
+ | MCP option | Meaning |
21
+ | ------------------------------------- | ----------------------------------------------------------------------------------- |
22
+ | `mcpRequest`: `url`, `method` | Required destination and JSON-RPC method. |
23
+ | `mcpRequest`: `params`, `headers` | Optional parameters and request headers. |
24
+ | `withMcpClient`: `fetch`, `path` | Required in-memory request handler and endpoint path. |
25
+ | `withMcpClient`: `baseUrl`, `headers` | Optional base URL (default `http://localhost`) and headers. |
26
+ | `withMcpClient`: `versionNegotiation` | Optional native client negotiation configuration; omitted means its native default. |
23
27
 
24
- interface McpRequestOptions {
25
- readonly url: string | URL;
26
- readonly method: string; // "tools/list", "tools/call", ...
27
- readonly params?: McpRequestParams; // anything JSON.stringify accepts, undefined fields included
28
- readonly headers?: HeadersInit;
29
- }
30
-
31
- /** Connect the official client, run the callback, always close the transport. */
32
- const withMcpClient: <A>(
33
- options: McpClientOptions,
34
- run: (client: Client) => Promise<A>,
35
- ) => Promise<A>;
36
-
37
- interface McpClientOptions {
38
- readonly fetch: (request: Request) => Promise<Response>; // the web handler
39
- readonly path: string;
40
- readonly versionNegotiation?: ClientOptions["versionNegotiation"]; // official client default is legacy
41
- readonly baseUrl?: string | URL; // http://localhost
42
- readonly headers?: HeadersInit;
43
- }
44
- ```
28
+ Exported option/value types: `McpRequestOptions`, `McpRequestParams`, `McpRequestValue`
29
+ from `Testing`, and `McpClientOptions` from `TestingClient`.
45
30
 
46
31
  ## Canonical
47
32
 
@@ -53,25 +38,31 @@ import * as TestingClient from "@gjermundgaraba/effect-actions/TestingClient";
53
38
  import { Http, routes } from "./quickstart.js";
54
39
 
55
40
  const web = HttpRouter.toWebHandler(routes.pipe(Layer.provide(HttpServer.layerServices)));
56
- // call web.dispose() when the test finishes
57
-
58
- // HTTP, typed, in memory.
59
- const greeting = Effect.gen(function* () {
60
- const client = yield* Testing.httpClient(Http.api, web.handler);
61
-
62
- return yield* client.greetings.greet({ payload: { name: "Ada" } });
63
- });
64
41
 
65
- // MCP, raw request, no handshake (2026-07-28 is stateless).
66
- const listed = await web.handler(
67
- Testing.mcpRequest({ url: "http://localhost/mcp", method: "tools/list" }),
68
- );
69
-
70
- // MCP, official client.
71
- const result = await TestingClient.withMcpClient(
72
- { fetch: web.handler, path: "/mcp", versionNegotiation: { mode: { pin: "2026-07-28" } } },
73
- (client) => client.callTool({ name: "greet", arguments: { name: "Ada" } }),
74
- );
42
+ try {
43
+ const greeting = await Effect.runPromise(
44
+ Effect.gen(function* () {
45
+ const client = yield* Testing.httpClient(Http.api, web.handler);
46
+
47
+ return yield* client.greetings.greet({ payload: { name: "Ada" } });
48
+ }),
49
+ );
50
+
51
+ // Raw MCP request; this stateless revision needs no initialize handshake.
52
+ const listed = await web.handler(
53
+ Testing.mcpRequest({ url: "http://localhost/mcp", method: "tools/list" }),
54
+ );
55
+
56
+ // The official client uses the same in-memory handler.
57
+ const result = await TestingClient.withMcpClient(
58
+ { fetch: web.handler, path: "/mcp", versionNegotiation: { mode: { pin: "2026-07-28" } } },
59
+ (client) => client.callTool({ name: "greet", arguments: { name: "Ada" } }),
60
+ );
61
+
62
+ console.log({ greeting, listStatus: listed.status, result });
63
+ } finally {
64
+ await web.dispose();
65
+ }
75
66
  ```
76
67
 
77
68
  ## Rules
@@ -88,4 +79,5 @@ const result = await TestingClient.withMcpClient(
88
79
  - `@modelcontextprotocol/client` not found: only `TestingClient` needs it. Install the peer in devDependencies, or use `Testing.mcpRequest` instead.
89
80
  - Official client fails negotiation against a stateless endpoint: set `versionNegotiation: { mode: { pin: "2026-07-28" } }`.
90
81
  - Handler leaks between tests: `web.dispose()` was not called. Register it with the test runner's cleanup hook.
91
- - 404 from `httpClient`: `routes` did not include `Http.layer({}, app)`, or `HttpServer.layerServices` was not provided to `toWebHandler`.
82
+ - 404 from `httpClient`: the requested route was not mounted, or the path/base URL is wrong. Include `Http.layer([app])` in the served routes.
83
+ - Missing platform-service requirements when constructing the web handler: provide `HttpServer.layerServices` to the routes before `toWebHandler`.
@@ -5,17 +5,17 @@ Rules that hold across every adapter. Module pages link here rather than repeati
5
5
  ## Dependency lifetimes
6
6
 
7
7
  - Handlers receive decoded input, return decoded results, and may fail only with declared errors. Anything else is a defect.
8
- - Each surface binds its own pre-handler hook: `Http.layer({ before }, ...)`, `ActionMcp.layerHttp`/`layerStdio`, `ActionToolkit.make`, `ActionCli.command`/`group`. A bound hook runs once per invocation, before the selected handler and outside its span, for every action that surface serves. There is no per-action opt-out and no group that escapes it; only calling `build` directly does, and that is not dispatch.
8
+ - Each surface binds its own pre-handler hook: `Http.layer(apps, { before })`, `ActionMcp.layerHttp`/`layerStdio`, `ActionToolkit.make`, `ActionCli.command`/`group`. After successful input decoding, a bound hook runs once before the selected handler and outside its span. There is no per-action opt-out; direct `build` calls bypass dispatch.
9
9
  - The hook receives the selected action contract (`Action.Any`), so a policy reads `action.access`, `action.name` or `action.mcp` instead of a hand-maintained list. It is not authentication: establish identity in middleware, then let the hook authorize what that identity may do.
10
- - It fails with the surface's own `errors`, the same list that declares what the surface answers instead of a handler, so a refusal is encoded exactly like a declared error: the schema's `httpApiStatus` on HTTP, an `isError` tool result on MCP, a returned failure on the Toolkit. `ActionCli` declares no errors because it encodes nothing; there a refusal is a typed failure of the command effect.
10
+ - It fails with the surface's own `errors`, the same list that declares what the surface answers instead of a handler, so a refusal is encoded exactly like a declared error: the schema's `httpApiStatus` on HTTP, an `isError` tool result on MCP, a returned failure on the Toolkit. `ActionCli` declares no surface errors because it does not serialize failures; there a refusal is a typed failure of the command effect.
11
11
  - Its services are request-time requirements, like a handler's, and they join the adapter's request context. A local caller (`ActionCli`, `ActionToolkit`, stdio) supplies them itself.
12
- - Ordering differs by surface, because each hooks into its own transport: HTTP runs the hook **before** the request payload is decoded, so an unauthorized caller learns nothing about the input schema. MCP, the Toolkit and the CLI run it after the native transport has decoded the arguments, since decoding is the transport's own first step there.
12
+ - All surfaces decode input before dispatch. Invalid input skips the hook and handler, so unauthorized callers can receive schema errors. Use outer native HTTP middleware for admission that must run before decoding; a pre-handler hook does not provide that guarantee.
13
13
  - Build-time services are those yielded in the `implement` builder Effect. They resolve once per adapter layer, in that layer's scope. An implementation served by two adapters is built twice. Share state through a Layer provided to the adapters, never through the builder.
14
14
  - Request-time services are those yielded inside a handler. For HTTP-hosted adapters (`ActionHttp`, `ActionMcp.layerHttp`) they appear as `HttpRouter.Request.From<"Requires", R>` and are supplied by router middleware, `HttpRouter.provideRequest`, or request context. For `ActionToolkit`, `ActionCli`, and `ActionMcp.layerStdio` the host supplies them at invocation.
15
15
  - Use distinct tags for build-time capabilities and request-scoped identity. Never provide an identity or tenant tag in a startup layer or root context. The adapters use native Effect context capture and merging: a startup value under a tag can shadow a request value or satisfy a missing one. Types verify presence, not provenance.
16
- - Authentication must establish identity on every protected request. Types cannot verify that a handler performed authorization; the `before` hook is the one place the library guarantees runs first, so put the check there rather than in each handler.
17
- - `access` is contract metadata, required on every action and kept as a literal type. Nothing in the library reads it: adapters ignore it and the catalog omits it. It exists so a hook can switch on what an action does instead of on its name.
18
- - `ActionCli` acquires the implementation per invocation and releases it after the call. `ActionToolkit.layer` acquires it once for the layer's lifetime.
16
+ - Authentication must establish identity on every protected request. Types cannot verify that a handler performed authorization; the `before` hook is the one place the library guarantees runs before the handler, so put the check there rather than in each handler.
17
+ - `access` is contract metadata, required on every action and kept as a literal type. It supplies the default MCP `readOnly` hint and the `action.access` span/log annotation, but enforces no authorization. A hook can switch on what an action does instead of on its name. The catalog omits it.
18
+ - `ActionCli` acquires the implementation per invocation and releases it after the call. `ActionToolkit.make(...).layer` acquires it once for the layer's lifetime.
19
19
 
20
20
  ## Wire behavior
21
21
 
@@ -26,14 +26,14 @@ Defaults without a schema-error policy. HTTP is a native `HttpApi`; MCP is a nat
26
26
  | Input | decoded with the schema's JSON codec; failure is an empty **400** | decoded with the schema's JSON codec; failure is `InvalidParams`, presented as an `isError` result |
27
27
  | Success | the success codec's encoding as the body: `{"id":"1","name":"Ada"}`, `42` | `structuredContent: { value: <encoded> }` |
28
28
  | Declared error | encoded by its schema with its `httpApiStatus` (unannotated: 500): `{"_tag":"UserNotFound","id":"x"}` | `isError: true`; text content is the same JSON; no `structuredContent` |
29
- | Pre-handler hook failure | the same, using the hook's error schema, before the payload is decoded; the handler never runs | the same `isError` result, after arguments are decoded; the handler never runs |
29
+ | Pre-handler hook failure | the same, using the hook's error schema, after input decoding; the handler never runs | the same `isError` result, after arguments are decoded; the handler never runs |
30
30
  | Invalid output encoding | empty **400** (`HttpApiSchemaError`) | `isError: true`, text `Tool execution failed due to an internal server error.`; cause logged, not sent |
31
31
  | Defect | empty **500** | same generic `isError` result; cause logged, not sent |
32
32
  | Unknown path or method | 404 | n/a |
33
33
  | Invalid JSON / content type | 400 / 415 | n/a |
34
34
 
35
35
  - A group `schemaError` policy replaces the empty 400s on HTTP with a declared error and status. It never affects MCP. Details in [ActionGroup.md](ActionGroup.md).
36
- - Every HTTP response with a status of 400 or more carries `cache-control: no-store`. Successful responses are left to the host. An error response has no other added headers: a hook refusal and a handler error are plain declared errors, and challenge headers such as `WWW-Authenticate` belong to the admission middleware that answers before the router reaches these routes.
36
+ - `ActionHttp` sets no headers of its own. The host owns cache and challenge headers; `Authentication.middleware` marks its responses `cache-control: no-store`. Hook refusals and handler failures are ordinary declared error responses.
37
37
  - Routes are `POST <apiPath>/<group>/<action>`. Operation IDs are `<group>.<action>`. Effect generates OpenAPI component names and references.
38
38
  - `ActionHttp.make`'s `errors` are declared on every endpoint so clients decode the surface's own failures (401, 403, 429, 503). They are produced by middleware or by the binding's hook, never by a handler. `ActionMcp` and `ActionToolkit` declare their own `errors` the same way, joined into every tool's failure schema. Two errors may share an HTTP status; their `_tag`s must differ, because the client decodes a status by trying the schemas declared for it.
39
39
  - HTTP strips undeclared input fields. MCP tools are strict (`Tool.Strict`): undeclared arguments are an invalid-arguments result and input schemas publish `additionalProperties: false`. HTTP is not strict, and cannot be made strict without a hack: Effect merges one `HttpApi.ParseOptions` per endpoint and uses it for payload decoding _and_ error encoding, so `onExcessProperty: "error"` also rejects a `TaggedError` instance's own `message` and `stack`, turning a declared 409 into an empty 500. Making the payload schema itself strict would require wrapping it in an open schema, which erases its OpenAPI shape. Declare the fields you accept and treat extra HTTP fields as ignored.
@@ -46,7 +46,7 @@ Defaults without a schema-error policy. HTTP is a native `HttpApi`; MCP is a nat
46
46
 
47
47
  ## Observability
48
48
 
49
- - Each handler runs in a span named `<group>.<action>`, a child of the transport's request span. The pre-handler hook, decoding and encoding happen outside the handler span.
49
+ - Each handler runs in a span named `<group>.<action>`, a child of the transport's request span, with attributes `action.group`, `action.name` and `action.access`. Every log line the handler writes carries the same three annotations. The pre-handler hook, decoding and encoding happen outside the handler span.
50
50
  - MCP defects and encoding failures are logged with their cause and answered generically.
51
51
 
52
52
  ## MCP transport
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjermundgaraba/effect-actions",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Define Effect action contracts once and project them onto HTTP, MCP, native Toolkits, catalogs, and CLIs.",
5
5
  "homepage": "https://github.com/gjermundgaraba/effect-actions#readme",
6
6
  "bugs": {