@gjermundgaraba/effect-actions 0.1.0-rc.0 → 0.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +249 -75
  2. package/dist/Action.d.mts +35 -27
  3. package/dist/Action.mjs +9 -10
  4. package/dist/ActionCatalog.d.mts +31 -0
  5. package/dist/ActionCatalog.mjs +36 -0
  6. package/dist/ActionCli.d.mts +27 -0
  7. package/dist/ActionCli.mjs +26 -0
  8. package/dist/ActionCliClient.d.mts +45 -0
  9. package/dist/ActionCliClient.mjs +52 -0
  10. package/dist/ActionGroup.d.mts +25 -16
  11. package/dist/ActionGroup.mjs +18 -19
  12. package/dist/ActionHttp.d.mts +39 -0
  13. package/dist/ActionHttp.mjs +67 -0
  14. package/dist/ActionMcp.d.mts +41 -0
  15. package/dist/ActionMcp.mjs +25 -0
  16. package/dist/ActionToolkit.d.mts +37 -0
  17. package/dist/ActionToolkit.mjs +10 -0
  18. package/dist/Authentication.d.mts +46 -0
  19. package/dist/Authentication.mjs +55 -0
  20. package/dist/Testing.d.mts +36 -0
  21. package/dist/Testing.mjs +42 -0
  22. package/dist/TestingClient.d.mts +16 -0
  23. package/dist/{testing/client.mjs → TestingClient.mjs} +5 -6
  24. package/dist/{rolldown-runtime-D7D4PA-g.mjs → _virtual/_rolldown/runtime.mjs} +1 -1
  25. package/dist/internal/actions.d.mts +18 -0
  26. package/dist/internal/actions.mjs +16 -0
  27. package/dist/internal/cli.d.mts +29 -0
  28. package/dist/internal/cli.mjs +36 -0
  29. package/dist/internal/implementation.d.mts +39 -0
  30. package/dist/internal/implementation.mjs +37 -0
  31. package/dist/internal/tools.mjs +84 -0
  32. package/package.json +16 -12
  33. package/dist/authentication.d.mts +0 -27
  34. package/dist/authentication.mjs +0 -31
  35. package/dist/http.d.mts +0 -53
  36. package/dist/http.mjs +0 -74
  37. package/dist/implementation-BkLqMWtv.mjs +0 -32
  38. package/dist/implementation-D75QAcD3.d.mts +0 -21
  39. package/dist/index.d.mts +0 -6
  40. package/dist/index.mjs +0 -6
  41. package/dist/mcp.d.mts +0 -46
  42. package/dist/mcp.mjs +0 -189
  43. package/dist/testing/client.d.mts +0 -11
  44. package/dist/testing.d.mts +0 -10
  45. package/dist/testing.mjs +0 -34
@@ -1,21 +0,0 @@
1
- import { Any, t as Action_d_exports } from "./Action.mjs";
2
- import { Effect, Layer, Scope } from "effect";
3
- //#region src/internal/implementation.d.ts
4
- type Erased<R> = {
5
- handle(input: unknown): Effect.Effect<unknown, unknown, R>;
6
- }["handle"];
7
- type Handlers<R> = Readonly<Record<string, Erased<R>>>;
8
- /**
9
- * Opaque implementation value; only its type is public. `R` is required per
10
- * request, `EX`/`RX` describe handler acquisition at Layer build. Layer
11
- * memoization lets both adapters share one handler build per runtime.
12
- */
13
- declare class Implementation<Actions extends ReadonlyArray<Any>, R, EX, RX> {
14
- #private;
15
- readonly actions: Actions;
16
- private constructor();
17
- static make<Actions extends ReadonlyArray<Any>, R, EX, RX>(actions: Actions, build: Effect.Effect<Handlers<R>, EX, RX>): Implementation<Actions, R, EX, Exclude<RX, Scope.Scope>>;
18
- static register<Actions extends ReadonlyArray<Any>, R, EX, RX, Out, E, In>(app: Implementation<Actions, R, EX, RX>, make: (handlers: Handlers<R>) => Layer.Layer<Out, E, In>): Layer.Layer<Out, E | EX, In | RX>;
19
- }
20
- //#endregion
21
- export { Implementation as t };
package/dist/index.d.mts DELETED
@@ -1,6 +0,0 @@
1
- import { t as Action_d_exports } from "./Action.mjs";
2
- import { t as ActionGroup_d_exports } from "./ActionGroup.mjs";
3
- import { t as ActionHttp_d_exports } from "./http.mjs";
4
- import { t as ActionMcp_d_exports } from "./mcp.mjs";
5
- import { t as Authentication_d_exports } from "./authentication.mjs";
6
- export { Action_d_exports as Action, ActionGroup_d_exports as ActionGroup, ActionHttp_d_exports as ActionHttp, ActionMcp_d_exports as ActionMcp, Authentication_d_exports as Authentication };
package/dist/index.mjs DELETED
@@ -1,6 +0,0 @@
1
- import { t as Action_exports } from "./Action.mjs";
2
- import { t as ActionGroup_exports } from "./ActionGroup.mjs";
3
- import { t as ActionHttp_exports } from "./http.mjs";
4
- import { t as ActionMcp_exports } from "./mcp.mjs";
5
- import { t as Authentication_exports } from "./authentication.mjs";
6
- export { Action_exports as Action, ActionGroup_exports as ActionGroup, ActionHttp_exports as ActionHttp, ActionMcp_exports as ActionMcp, Authentication_exports as Authentication };
package/dist/mcp.d.mts DELETED
@@ -1,46 +0,0 @@
1
- import { Any, Codec, SchemaErrorPolicy, t as Action_d_exports } from "./Action.mjs";
2
- import { t as Implementation } from "./implementation-D75QAcD3.mjs";
3
- import { Cause, Layer } from "effect";
4
- import { McpProtocol } from "effect/unstable/ai";
5
- import { HttpRouter } from "effect/unstable/http";
6
- import { NonEmptyReadonlyArray } from "effect/Array";
7
- declare namespace ActionMcp_d_exports {
8
- export { BearerChallengeOptions, Options, ProtectedResourceOptions, layer, protectedResource, protocols };
9
- }
10
- export interface Options<Errors extends ReadonlyArray<Codec> = []> {
11
- readonly schemaError?: SchemaErrorPolicy<Errors>;
12
- readonly name: string;
13
- readonly version: string;
14
- readonly path: HttpRouter.PathInput;
15
- readonly protocols?: NonEmptyReadonlyArray<McpProtocol.ProtocolAdapter>;
16
- readonly allowedOrigins?: ReadonlyArray<string>;
17
- readonly instructions?: string;
18
- }
19
- /** Protocol revisions served over Streamable HTTP; 2026-07-28 is stateless. */
20
- export declare const protocols: NonEmptyReadonlyArray<McpProtocol.ProtocolAdapter>;
21
- /** A Streamable HTTP MCP endpoint serving the group's MCP-enabled actions. */
22
- export declare const layer: <Actions extends ReadonlyArray<Any>, R, EX, RX, Errors extends ReadonlyArray<Codec> = []>(app: Implementation<Actions, R, EX, RX>, options: Options<Errors>) => Layer.Layer<never, EX | Cause.IllegalArgumentError, RX | HttpRouter.HttpRouter | HttpRouter.Request.From<"Requires", R>>;
23
- export interface ProtectedResourceOptions {
24
- /** Exact OAuth resource identifier. HTTPS, or HTTP on loopback for development. */
25
- readonly resource: string;
26
- readonly authorizationServers: NonEmptyReadonlyArray<string>;
27
- readonly scopesSupported?: ReadonlyArray<string>;
28
- readonly resourceName?: string;
29
- }
30
- export interface BearerChallengeOptions {
31
- readonly error?: "invalid_token" | "insufficient_scope";
32
- readonly errorDescription?: string;
33
- /** Space-separated scopes needed for this request, not all supported scopes. */
34
- readonly scope?: string;
35
- }
36
- /**
37
- * Publish RFC 9728 discovery independently from the authenticated MCP route.
38
- * This supplies metadata and challenges; the host still verifies access tokens.
39
- */
40
- export declare const protectedResource: (options: ProtectedResourceOptions) => {
41
- layer: Layer.Layer<never, never, HttpRouter.HttpRouter>;
42
- metadataUrl: string;
43
- challenge: (challenge?: BearerChallengeOptions) => string;
44
- };
45
- //#endregion
46
- export { ActionMcp_d_exports as t };
package/dist/mcp.mjs DELETED
@@ -1,189 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { n as handlerFor, t as Implementation } from "./implementation-BkLqMWtv.mjs";
3
- import { Context, Effect, JsonPointer, Layer, Schema } from "effect";
4
- import { McpProtocol, McpSchema, McpServer, Tool } from "effect/unstable/ai";
5
- import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
6
- //#region src/ActionMcp.ts
7
- var ActionMcp_exports = /* @__PURE__ */ __exportAll({
8
- layer: () => layer,
9
- protectedResource: () => protectedResource,
10
- protocols: () => protocols
11
- });
12
- /** Protocol revisions served over Streamable HTTP; 2026-07-28 is stateless. */
13
- const protocols = [
14
- McpProtocol.v2026_07_28,
15
- McpProtocol.v2025_11_25,
16
- McpProtocol.v2025_06_18,
17
- McpProtocol.v2025_03_26,
18
- McpProtocol.v2024_11_05
19
- ];
20
- const toolResult = (structuredContent, isError) => new McpSchema.CallToolResult({
21
- isError,
22
- structuredContent,
23
- content: [{
24
- type: "text",
25
- text: JSON.stringify(structuredContent)
26
- }]
27
- });
28
- const resolveReference = (name, what, schema, definitions) => {
29
- if (typeof schema.$ref !== "string") return schema;
30
- const reference = schema.$ref;
31
- const path = JsonPointer.parseUriFragment(reference);
32
- const key = path?.[1];
33
- if (path?.length !== 2 || path[0] !== "$defs" || key === void 0) throw new Error(`${name}: unsupported MCP ${what} reference ${reference}`);
34
- const definition = Object.hasOwn(definitions, key) ? definitions[key] : void 0;
35
- if (definition === void 0) throw new Error(`${name}: missing MCP ${what} reference ${reference}`);
36
- return definition;
37
- };
38
- /** MCP `inputSchema` requires a literal object root. */
39
- const inputJsonSchema = (action) => {
40
- const { schema, definitions } = Schema.toJsonSchemaDocument(action.input);
41
- const root = resolveReference(action.name, "input", schema, definitions);
42
- if (root.type !== "object") throw new Error(`${action.name}: MCP input must have an object root; use Action.NoInput for no arguments`);
43
- return Object.keys(definitions).length === 0 ? root : {
44
- ...root,
45
- $defs: definitions
46
- };
47
- };
48
- /** Declared errors are published as-is in `structuredContent`, so each must encode to an object. */
49
- const assertObjectError = (owner, error) => {
50
- const { schema, definitions } = Schema.toJsonSchemaDocument(error);
51
- const visited = /* @__PURE__ */ new Set();
52
- const encodesObject = (candidate) => {
53
- const root = resolveReference(owner, "error", candidate, definitions);
54
- if (root.type === "object" || visited.has(root)) return true;
55
- visited.add(root);
56
- const members = root.anyOf ?? root.oneOf;
57
- return Array.isArray(members) && members.length > 0 && members.every(encodesObject);
58
- };
59
- if (!encodesObject(schema)) throw new Error(`${owner}: MCP error must have an object root`);
60
- };
61
- const registerTool = (action, table, policy) => Effect.gen(function* () {
62
- if (action.mcp === false) return;
63
- const server = yield* McpServer.McpServer;
64
- const inputSchema = yield* Schema.decodeUnknownEffect(McpSchema.ToolJson)(inputJsonSchema(action)).pipe(Effect.orDie);
65
- const errors = [...action.errors, ...policy?.errors ?? []];
66
- for (const error of action.errors) assertObjectError(action.name, error);
67
- const input = Schema.toCodecJson(action.input);
68
- const success = Schema.toCodecJson(action.success);
69
- const failure = Schema.toCodecJson(Schema.Union(errors));
70
- const handle = handlerFor(table, action);
71
- const failureResult = (error) => Schema.encodeUnknownEffect(failure)(error).pipe(Effect.orDie, Effect.map((json) => toolResult(json, true)));
72
- const schemaFailure = (phase, cause) => {
73
- if (policy !== void 0) return failureResult(policy.map({
74
- phase,
75
- cause
76
- }));
77
- if (phase === "input") return Effect.fail(new McpSchema.InvalidParams({ message: cause.message }));
78
- return Effect.die(cause);
79
- };
80
- const successResult = (value) => Schema.encodeUnknownEffect(success)(value).pipe(Effect.matchEffect({
81
- onFailure: (cause) => schemaFailure("output", cause),
82
- onSuccess: (json) => Effect.succeed(toolResult({ value: json }, false))
83
- }));
84
- yield* server.addTool({
85
- tool: new McpSchema.Tool({
86
- name: action.mcp.name,
87
- description: action.description,
88
- inputSchema,
89
- outputSchema: Tool.getJsonSchemaFromSchema(Schema.Struct({ value: action.success })),
90
- annotations: {
91
- readOnlyHint: action.mcp.readOnly,
92
- destructiveHint: action.mcp.destructive
93
- }
94
- }),
95
- annotations: Context.empty(),
96
- handle: (payload) => Schema.decodeUnknownEffect(input)(payload).pipe(Effect.matchEffect({
97
- onFailure: (cause) => schemaFailure("input", cause),
98
- onSuccess: (decoded) => handle(decoded).pipe(Effect.matchEffect({
99
- onSuccess: successResult,
100
- onFailure: failureResult
101
- }))
102
- }))
103
- });
104
- });
105
- const registrationRouter = (router, context) => HttpRouter.HttpRouter.of({
106
- ...router,
107
- add: (method, path, handler, options) => router.add(method, path, handler, options).pipe(Effect.provideContext(context)),
108
- addAll: (routes) => router.addAll(routes).pipe(Effect.provideContext(context)),
109
- prefixed: (prefix) => registrationRouter(router.prefixed(prefix), context)
110
- });
111
- /** A Streamable HTTP MCP endpoint serving the group's MCP-enabled actions. */
112
- const layer = (app, options) => Implementation.register(app, (table) => {
113
- const native = Layer.effectDiscard(Effect.gen(function* () {
114
- if (app.actions.some((action) => action.mcp !== false)) for (const error of options.schemaError?.errors ?? []) assertObjectError("Schema-error policy", error);
115
- yield* Effect.forEach(app.actions, (action) => registerTool(action, table, options.schemaError), { discard: true });
116
- })).pipe(Layer.provide(McpServer.layerHttp({
117
- name: options.name,
118
- version: options.version,
119
- instructions: options.instructions,
120
- path: options.path,
121
- protocols: options.protocols ?? protocols,
122
- allowedOrigins: options.allowedOrigins
123
- })), Layer.fresh);
124
- return Layer.fromBuildMemo((memoMap, scope) => Effect.gen(function* () {
125
- const router = registrationRouter(yield* HttpRouter.HttpRouter, yield* Effect.context());
126
- return yield* Layer.buildWithMemoMap(native, memoMap, scope).pipe(Effect.setContext(Context.make(HttpRouter.HttpRouter, router)));
127
- }));
128
- });
129
- const oauthUrl = (value) => {
130
- const url = new URL(value);
131
- const loopback = [
132
- "localhost",
133
- "127.0.0.1",
134
- "[::1]"
135
- ].includes(url.hostname);
136
- if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback) || url.username !== "" || url.password !== "" || value.includes("#")) throw new Error("OAuth URLs must use HTTPS (or loopback HTTP) without credentials or fragment");
137
- return url;
138
- };
139
- const scopeToken = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
140
- /**
141
- * Publish RFC 9728 discovery independently from the authenticated MCP route.
142
- * This supplies metadata and challenges; the host still verifies access tokens.
143
- */
144
- const protectedResource = (options) => {
145
- const resource = oauthUrl(options.resource);
146
- if (options.authorizationServers.length === 0) throw new Error("An authorization server is required");
147
- for (const issuer of options.authorizationServers) {
148
- oauthUrl(issuer);
149
- if (issuer.includes("?")) throw new Error("Authorization server issuers must not contain a query");
150
- }
151
- for (const scope of options.scopesSupported ?? []) if (!scopeToken.test(scope)) throw new Error("Invalid OAuth scope token");
152
- const path = `/.well-known/oauth-protected-resource${resource.pathname === "/" ? "" : resource.pathname}`;
153
- const discoveryUrl = new URL(resource);
154
- discoveryUrl.pathname = path;
155
- const metadataUrl = discoveryUrl.href;
156
- const target = metadataUrl.slice(discoveryUrl.origin.length);
157
- const metadata = {
158
- resource: options.resource,
159
- authorization_servers: options.authorizationServers,
160
- bearer_methods_supported: ["header"],
161
- ...options.scopesSupported?.length ? { scopes_supported: options.scopesSupported } : {},
162
- ...options.resourceName === void 0 ? {} : { resource_name: options.resourceName }
163
- };
164
- const response = HttpServerResponse.jsonUnsafe(metadata);
165
- return {
166
- layer: HttpRouter.middleware((next) => Effect.gen(function* () {
167
- const request = yield* HttpServerRequest.HttpServerRequest;
168
- const url = new URL(request.url, resource.origin);
169
- if ((request.method === "GET" || request.method === "HEAD") && url.href.slice(url.origin.length) === target) return response;
170
- return yield* next;
171
- }), { global: true }),
172
- metadataUrl,
173
- challenge: (challenge = {}) => {
174
- const parameters = [`resource_metadata="${metadataUrl.replace(/["\\]/g, "\\$&")}"`];
175
- if (challenge.error !== void 0) parameters.push(`error="${challenge.error}"`);
176
- if (challenge.errorDescription !== void 0) {
177
- if (!/^[\x20-\x21\x23-\x5B\x5D-\x7E]*$/.test(challenge.errorDescription)) throw new Error("Invalid OAuth error description");
178
- parameters.push(`error_description="${challenge.errorDescription}"`);
179
- }
180
- if (challenge.scope !== void 0) {
181
- if (!challenge.scope.split(" ").every((scope) => scopeToken.test(scope))) throw new Error("Invalid OAuth challenge scope");
182
- parameters.push(`scope="${challenge.scope}"`);
183
- }
184
- return `Bearer ${parameters.join(", ")}`;
185
- }
186
- };
187
- };
188
- //#endregion
189
- export { layer, protectedResource, protocols, ActionMcp_exports as t };
@@ -1,11 +0,0 @@
1
- import { Client } from "@modelcontextprotocol/client";
2
- //#region src/TestingClient.d.ts
3
- export interface McpClientOptions {
4
- readonly path: string;
5
- readonly mode?: "legacy" | "modern";
6
- readonly baseUrl?: string | URL;
7
- readonly headers?: ConstructorParameters<typeof Headers>[0];
8
- }
9
- /** Connect an official client and always close its transport after the callback. */
10
- export declare const withMcpClient: <A>(fetch: (request: Request) => Promise<Response>, run: (client: Client) => Promise<A>, { mode, path, baseUrl, headers }: McpClientOptions) => Promise<A>;
11
- //#endregion
@@ -1,10 +0,0 @@
1
- //#region src/Testing.d.ts
2
- /** A stateless 2026-07-28 request. */
3
- export interface McpRequestOptions {
4
- readonly url: string | URL;
5
- readonly headers?: ConstructorParameters<typeof Headers>[0];
6
- }
7
- export declare const mcpRequest: (method: string, params: (Record<string, unknown> & {
8
- readonly _meta?: Record<string, unknown>;
9
- }) | undefined, options: McpRequestOptions) => Request;
10
- //#endregion
package/dist/testing.mjs DELETED
@@ -1,34 +0,0 @@
1
- //#region src/Testing.ts
2
- const mcpRequest = (method, params = {}, options) => {
3
- const headers = new Headers(options.headers);
4
- for (const [name, value] of Object.entries({
5
- "content-type": "application/json",
6
- accept: "application/json, text/event-stream",
7
- "mcp-protocol-version": "2026-07-28",
8
- "mcp-method": method,
9
- ...typeof params.name === "string" ? { "mcp-name": params.name } : {}
10
- })) headers.set(name, value);
11
- return new Request(options.url, {
12
- method: "POST",
13
- headers,
14
- body: JSON.stringify({
15
- jsonrpc: "2.0",
16
- id: 1,
17
- method,
18
- params: {
19
- ...params,
20
- _meta: {
21
- "io.modelcontextprotocol/clientCapabilities": {},
22
- "io.modelcontextprotocol/clientInfo": {
23
- name: "test",
24
- version: "0"
25
- },
26
- ...params._meta,
27
- "io.modelcontextprotocol/protocolVersion": "2026-07-28"
28
- }
29
- }
30
- })
31
- });
32
- };
33
- //#endregion
34
- export { mcpRequest };