@gjermundgaraba/effect-actions 0.18.2 → 0.19.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 +20 -47
- package/dist/Action.d.mts +96 -47
- package/dist/Action.mjs +11 -14
- package/dist/ActionCli.d.mts +81 -42
- package/dist/ActionCli.mjs +52 -33
- package/dist/ActionHttp.d.mts +84 -43
- package/dist/ActionHttp.mjs +2 -2
- package/dist/ActionMcp.d.mts +54 -27
- package/dist/ActionMcp.mjs +5 -5
- package/dist/ActionRpc.d.mts +48 -35
- package/dist/ActionRpc.mjs +1 -1
- package/dist/ActionToolkit.d.mts +32 -13
- package/dist/ActionToolkit.mjs +10 -7
- package/dist/Authentication.d.mts +92 -40
- package/dist/Authentication.mjs +37 -35
- package/dist/Testing.d.mts +22 -24
- package/dist/authentication/provider.d.mts +1 -4
- package/dist/authentication/provider.mjs +1 -1
- package/dist/authentication/refusal.mjs +2 -1
- package/dist/cli/commands.d.mts +34 -11
- package/dist/cli/commands.mjs +47 -22
- package/dist/contract/caller.mjs +1 -1
- package/dist/contract/declared.mjs +27 -14
- package/dist/contract/errors.d.mts +42 -17
- package/dist/contract/errors.mjs +42 -17
- package/dist/contract/hints.mjs +3 -2
- package/dist/contract/implementation.d.mts +5 -8
- package/dist/contract/implementation.mjs +29 -17
- package/dist/contract/media.d.mts +6 -2
- package/dist/contract/media.mjs +6 -2
- package/dist/contract/names.mjs +14 -12
- package/dist/contract/status.mjs +3 -10
- package/dist/http/client.d.mts +15 -4
- package/dist/http/client.mjs +1 -1
- package/dist/mcp/client.d.mts +21 -8
- package/dist/mcp/client.mjs +6 -8
- package/docs/Action-failures.md +6 -15
- package/docs/Action.md +159 -191
- package/docs/ActionCli-failures.md +12 -92
- package/docs/ActionCli.md +102 -156
- package/docs/ActionHttp-failures.md +14 -76
- package/docs/ActionHttp.md +150 -176
- package/docs/ActionMcp-failures.md +2 -22
- package/docs/ActionMcp.md +79 -170
- package/docs/ActionRpc-failures.md +3 -12
- package/docs/ActionRpc.md +183 -120
- package/docs/ActionToolkit-failures.md +3 -10
- package/docs/ActionToolkit.md +112 -109
- package/docs/Authentication-advanced.md +167 -108
- package/docs/Authentication-failures.md +11 -91
- package/docs/Authentication.md +114 -135
- package/docs/README.md +45 -30
- package/docs/Testing-failures.md +2 -6
- package/docs/Testing.md +126 -159
- package/docs/setup-failures.md +8 -32
- package/docs/setup.md +42 -37
- package/examples/README.md +6 -10
- package/examples/assistant/README.md +20 -38
- package/examples/assistant/src/agent.ts +2 -3
- package/examples/assistant/src/cli.ts +4 -9
- package/examples/assistant/src/commands.ts +6 -4
- package/examples/assistant/src/identity/authorize.ts +3 -3
- package/examples/assistant/src/tasks/Tasks.ts +1 -2
- package/examples/assistant/src/tasks/handlers.ts +3 -3
- package/examples/assistant/src/toolkit.ts +6 -8
- package/examples/assistant/test/commands.test.ts +16 -11
- package/examples/assistant/test/tasks/tasks.test.ts +0 -2
- package/examples/assistant/test/toolkit.test.ts +0 -1
- package/examples/kitchen-sink/README.md +30 -52
- package/examples/kitchen-sink/src/Actions.ts +2 -3
- package/examples/kitchen-sink/src/Api.ts +3 -4
- package/examples/kitchen-sink/src/admin/Admin.ts +1 -1
- package/examples/kitchen-sink/src/admin/authenticate.ts +3 -3
- package/examples/kitchen-sink/src/admin/authorize.ts +1 -2
- package/examples/kitchen-sink/src/agent.ts +1 -1
- package/examples/kitchen-sink/src/assistant/handlers.ts +1 -6
- package/examples/kitchen-sink/src/attachments/Attachments.ts +4 -8
- package/examples/kitchen-sink/src/attachments/download.ts +5 -8
- package/examples/kitchen-sink/src/attachments/handlers.ts +2 -3
- package/examples/kitchen-sink/src/browser.ts +3 -4
- package/examples/kitchen-sink/src/cli.ts +6 -7
- package/examples/kitchen-sink/src/config.ts +2 -0
- package/examples/kitchen-sink/src/identity/Identity.ts +7 -7
- package/examples/kitchen-sink/src/identity/authenticate.ts +3 -5
- package/examples/kitchen-sink/src/identity/authorize.ts +4 -3
- package/examples/kitchen-sink/src/identity/quota.ts +1 -1
- package/examples/kitchen-sink/src/issues/Issues.ts +2 -2
- package/examples/kitchen-sink/src/issues/assistant.ts +3 -3
- package/examples/kitchen-sink/src/issues/guide.ts +2 -3
- package/examples/kitchen-sink/src/ops.ts +3 -6
- package/examples/kitchen-sink/src/request-policy.ts +3 -3
- package/examples/kitchen-sink/src/routes.ts +4 -6
- package/examples/kitchen-sink/src/seed.ts +0 -1
- package/examples/kitchen-sink/src/server.ts +11 -12
- package/examples/kitchen-sink/src/stdio.ts +3 -2
- package/examples/kitchen-sink/src/workspaces/tenant.ts +3 -1
- package/examples/kitchen-sink/test/admin/admin.test.ts +0 -1
- package/examples/kitchen-sink/test/assistant/assistant.test.ts +1 -1
- package/examples/kitchen-sink/test/attachments/attachments.test.ts +1 -2
- package/examples/kitchen-sink/test/identity/identity.test.ts +0 -1
- package/examples/kitchen-sink/test/issues/issues.test.ts +0 -2
- package/examples/kitchen-sink/test/request-policy.test.ts +1 -1
- package/examples/kitchen-sink/test/routes.test.ts +3 -3
- package/examples/live-rpc/README.md +8 -49
- package/examples/live-rpc/src/browser.ts +8 -41
- package/examples/live-rpc/src/chat/Chat.ts +9 -3
- package/examples/live-rpc/src/chat/room.ts +1 -1
- package/examples/live-rpc/src/client.ts +2 -3
- package/examples/live-rpc/src/page.ts +34 -0
- package/examples/live-rpc/src/routes.ts +6 -7
- package/examples/live-rpc/src/server.ts +6 -4
- package/examples/live-rpc/test/browser.test.ts +6 -5
- package/examples/live-rpc/test/routes.test.ts +16 -15
- package/examples/live-rpc/test/server.test.ts +1 -1
- package/examples/mcp-tools/README.md +22 -45
- package/examples/mcp-tools/src/printers/Printers.ts +3 -7
- package/examples/mcp-tools/src/routes.ts +0 -7
- package/examples/mcp-tools/src/server.ts +1 -1
- package/examples/mcp-tools/test/printers/printers.test.ts +2 -3
- package/examples/mcp-tools/test/routes.test.ts +2 -3
- package/examples/notes-service/README.md +9 -43
- package/examples/notes-service/src/cli.ts +0 -3
- package/examples/notes-service/src/commands.ts +7 -17
- package/examples/notes-service/src/identity/authorize.ts +2 -5
- package/examples/notes-service/src/identity/verifier.ts +3 -4
- package/examples/notes-service/src/notes/Notes.ts +2 -6
- package/examples/notes-service/src/notes/store.ts +5 -1
- package/examples/notes-service/src/routes.ts +6 -8
- package/examples/notes-service/src/server.ts +4 -3
- package/examples/notes-service/src/stdio.ts +4 -3
- package/examples/notes-service/test/commands.test.ts +1 -1
- package/examples/notes-service/test/notes/as.ts +6 -0
- package/examples/notes-service/test/notes/idempotency.test.ts +1 -4
- package/examples/notes-service/test/notes/in-process.test.ts +50 -0
- package/examples/notes-service/test/notes/notes.test.ts +3 -42
- package/examples/notes-service/test/routes.test.ts +2 -2
- package/examples/quickstart/README.md +1 -1
- package/examples/quickstart/src/client.ts +1 -0
- package/examples/quickstart/src/server.ts +2 -4
- package/examples/quickstart/test/{greeting/greeting.test.ts → routes.test.ts} +3 -3
- package/package.json +1 -1
- package/docs/Action-in-process.md +0 -93
- package/docs/CONTEXT.md +0 -46
- package/docs/guarantees.md +0 -44
package/docs/ActionMcp.md
CHANGED
|
@@ -5,82 +5,42 @@ after it. `layerHttp` mounts a Streamable HTTP endpoint, for clients that connec
|
|
|
5
5
|
`runStdio` is the whole program of a subprocess a local agent starts. For a model your own code
|
|
6
6
|
calls in process, use [ActionToolkit.md](ActionToolkit.md).
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## API
|
|
13
|
-
|
|
14
|
-
Import `@gjermundgaraba/effect-actions/ActionMcp`.
|
|
15
|
-
|
|
16
|
-
| API | Purpose |
|
|
17
|
-
| ------------------------------------- | ----------------------------------------------------------- |
|
|
18
|
-
| `layerHttp(implementations, options)` | A Layer serving the actions as tools at one HTTP endpoint. |
|
|
19
|
-
| `runStdio(implementations, options)` | An Effect serving them on standard I/O until stdin closes. |
|
|
20
|
-
| `Options` | Type of `runStdio`'s options. |
|
|
21
|
-
| `LayerHttpOptions` | Type of `layerHttp`'s options: `Options` and HTTP's own. |
|
|
22
|
-
| `client(actions, options?)` | An Effect of a client calling each action's tool. |
|
|
23
|
-
| `request(method, params?, options?)` | One MCP request, as the native `HttpClientRequest` to send. |
|
|
24
|
-
| `McpCallError` | A client method's undecodable answer, by tagged `reason`. |
|
|
25
|
-
|
|
26
|
-
`implementations` is one implementation or a list.
|
|
27
|
-
|
|
28
|
-
| Option | Meaning |
|
|
29
|
-
| ------------------------------------ | ----------------------------------------------------------- |
|
|
30
|
-
| `name`, `version` | Required server information. |
|
|
31
|
-
| `description`, `websiteUrl`, `icons` | Optional server information. |
|
|
32
|
-
| `instructions` | Optional text for the model about the server as a whole. |
|
|
33
|
-
| `extensions` | Optional native capability extensions. |
|
|
34
|
-
| `actions` | The implementations' actions that are tools; default all. |
|
|
35
|
-
| `features` | One Layer of native resources, prompts and tools to serve. |
|
|
36
|
-
| `path` | HTTP only: the endpoint's path; defaults to `/mcp`. |
|
|
37
|
-
| `allowedOrigins` | HTTP only: the exact origins allowed; none by default. |
|
|
38
|
-
| `allowSubscriptions` | HTTP only: 2026-07-28 subscriptions; default if `features`. |
|
|
39
|
-
| `requestScoped` | HTTP only: `true` runs a server per request. |
|
|
40
|
-
| `authentication` | HTTP only: the protected tools' authentication descriptor. |
|
|
41
|
-
|
|
42
|
-
All but `actions`, `features`, `authentication` and `requestScoped` are Effect's own `McpServer`
|
|
43
|
-
options. Its `protocols` and `allowSessionTermination` are not options.
|
|
44
|
-
|
|
45
|
-
`client` takes `url`, default `/mcp`, `transformClient`, and the endpoint's `authentication`, so an
|
|
46
|
-
error its verifier declares decodes; `request` takes `url` and `headers`.
|
|
47
|
-
Types: `Client<Actions, D, E, R>`, `ClientOptions`, `RequestOptions`, `Params`.
|
|
8
|
+
A message the library throws says its fix; for a compiler diagnostic or Effect's own error,
|
|
9
|
+
search [ActionMcp-failures.md](ActionMcp-failures.md). For one symbol,
|
|
10
|
+
`grep -rn <symbol> node_modules/@gjermundgaraba/effect-actions/dist/`: each declaration's JSDoc
|
|
11
|
+
carries its rules and gotchas, and this page only what an example cannot show.
|
|
48
12
|
|
|
49
13
|
## Serve tools over HTTP
|
|
50
14
|
|
|
51
15
|
Merge `layerHttp` into the routes, beside any other surface, and serve them with
|
|
52
16
|
`HttpRouter.serve`:
|
|
53
17
|
|
|
54
|
-
```ts
|
|
18
|
+
```ts example=quickstart/src/routes.ts
|
|
19
|
+
import { Layer } from "effect";
|
|
20
|
+
import * as ActionHttp from "@gjermundgaraba/effect-actions/ActionHttp";
|
|
21
|
+
import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
|
|
22
|
+
import { Http } from "./Api.js";
|
|
23
|
+
import { greeting } from "./greeting/handlers.js";
|
|
24
|
+
|
|
25
|
+
// One implementation, two surfaces: a JSON route and an MCP tool at `/mcp`.
|
|
55
26
|
export const routes = Layer.mergeAll(
|
|
56
27
|
ActionHttp.layer(Http, greeting),
|
|
57
28
|
ActionMcp.layerHttp(greeting, { name: "greetings", version: "1.0.0" }),
|
|
58
29
|
);
|
|
59
30
|
```
|
|
60
31
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
see [Action-in-process.md](Action-in-process.md#share-one-build-across-servers).
|
|
32
|
+
[quickstart/src/server.ts](../examples/quickstart/src/server.ts) serves them. Surfaces in one
|
|
33
|
+
set of routes share each builder, as `Action.implement` says:
|
|
34
|
+
[notes-service/src/routes.ts](../examples/notes-service/src/routes.ts).
|
|
65
35
|
|
|
66
|
-
-
|
|
67
|
-
Claude Code 2.1 and Codex CLI 0.162 connect (October 2026):
|
|
36
|
+
- Claude Code 2.1 and Codex CLI 0.162 connect:
|
|
68
37
|
`claude mcp add --transport http <name> <url>/mcp --header "Authorization: Bearer <token>"`,
|
|
69
38
|
`codex mcp add <name> --url <url>/mcp --bearer-token-env-var <VAR>`;
|
|
70
39
|
[mcp-tools](../examples/mcp-tools/README.md#connect-a-client) has stdio and Claude Desktop.
|
|
71
|
-
-
|
|
72
|
-
alone. A session holds what its client sent there and no identity: each request authenticates,
|
|
73
|
-
as its own caller. It lasts until its client ends it with a `DELETE`, answered 204, or the process stops, as
|
|
74
|
-
Effect expires none, so limit request bodies, which bounds what one holds. Where an endpoint
|
|
75
|
-
serves a protected tool, only a signed-in caller opens one, and that client signs in for every
|
|
76
|
-
request, a public tool's call included. An endpoint of public tools alone lets anyone open them.
|
|
77
|
-
- **On a serverless host**, such as Cloudflare Workers, pass `requestScoped: true`: a server per
|
|
78
|
-
request, speaking 2026-07-28 alone with no session. Connect a client that opens with
|
|
79
|
-
`initialize` to a long-lived endpoint: [notes-service/src/worker.ts](../examples/notes-service/src/worker.ts).
|
|
80
|
-
- An endpoint without `authentication` answers whoever reaches its port. Bind it to loopback or to
|
|
81
|
-
a network only trusted clients are on, and limit request bodies
|
|
82
|
-
([ActionHttp.md](ActionHttp.md#serve-the-routes)):
|
|
40
|
+
- Where an endpoint without `authentication` listens, and its body limit:
|
|
83
41
|
[mcp-tools/src/server.ts](../examples/mcp-tools/src/server.ts).
|
|
42
|
+
- **On a serverless host**, such as Cloudflare Workers, pass `requestScoped: true`:
|
|
43
|
+
[notes-service/src/worker.ts](../examples/notes-service/src/worker.ts).
|
|
84
44
|
- The endpoint takes no layer middleware. A limit that must hold for tools goes in the handlers:
|
|
85
45
|
[kitchen-sink/src/issues/write-limit.ts](../examples/kitchen-sink/src/issues/write-limit.ts).
|
|
86
46
|
|
|
@@ -88,144 +48,99 @@ see [Action-in-process.md](Action-in-process.md#share-one-build-across-servers).
|
|
|
88
48
|
|
|
89
49
|
Provide `Stdio` and the startup services, then run it:
|
|
90
50
|
|
|
91
|
-
```ts
|
|
51
|
+
```ts example=notes-service/src/stdio.ts
|
|
52
|
+
import { NodeRuntime, NodeStdio } from "@effect/platform-node";
|
|
53
|
+
import { Effect } from "effect";
|
|
54
|
+
import * as ActionCli from "@gjermundgaraba/effect-actions/ActionCli";
|
|
55
|
+
import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
|
|
56
|
+
import { identity } from "./identity/handlers.js";
|
|
57
|
+
import { CurrentUser } from "./identity/Identity.js";
|
|
58
|
+
import { users } from "./identity/users.js";
|
|
59
|
+
import { notes } from "./notes/handlers.js";
|
|
60
|
+
import { NoteStore } from "./notes/store.js";
|
|
61
|
+
import { status } from "./status/handlers.js";
|
|
62
|
+
|
|
63
|
+
// Entrypoint: an MCP server a local agent starts as a subprocess, such as
|
|
64
|
+
// `claude mcp add notes -- "$PWD/node_modules/.bin/tsx" "$PWD/examples/notes-service/src/stdio.ts"`
|
|
65
|
+
// from the repository root. The host starts the process in a directory it chooses, so `tsx`
|
|
66
|
+
// and the file are named by full path.
|
|
67
|
+
// The same implementations as the HTTP server, and the same `authorize`. No token arrives
|
|
68
|
+
// over stdio, so this host names the caller itself. It is a process of its own with services
|
|
69
|
+
// of its own. DEMO ONLY: this in-memory store holds its notes, not the running server's. To
|
|
70
|
+
// share them, both provide a store they both reach, such as a database.
|
|
92
71
|
ActionMcp.runStdio([status, notes, identity], { name: "notes-service", version: "1.0.0" }).pipe(
|
|
93
72
|
// DEMO ONLY: a real host reads who started it, from its own login or configuration.
|
|
94
73
|
Effect.provideService(CurrentUser, users.ada),
|
|
95
74
|
Effect.provide([NodeStdio.layer, NoteStore.layerMemory]),
|
|
75
|
+
// stdout carries the protocol alone, so logs and failures go to stderr.
|
|
96
76
|
ActionCli.logToStderr,
|
|
97
77
|
NodeRuntime.runMain,
|
|
98
78
|
);
|
|
99
79
|
```
|
|
100
80
|
|
|
101
|
-
|
|
102
|
-
|
|
81
|
+
[mcp-tools/src/stdio.ts](../examples/mcp-tools/src/stdio.ts) serves public tools. What
|
|
82
|
+
`ActionCli.logToStderr` moves, and what it cannot, is on `ActionCli.logToStderr`. Test the tools
|
|
83
|
+
without a process: [Testing.md](Testing.md#test-mcp-tools).
|
|
103
84
|
|
|
104
|
-
To try a server by hand, send one request as `request` builds it, its 2026-07-28
|
|
105
|
-
`_meta`: [mcp-tools](../examples/mcp-tools/README.md#connect-a-client) has the
|
|
106
|
-
HTTP's five headers, and
|
|
107
|
-
|
|
108
|
-
- **stdout carries the protocol alone.** `runStdio` sends its own program's Effect logs and
|
|
109
|
-
`Console` output to stderr. For the layers provided around it, apply `ActionCli.logToStderr`
|
|
110
|
-
last, before `runMain`: it moves their default logger and reports a startup failure there too.
|
|
111
|
-
Nothing moves the global `console.log`: keep it off stdout.
|
|
112
|
-
- **A subprocess has its own services.** It runs the handlers itself, so it shares a server's
|
|
113
|
-
data only through a store both reach, never one in memory. A host that should see what the
|
|
114
|
-
server holds connects to its HTTP endpoint.
|
|
115
|
-
- **No token arrives, so the host names the caller.** stdio takes no `authentication`: provide the
|
|
116
|
-
protected tools' identity around `runStdio`, whose one caller is whoever started it. Each
|
|
117
|
-
implementation's `authorize` still runs, so give that caller what it checks. Tools served
|
|
118
|
-
behind a token over HTTP keep their one protected contract here: never a public copy.
|
|
119
|
-
- **Test the tools without a process**: [Testing.md](Testing.md#test-mcp-tools).
|
|
85
|
+
To try a server by hand, send one request as `ActionMcp.request` builds it, its 2026-07-28
|
|
86
|
+
metadata in `_meta`: [mcp-tools](../examples/mcp-tools/README.md#connect-a-client) has the
|
|
87
|
+
`curl`, with HTTP's five headers, and
|
|
88
|
+
[notes-service](../examples/notes-service/README.md#run-it) the stdio command.
|
|
120
89
|
|
|
121
90
|
## Describe tools to a model
|
|
122
91
|
|
|
123
92
|
A tool is its contract ([Action.md](Action.md#define-a-contract)): a model knows only the action's
|
|
124
|
-
name, its `description`, and its input schema with each field's `description
|
|
125
|
-
|
|
126
|
-
|
|
93
|
+
name, its `description`, and its input schema with each field's `description`; how the tool
|
|
94
|
+
presents itself is `Action.Options`'s `mcp`. The server's `instructions` say what the tools are
|
|
95
|
+
for together. See
|
|
127
96
|
[mcp-tools/src/printers/Printers.ts](../examples/mcp-tools/src/printers/Printers.ts) and
|
|
128
97
|
[mcp-tools/src/instructions.ts](../examples/mcp-tools/src/instructions.ts).
|
|
129
98
|
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
what a model may send: an id written into an upstream URL is not made safe by
|
|
137
|
-
`encodeURIComponent`, which leaves `..` alone.
|
|
138
|
-
- A `Schema.makeFilter` check adds nothing to the tool's listed input schema, and
|
|
139
|
-
`Schema.isPattern` its `pattern` only for a RegExp with the `u` flag. Say such a rule in the
|
|
140
|
-
field's `description`, annotated before the check: after a custom one it is lost too.
|
|
141
|
-
- A declared error reaches the model as an `isError` result whose text is the error's JSON. Write
|
|
142
|
-
its `message` for the model, which acts on it:
|
|
143
|
-
[mcp-tools/src/platform/print-farm.ts](../examples/mcp-tools/src/platform/print-farm.ts). Every other case:
|
|
144
|
-
[guarantees.md](guarantees.md#wire-behavior).
|
|
145
|
-
- `actions` lists the tools, and only listed actions must suit MCP:
|
|
99
|
+
- Constrain in the schema what a model may send: `PrinterId` in
|
|
100
|
+
[mcp-tools/src/printers/Printers.ts](../examples/mcp-tools/src/printers/Printers.ts). Which
|
|
101
|
+
checks reach the listed schema is on `Action.Options.input`.
|
|
102
|
+
- A model acts on a declared error's `message`, so write it for the model:
|
|
103
|
+
[mcp-tools/src/platform/print-farm.ts](../examples/mcp-tools/src/platform/print-farm.ts).
|
|
104
|
+
- `ActionMcp.Options`'s `actions` lists the tools, and only listed actions must suit MCP:
|
|
146
105
|
[kitchen-sink/src/Actions.ts](../examples/kitchen-sink/src/Actions.ts). Leave out an action a
|
|
147
106
|
model should not call, or one that takes a secret ([Action.md](Action.md#define-a-contract)).
|
|
148
107
|
|
|
149
108
|
## Return images and other media
|
|
150
109
|
|
|
151
|
-
Give the success an `Action.Image` field
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
The tool sends the field as an image block, after the JSON of the rest. Every other surface sends
|
|
159
|
-
it as JSON, the bytes in base64. See
|
|
160
|
-
[mcp-tools/src/printers/Printers.ts](../examples/mcp-tools/src/printers/Printers.ts); `client`
|
|
161
|
-
decodes it back.
|
|
162
|
-
|
|
163
|
-
- An image is a top-level field of a struct success, bare, optional or a required array, or the
|
|
164
|
-
whole success or an array of it. Anywhere else in a served action, as in a `Schema.NullOr`, a
|
|
165
|
-
`Schema.Class` success or an input, `layerHttp` and `runStdio` throw. So an action taking an
|
|
166
|
-
image as input is no tool: leave it out of `actions`
|
|
167
|
-
([kitchen-sink/src/attachments/Attachments.ts](../examples/kitchen-sink/src/attachments/Attachments.ts)).
|
|
168
|
-
- A media field's own `description` reaches no model. Describe the image in the action's
|
|
169
|
-
`description`.
|
|
110
|
+
Give the success an `Action.Image` field and return its bytes and MIME type from the handler:
|
|
111
|
+
`get_camera_snapshot` in
|
|
112
|
+
[mcp-tools/src/printers/Printers.ts](../examples/mcp-tools/src/printers/Printers.ts);
|
|
113
|
+
`ActionMcp.client` decodes it back. Where `ActionMcp` refuses one is on `Action.Image`:
|
|
114
|
+
[kitchen-sink/src/Actions.ts](../examples/kitchen-sink/src/Actions.ts).
|
|
170
115
|
|
|
171
116
|
## Protect tools
|
|
172
117
|
|
|
173
|
-
Name the protected actions' authentication descriptor and provide its
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const mcp = ActionMcp.layerHttp(implementations, {
|
|
177
|
-
name: "notes-service",
|
|
178
|
-
version: "1.0.0",
|
|
179
|
-
authentication: Login,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
export const routes = Layer.mergeAll(api, mcp, health, openApi).pipe(Layer.provide(authenticate));
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
See [notes-service/src/routes.ts](../examples/notes-service/src/routes.ts). Write the
|
|
186
|
-
[descriptor](Authentication.md#declare-how-callers-sign-in) and its
|
|
118
|
+
Name the protected actions' authentication descriptor as `authentication` and provide its
|
|
119
|
+
provider to the routes: [notes-service/src/routes.ts](../examples/notes-service/src/routes.ts).
|
|
120
|
+
Write the [descriptor](Authentication.md#declare-how-callers-sign-in) and its
|
|
187
121
|
[provider](Authentication.md#verify-credentials) once for every surface, and
|
|
188
122
|
[publish discovery](Authentication.md#publish-oauth-discovery) so an MCP client finds where to
|
|
189
123
|
sign in. What a signed-in caller may do is each implementation's
|
|
190
124
|
[`authorize`](Action.md#authorize-calls), which runs on every call of a protected tool.
|
|
191
125
|
|
|
192
|
-
- **One endpoint or two.**
|
|
193
|
-
|
|
194
|
-
request lists every tool, protected ones included, and calls a public one signed out, and a
|
|
195
|
-
session's every request signs in ([Serve tools over HTTP](#serve-tools-over-http)). A
|
|
196
|
-
protected tool's call answers the 401 a client signs in on. To keep protected tools unlisted
|
|
197
|
-
to signed-out callers, or public ones open to a session's client, serve the public tools on an
|
|
198
|
-
endpoint of their own: a second `layerHttp` on another `path`.
|
|
199
|
-
- No listing is filtered by caller: `authorize` refuses the call, not the sight of the tool.
|
|
200
|
-
- On a Bearer endpoint, a protected tool's step-up refusal answers the request, not a tool
|
|
201
|
-
result ([Authentication.md](Authentication.md#step-up-for-a-scope),
|
|
202
|
-
[guarantees.md](guarantees.md#wire-behavior)). Once a handler has sent a notification the
|
|
203
|
-
response has started, and its refusal is a tool result: refuse in `authorize`.
|
|
126
|
+
- **One endpoint or two.** What a mixed endpoint, serving public and protected tools, shows and
|
|
127
|
+
runs signed out is on `ActionMcp.LayerHttpOptions`'s `authentication`.
|
|
204
128
|
- Test as a signed-in caller ([Testing.md](Testing.md#test-as-a-signed-in-caller)):
|
|
205
129
|
[notes-service/test/routes.test.ts](../examples/notes-service/test/routes.test.ts).
|
|
206
130
|
|
|
207
131
|
## Allow browser clients
|
|
208
132
|
|
|
209
|
-
A browser MCP client sends its page's `Origin
|
|
133
|
+
A browser MCP client sends its page's `Origin`. List the origins it may come from as
|
|
134
|
+
`allowedOrigins`, and add CORS where one is configured:
|
|
135
|
+
[mcp-tools/src/routes.ts](../examples/mcp-tools/src/routes.ts), and
|
|
136
|
+
[kitchen-sink/src/request-policy.ts](../examples/kitchen-sink/src/request-policy.ts) with
|
|
137
|
+
authentication.
|
|
210
138
|
|
|
211
|
-
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
- **CORS.** `allowedOrigins` only refuses: it answers no preflight and adds no header. Add
|
|
217
|
-
`HttpRouter.cors`, global to the router, only where an origin is configured: given an empty
|
|
218
|
-
list, it answers `Access-Control-Allow-Origin: *`, and given one origin it names that origin
|
|
219
|
-
to every request, an unlisted page's included, which `allowedOrigins` still refuses. Allow the methods `POST` and
|
|
220
|
-
`DELETE`, the endpoint's only ones, and the headers `Content-Type`, `Authorization`,
|
|
221
|
-
`MCP-Protocol-Version`, `MCP-Method`, `MCP-Name` and `Mcp-Session-Id`; expose
|
|
222
|
-
`WWW-Authenticate`, `MCP-Protocol-Version` and `Mcp-Session-Id`: the same `routes.ts`, and
|
|
223
|
-
[kitchen-sink/src/request-policy.ts](../examples/kitchen-sink/src/request-policy.ts) with
|
|
224
|
-
authentication.
|
|
225
|
-
|
|
226
|
-
A `Host` allowlist, for a request that sends no `Origin`, or an Origin check that must run first
|
|
227
|
-
is a request policy:
|
|
228
|
-
[Authentication-advanced.md](Authentication-advanced.md#run-a-request-policy-before-authentication).
|
|
139
|
+
- Authentication runs before the Origin check, so a request from an unlisted origin that must
|
|
140
|
+
authenticate, or sends a token, gets 401 without a valid one, not 403.
|
|
141
|
+
- `allowedOrigins` checks no `Host`, and a request sending no `Origin` passes. A `Host`
|
|
142
|
+
allowlist, or an Origin check that must run first, is a request policy:
|
|
143
|
+
[Authentication-advanced.md](Authentication-advanced.md#run-a-request-policy-before-authentication).
|
|
229
144
|
|
|
230
145
|
## Add native prompts and resources
|
|
231
146
|
|
|
@@ -233,15 +148,9 @@ Merge Effect's own `McpServer.resource`, `McpServer.prompt` and `McpServer.toolk
|
|
|
233
148
|
one and pass it as `features`:
|
|
234
149
|
[kitchen-sink/src/issues/guide.ts](../examples/kitchen-sink/src/issues/guide.ts), given in
|
|
235
150
|
[kitchen-sink/src/routes.ts](../examples/kitchen-sink/src/routes.ts) and
|
|
236
|
-
[kitchen-sink/src/stdio.ts](../examples/kitchen-sink/src/stdio.ts).
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
- No `authorize` runs for them, and they run with the services they were built with, never a
|
|
241
|
-
request's, so they cannot read the caller. Content that depends on the caller is an action.
|
|
242
|
-
- On an endpoint naming `authentication`, every prompt and resource request needs a credential,
|
|
243
|
-
listings included. To offer them to signed-out callers, serve them on an endpoint without
|
|
244
|
-
protected tools.
|
|
151
|
+
[kitchen-sink/src/stdio.ts](../examples/kitchen-sink/src/stdio.ts). Where their services
|
|
152
|
+
come from is on `ActionMcp.Options`'s `features`, and when they need a credential on
|
|
153
|
+
`ActionMcp.LayerHttpOptions`'s `authentication`.
|
|
245
154
|
|
|
246
155
|
## Send notifications from a handler
|
|
247
156
|
|
|
@@ -3,19 +3,10 @@
|
|
|
3
3
|
What you see when something is wrong, and the fix. Search for a fragment of the message.
|
|
4
4
|
The tasks are in [ActionRpc.md](ActionRpc.md).
|
|
5
5
|
|
|
6
|
-
- `No action of these implementations is in this RPC binding: <names>` thrown by `layer`: none of the implementations' actions was passed to this binding's `make`. Implement the contract value the binding received; `(another contract)` marks an equal-looking copy.
|
|
7
|
-
- `Listed in actions, but the binding does not hold it: <names>`, or `but no implementation holds it`, thrown by `layer`: an action in `layer`'s `actions` is not the binding's or the implementations', by identity.
|
|
8
|
-
- `Duplicate served action: <name>` thrown by `layer`: one `layer` call received two implementations of one bound action. Pass one.
|
|
9
|
-
- `ActionRpc binding: error _tag "<tag>" is built in, and declared on every surface` thrown by `make`: the binding's `error` holds a built-in error, or one reusing a built-in tag. Drop the built-in one; rename your own.
|
|
10
|
-
- `Protected action '<name>' requires its matching authentication descriptor` thrown by `make` or `layer` in plain JavaScript, a `No overload matches this call` at `make` in TypeScript: the binding holds a protected action and names no descriptor, or one of another identity. Give `{ authentication: Login }`.
|
|
11
|
-
- `ActionRpc serves a protocol speaking JSON: ...`, a defect when `layer` builds: the host provides another serialization. Provide a JSON one, on the client too.
|
|
12
6
|
- A type error on `middleware` naming `"Layer middleware fails only with the binding's errors and needs no client"`: a middleware declares an error neither the binding nor the built-ins hold, or is `requiredForClient`. Add the error to the binding's `error`, or check in the handler, declaring the error on its actions.
|
|
13
|
-
- `
|
|
14
|
-
- `Type 'HttpRouter' is not assignable to type 'never'` where the server is launched: an rpc protocol adds its route to the router, so it belongs inside what `HttpRouter.serve` serves, and here it was provided around `HttpRouter.serve`. Provide the protocol to the rpc layer.
|
|
7
|
+
- `Type 'HttpRouter' is not assignable to type 'never'` where the server is launched: an rpc protocol was provided around `HttpRouter.serve`, whose router it adds its route to. Provide the protocol to the rpc layer.
|
|
15
8
|
- `HttpRouter` owed by an `ActionRpc.layer` over stdio or a socket server: its authentication provider publishes a `protectedResource`, on a router those protocols lack. Give the provider serving that protocol no `protectedResource`.
|
|
16
9
|
- `Request<"Requires", X>` owed by an `ActionRpc.layer` over stdio or a socket server though no handler yields `X`: the verifier yields `X`, which only a router-mounted protocol's route provides. Read `X` in the verifier's build, or serve the protected actions over HTTP or a WebSocket.
|
|
17
|
-
- `Request<"Requires", X>` in the type of `ActionRpc.layer`, or `X` not assignable to `never` where the server is launched: a handler
|
|
18
|
-
-
|
|
19
|
-
- A call dies with `Unknown request tag: <name>`: no layer serves the action, for which the client has a method all the same, or two `ActionRpc.layer`s were given one protocol, and only one receives its messages. Serve every rpc of a protocol from one layer; a second layer needs a protocol of its own, on another path.
|
|
20
|
-
- A call dies with `Internal server error`: a defect, an undeclared failure, a middleware that throws, or a success or a declared failure that does not encode. The cause is in the server's logs and error reporters.
|
|
10
|
+
- `Request<"Requires", X>` in the type of `ActionRpc.layer`, or `X` not assignable to `never` where the server is launched: a handler, the authorizer or a middleware yields `X`, which nothing provides per request, and a startup layer of `X` does not count. List a middleware providing `X`, by the rules on `ActionRpc.LayerOptions.middleware`, or provide `X` with router middleware around the layer.
|
|
11
|
+
- A call dies with `Unknown request tag: <name>`: no layer serves the action, for which the client has a method all the same, or a second `ActionRpc.layer` was given the same protocol. Serve every rpc of a protocol from one layer, or give the second a protocol on another path.
|
|
21
12
|
- `RpcClientError` from every call, Effect's own error: the client could not reach the server. Check the client's protocol, URL and path against the server's.
|