@emseepea/server 0.0.0 → 0.0.1

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 (2) hide show
  1. package/README.md +321 -3
  2. package/package.json +43 -2
package/README.md CHANGED
@@ -1,5 +1,323 @@
1
- # Em See Pea Server
1
+ # `@emseepea/server`
2
2
 
3
- This version only reserves the package name.
3
+ `@emseepea/server` is the Fastify-first package for building Model Context
4
+ Protocol (MCP) `2026-07-28` servers over Streamable HTTP.
4
5
 
5
- Do not use version `0.0.0`. Install a later release instead.
6
+ It checks data when requests enter and leave the server. It supports public
7
+ tools, tools that require sign-in, public resources and prompts, request time
8
+ limits, and bounded progress updates for local examples. Prompt arguments and
9
+ resource fields may also offer suggestions.
10
+
11
+ The package is pre-alpha. See the
12
+ [repository README](https://github.com/windyroad/emseepea#readme) for what the
13
+ package supports and what it does not support.
14
+
15
+ ## Public Tool
16
+
17
+ ```ts
18
+ import { createEmseepea, defineMappedTool, defineTool, serveEmseepea } from "@emseepea/server";
19
+ import { z } from "zod";
20
+
21
+ const beanDetails = z.object({
22
+ name: z.string(),
23
+ origin: z.string(),
24
+ variety: z.string(),
25
+ process: z.enum(["washed", "natural"]),
26
+ roast: z.enum(["light", "medium", "dark"]),
27
+ tastingNotes: z.array(z.string()),
28
+ });
29
+
30
+ const getBeanDetails = defineTool({
31
+ name: "get-bean-details",
32
+ access: "public",
33
+ description: "Get the origin, variety, process, roast, and tasting notes for one coffee.",
34
+ inputSchema: z.object({ name: z.string() }),
35
+ outputSchema: beanDetails,
36
+ handler: ({ name }) => {
37
+ const data = {
38
+ name,
39
+ origin: "Sample Highlands",
40
+ variety: "Bourbon",
41
+ process: "natural" as const,
42
+ roast: "medium" as const,
43
+ tastingNotes: ["berry", "cocoa"],
44
+ };
45
+ return { text: `${name} is a medium-roast natural Bourbon from Sample Highlands.`, data };
46
+ },
47
+ });
48
+
49
+ const app = createEmseepea({ name: "coffee-guide", version: "1.0.0", tools: [getBeanDetails] });
50
+ await serveEmseepea(app);
51
+ ```
52
+
53
+ ## Mapped Backend Tool
54
+
55
+ `defineMappedTool` validates the public input, mapped backend command, backend
56
+ result, and public output. The adapter receives only its validated command, the
57
+ request cancellation signal, and the shared deadline.
58
+
59
+ Use `defineTool` instead when the backend already accepts the public input and
60
+ returns the public output. `defineMappedTool` is for a real contract boundary,
61
+ not an identity mapping.
62
+
63
+ ```ts
64
+ const backendCommand = z.object({ search: z.string() });
65
+ const backendResult = z.object({
66
+ record: z.object({
67
+ name: z.string(),
68
+ country: z.string(),
69
+ variety: z.string(),
70
+ processCode: z.enum(["W", "N"]),
71
+ roastLevel: z.enum(["light", "medium", "dark"]),
72
+ flavourNotes: z.array(z.string()),
73
+ }),
74
+ });
75
+
76
+ const getBeanDetails = defineMappedTool({
77
+ name: "get-bean-details",
78
+ access: "public",
79
+ description: "Get clear coffee details from the catalogue service.",
80
+ inputSchema: z.object({ name: z.string() }),
81
+ outputSchema: beanDetails,
82
+ backendInputSchema: backendCommand,
83
+ backendOutputSchema: backendResult,
84
+ mapInput: ({ name }) => ({ search: name }),
85
+ adapter: async (command, { signal }) => backend.findCoffee(command, { signal }),
86
+ mapOutput: ({ record }) => {
87
+ const data = {
88
+ name: record.name,
89
+ origin: record.country,
90
+ variety: record.variety,
91
+ process: record.processCode === "W" ? "washed" as const : "natural" as const,
92
+ roast: record.roastLevel,
93
+ tastingNotes: record.flavourNotes,
94
+ };
95
+ return { text: `${data.name} is a ${data.roast}-roast ${data.process} coffee from ${data.origin}.`, data };
96
+ },
97
+ });
98
+ ```
99
+
100
+ For a read-only JSON API that does not require sign-in, use the narrow HTTP
101
+ client from `@emseepea/server/http`:
102
+
103
+ ```ts
104
+ import { createJsonHttpClient } from "@emseepea/server/http";
105
+
106
+ const coffeeApi = createJsonHttpClient({ origin: "https://coffee.example" });
107
+ const result = await coffeeApi.get({
108
+ pathname: "/api/coffees",
109
+ searchParams: { q: "ethiopia", limit: "5" },
110
+ signal,
111
+ deadlineMs,
112
+ });
113
+ ```
114
+
115
+ The client connects only to the configured HTTPS site. It blocks local and
116
+ private network addresses, redirects, compressed responses, oversized data,
117
+ and responses that are not JSON. It does not send credentials, retry requests,
118
+ cache data, accept custom headers, or change data. The result is `unknown` so a
119
+ mapped tool must check it with its backend output schema before using it.
120
+
121
+ A mapped tool may add an `isAvailable` function when its backend can be
122
+ temporarily unavailable. The check receives the same cancellation signal and
123
+ deadline as the adapter. It must be quick and must not change data.
124
+
125
+ An unavailable tool stays in `tools/list`, but calls to it return a generic
126
+ failure before mapping or backend work begins. Other tools and `/readyz` keep
127
+ working because `/readyz` reports whether the server process is ready, not
128
+ whether every application backend is available.
129
+
130
+ ## Form Presentation
131
+
132
+ Use `defineElicitationView` to validate form display data and
133
+ `renderElicitationForm` to create a native HTML fragment. The host application
134
+ still owns its page, request handling, authorization, and effects.
135
+
136
+ ```ts
137
+ import { defineElicitationView, renderElicitationForm } from "@emseepea/server";
138
+
139
+ const view = defineElicitationView({
140
+ id: "bean-report",
141
+ heading: "Preview a bean report",
142
+ legend: "Report options",
143
+ submitLabel: "Preview report",
144
+ fields: [{
145
+ kind: "text",
146
+ id: "title",
147
+ name: "title",
148
+ label: "Report title",
149
+ required: true,
150
+ }],
151
+ state: { kind: "ready", focusTarget: "none" },
152
+ });
153
+
154
+ const fragment = renderElicitationForm(view, { headingLevel: 2 });
155
+ ```
156
+
157
+ The schema accepts presentation data only. It excludes raw HTML, destinations,
158
+ credentials, backend state, and permission to perform an action. Rendering a
159
+ form never means that an action was authorized or completed.
160
+
161
+ ## Streaming Progress
162
+
163
+ Use `defineStreamingTool` only when a tool has meaningful progress to report.
164
+ It checks the input, progress updates, and final result. If the client asks for
165
+ progress, the official MCP library sends progress events over the same POST
166
+ request. Otherwise the call returns one JSON response.
167
+
168
+ ```ts
169
+ import { defineStreamingTool } from "@emseepea/server";
170
+
171
+ const roast = defineStreamingTool({
172
+ name: "roast-batch",
173
+ access: "public",
174
+ description: "Run one sample roast batch.",
175
+ inputSchema: z.object({ batch: z.string() }),
176
+ outputSchema: z.object({ status: z.literal("complete") }),
177
+ async handler(_input, { reportProgress, signal }) {
178
+ signal.throwIfAborted();
179
+ await reportProgress({ progress: 1, total: 1, message: "cool" });
180
+ return { text: "complete", data: { status: "complete" } };
181
+ },
182
+ });
183
+ ```
184
+
185
+ Progress is strictly increasing and defaults to at most 32 small notification
186
+ payloads, measured before protocol encoding.
187
+
188
+ Set `maxProgressEvents` and `maxProgressEventBytes` on `createEmseepea` to
189
+ change those positive bounds. The reporter closes with the tool call.
190
+ Heartbeats are disabled. Invalid, oversized, late, or extra updates are
191
+ rejected.
192
+
193
+ Streaming tools start only for local examples. They do not yet support:
194
+
195
+ - long-running streams opened with GET
196
+ - saved sessions, replay, or subscriptions
197
+ - proxy servers or recovery after reconnecting
198
+ - slowing the producer when a client cannot keep up
199
+ - more than one server instance
200
+
201
+ ## Public Resources and Prompts
202
+
203
+ Static resources and prompts are public operations. Their handlers receive only
204
+ the shared deadline and cancellation signal. The framework checks prompt
205
+ arguments and results before returning them.
206
+
207
+ `operationTimeoutMs` limits tool calls, resource reads, and prompt requests. It
208
+ defaults to 30 seconds.
209
+
210
+ `maxApplicationResultBytes` limits each checked handler result before MCP
211
+ encoding. It defaults to one mebibyte. The limit does not include MCP metadata,
212
+ protocol envelopes, discovery, or catalogues.
213
+
214
+ ```ts
215
+ import {
216
+ createEmseepea,
217
+ definePrompt,
218
+ defineResource,
219
+ defineResourceTemplate,
220
+ } from "@emseepea/server";
221
+ import { z } from "zod";
222
+
223
+ const guideUri = "guide://coffee/getting-started";
224
+
225
+ const guide = defineResource({
226
+ name: "getting-started",
227
+ uri: guideUri,
228
+ mimeType: "text/markdown",
229
+ handler: () => ({ contents: [{ uri: guideUri, text: "# Brew safely" }] }),
230
+ });
231
+
232
+ const methodGuide = defineResourceTemplate({
233
+ name: "method-guide",
234
+ uriTemplate: "guide://coffee/method/{method}",
235
+ mimeType: "text/markdown",
236
+ complete: {
237
+ method: (value) => ["espresso", "pour-over"].filter((method) => method.startsWith(value)),
238
+ },
239
+ handler: ({ uri, variables }) => ({
240
+ contents: [{ uri, text: `# ${String(variables.method)}` }],
241
+ }),
242
+ });
243
+
244
+ const brew = definePrompt({
245
+ name: "brew-guide",
246
+ argsSchema: z.object({ topic: z.string().min(1) }),
247
+ complete: {
248
+ topic: (value) => ["grind-size", "water-temperature"].filter((topic) => topic.startsWith(value)),
249
+ },
250
+ handler: ({ topic }) => ({
251
+ messages: [{ role: "user", content: { type: "text", text: `Explain ${topic}.` } }],
252
+ }),
253
+ });
254
+
255
+ const app = createEmseepea({
256
+ name: "coffee",
257
+ version: "1.0.0",
258
+ resources: [guide, methodGuide],
259
+ prompts: [brew],
260
+ });
261
+ ```
262
+
263
+ Resource templates advertise an address pattern rather than listing every
264
+ possible address. Clients list the patterns, then read an address that matches
265
+ one. Each `{variable}` fills one complete path segment. The scheme and host
266
+ cannot change. Conflicting patterns stop the server at startup.
267
+
268
+ Suggestions are optional. When enabled, a handler receives the partial text,
269
+ the request time limit, cancellation, and registered string arguments. Em See
270
+ Pea checks the returned suggestions and keeps at most the first 100.
271
+
272
+ Suggestions are public even when some tools require sign-in. Return only text
273
+ that is safe for anyone to discover.
274
+
275
+ Listing every matching address, large result pages, sign-in for resources, and
276
+ custom caching are not included yet.
277
+
278
+ ## Tool That Requires Sign-In
279
+
280
+ Tools that require sign-in declare their permissions. The framework leaves
281
+ `server/discover` and `tools/list` open to everyone. It verifies a bearer token
282
+ only when someone calls the restricted tool, then checks its expiry,
283
+ permissions, and intended server. The tool receives the caller's identity, not
284
+ the token itself.
285
+
286
+ ```ts
287
+ const lookup = defineTool({
288
+ name: "lookup-private-bean",
289
+ access: "protected",
290
+ requiredScopes: ["beans:read"],
291
+ description: "Look up a private bean.",
292
+ inputSchema: z.object({ id: z.string() }),
293
+ outputSchema: z.object({ id: z.string(), clientId: z.string() }),
294
+ handler: ({ id }, { principal }) => ({
295
+ text: id,
296
+ data: { id, clientId: principal.clientId },
297
+ }),
298
+ });
299
+
300
+ const app = createEmseepea({
301
+ name: "beans",
302
+ version: "1.0.0",
303
+ tools: [lookup],
304
+ oauth: {
305
+ verifier,
306
+ metadata: {
307
+ resourceServerUrl: new URL("https://api.example/mcp"),
308
+ scopesSupported: ["beans:read"],
309
+ oauthMetadata,
310
+ },
311
+ },
312
+ });
313
+ ```
314
+
315
+ The application's token checker must verify who issued the token and stop its
316
+ own slow network or file work. Em See Pea limits how long the request waits.
317
+ The official MCP library cannot pass a cancellation signal to the checker.
318
+
319
+ The application must decide whether the signed-in person may access each
320
+ record.
321
+
322
+ The HTTP client described above is only for public, read-only JSON APIs. The
323
+ application remains responsible for backend requests that use credentials.
package/package.json CHANGED
@@ -1,11 +1,52 @@
1
1
  {
2
2
  "name": "@emseepea/server",
3
- "version": "0.0.0",
4
- "description": "Reserved package identity for the Em See Pea MCP server framework",
3
+ "version": "0.0.1",
4
+ "description": "Fastify-first framework for Model Context Protocol 2026-07-28 servers over Streamable HTTP",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/windyroad/emseepea.git",
9
9
  "directory": "packages/framework"
10
+ },
11
+ "homepage": "https://github.com/windyroad/emseepea#readme",
12
+ "bugs": "https://github.com/windyroad/emseepea/issues",
13
+ "publishConfig": {
14
+ "access": "public",
15
+ "provenance": true,
16
+ "tag": "next"
17
+ },
18
+ "type": "module",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ },
24
+ "./ui": {
25
+ "types": "./dist/ui.d.ts",
26
+ "import": "./dist/ui.js"
27
+ },
28
+ "./http": {
29
+ "types": "./dist/http.d.ts",
30
+ "import": "./dist/http.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "LICENSE",
36
+ "README.md"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.json"
40
+ },
41
+ "dependencies": {
42
+ "@modelcontextprotocol/core": "2.0.0",
43
+ "@modelcontextprotocol/fastify": "2.0.0",
44
+ "@modelcontextprotocol/node": "2.0.0",
45
+ "@modelcontextprotocol/server": "2.0.0",
46
+ "fastify": "5.12.1",
47
+ "zod": "4.4.3"
48
+ },
49
+ "engines": {
50
+ "node": ">=22"
10
51
  }
11
52
  }