@emseepea/server 0.2.0 → 0.2.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.
- package/README.md +98 -100
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -19,42 +19,36 @@ package supports and what it does not support.
|
|
|
19
19
|
import { createEmseepea, defineMappedTool, defineTool, serveEmseepea } from "@emseepea/server";
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
|
|
22
|
-
const
|
|
22
|
+
const peaVariety = z.object({
|
|
23
23
|
name: z.string(),
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
roast: z.enum(["light", "medium", "dark"]),
|
|
28
|
-
tastingNotes: z.array(z.string()),
|
|
24
|
+
peaType: z.enum(["shelling", "snap"]),
|
|
25
|
+
growthHabit: z.enum(["bush", "climbing"]),
|
|
26
|
+
daysToMaturity: z.number().int().positive(),
|
|
29
27
|
});
|
|
30
28
|
|
|
31
|
-
const
|
|
32
|
-
name: "get-
|
|
29
|
+
const getPeaVariety = defineTool({
|
|
30
|
+
name: "get-pea-variety",
|
|
33
31
|
access: "public",
|
|
34
|
-
description: "Get the
|
|
32
|
+
description: "Get the type, growth habit, and maturity time for one pea variety.",
|
|
35
33
|
inputSchema: z.object({ name: z.string() }),
|
|
36
|
-
outputSchema:
|
|
34
|
+
outputSchema: peaVariety,
|
|
37
35
|
handler: ({ name }) => {
|
|
38
36
|
const data = {
|
|
39
37
|
name,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
roast: "medium" as const,
|
|
44
|
-
tastingNotes: ["berry", "cocoa"],
|
|
38
|
+
peaType: "snap" as const,
|
|
39
|
+
growthHabit: "climbing" as const,
|
|
40
|
+
daysToMaturity: 70,
|
|
45
41
|
};
|
|
46
|
-
return { text: `${name} is a
|
|
42
|
+
return { text: `${name} is a climbing snap pea that matures in 70 days.`, data };
|
|
47
43
|
},
|
|
48
44
|
});
|
|
49
45
|
|
|
50
|
-
const app = createEmseepea({ name: "
|
|
46
|
+
const app = createEmseepea({ name: "pea-guide", version: "1.0.0", tools: [getPeaVariety] });
|
|
51
47
|
await serveEmseepea(app);
|
|
52
48
|
```
|
|
53
49
|
|
|
54
50
|
## Discover Capability Modules at Startup
|
|
55
51
|
|
|
56
|
-
Available in this checkout; not yet published to npm.
|
|
57
|
-
|
|
58
52
|
Explicit registration still works. If you prefer one file per capability, put
|
|
59
53
|
modules in a directory and discover them before `createEmseepea`:
|
|
60
54
|
|
|
@@ -63,7 +57,7 @@ import { createEmseepea, discoverCapabilities, serveEmseepea } from "@emseepea/s
|
|
|
63
57
|
|
|
64
58
|
const capabilities = await discoverCapabilities(new URL("./capabilities/", import.meta.url));
|
|
65
59
|
const app = createEmseepea({
|
|
66
|
-
name: "
|
|
60
|
+
name: "pea-guide",
|
|
67
61
|
version: "1.0.0",
|
|
68
62
|
...capabilities,
|
|
69
63
|
});
|
|
@@ -73,9 +67,9 @@ await serveEmseepea(app);
|
|
|
73
67
|
|
|
74
68
|
Capability files must be named for their public MCP kind and name:
|
|
75
69
|
|
|
76
|
-
- `tool.get-
|
|
70
|
+
- `tool.get-pea-variety.ts`
|
|
77
71
|
- `resource.getting-started.ts`
|
|
78
|
-
- `prompt.
|
|
72
|
+
- `prompt.growing-guide.ts`
|
|
79
73
|
|
|
80
74
|
Each file exports only a default factory. The factory returns a value from
|
|
81
75
|
`defineTool`, `defineStreamingTool`, `defineMappedTool`, `defineResource`,
|
|
@@ -87,9 +81,9 @@ import { defineTool, type CapabilityModuleFactory } from "@emseepea/server";
|
|
|
87
81
|
import { z } from "zod";
|
|
88
82
|
|
|
89
83
|
export default (() => defineTool({
|
|
90
|
-
name: "get-
|
|
84
|
+
name: "get-pea-variety",
|
|
91
85
|
access: "public",
|
|
92
|
-
description: "Get one
|
|
86
|
+
description: "Get one pea variety.",
|
|
93
87
|
inputSchema: z.object({ name: z.string() }),
|
|
94
88
|
outputSchema: z.object({ name: z.string() }),
|
|
95
89
|
handler: ({ name }) => ({ text: name, data: { name } }),
|
|
@@ -109,8 +103,6 @@ the duplicate instead of choosing one.
|
|
|
109
103
|
|
|
110
104
|
## Measure Requests
|
|
111
105
|
|
|
112
|
-
Available in this checkout; not yet published to npm.
|
|
113
|
-
|
|
114
106
|
Set `telemetry: true` in `createEmseepea` to record request traces, counts, and
|
|
115
107
|
response times through OpenTelemetry. Leave it out to disable these measurements.
|
|
116
108
|
|
|
@@ -146,8 +138,6 @@ Structured logs are not included yet.
|
|
|
146
138
|
|
|
147
139
|
## Report Dependency Readiness
|
|
148
140
|
|
|
149
|
-
Available in this checkout; not yet published to npm.
|
|
150
|
-
|
|
151
141
|
Pass a `readiness` callback to `createEmseepea` to check the dependencies your
|
|
152
142
|
server needs. The callback receives `{ signal }` and must return `true` only
|
|
153
143
|
when those dependencies are ready. It may return a promise.
|
|
@@ -171,8 +161,6 @@ block MCP calls itself. Tool handlers must still handle an unavailable backend.
|
|
|
171
161
|
|
|
172
162
|
## Flush Measurements on Shutdown
|
|
173
163
|
|
|
174
|
-
Available in this checkout; not yet published to npm.
|
|
175
|
-
|
|
176
164
|
Pass `flushTelemetry` to `serveEmseepea` to flush your OpenTelemetry providers
|
|
177
165
|
when the returned server's `close()` method runs. The callback receives
|
|
178
166
|
`{ signal }`; it can call your providers' `forceFlush()` methods. Calling the
|
|
@@ -201,15 +189,15 @@ provide its website address. Tools can say whether they only read data and
|
|
|
201
189
|
whether they contact services outside the application:
|
|
202
190
|
|
|
203
191
|
```ts
|
|
204
|
-
const
|
|
205
|
-
name: "get-
|
|
206
|
-
title: "Get
|
|
192
|
+
const getPeaVariety = defineTool({
|
|
193
|
+
name: "get-pea-variety",
|
|
194
|
+
title: "Get pea variety",
|
|
207
195
|
access: "public",
|
|
208
|
-
description: "Get the recorded details for one
|
|
209
|
-
icons: [{ src: "https://
|
|
196
|
+
description: "Get the recorded details for one pea variety.",
|
|
197
|
+
icons: [{ src: "https://garden.example/icons/pea.png", mimeType: "image/png" }],
|
|
210
198
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
211
199
|
inputSchema: z.object({ name: z.string() }),
|
|
212
|
-
outputSchema:
|
|
200
|
+
outputSchema: peaVariety,
|
|
213
201
|
handler,
|
|
214
202
|
});
|
|
215
203
|
```
|
|
@@ -247,7 +235,7 @@ adding one option to the server:
|
|
|
247
235
|
|
|
248
236
|
```ts
|
|
249
237
|
const app = createEmseepea({
|
|
250
|
-
name: "
|
|
238
|
+
name: "pea-guide",
|
|
251
239
|
version: "1.0.0",
|
|
252
240
|
tools,
|
|
253
241
|
listPagination: { pageSize: 50, maxPageBytes: 256 * 1024 },
|
|
@@ -270,35 +258,35 @@ pattern, or prompt that pauses and asks a capable client for more information.
|
|
|
270
258
|
The client answers by making a fresh request, and the handler runs again with
|
|
271
259
|
those answers in its context.
|
|
272
260
|
|
|
273
|
-
This example asks the person for a
|
|
261
|
+
This example asks the person for a pea-growing preference:
|
|
274
262
|
|
|
275
263
|
```ts
|
|
276
264
|
import { acceptedContent, defineTool, inputRequired } from "@emseepea/server";
|
|
277
265
|
import { z } from "zod";
|
|
278
266
|
|
|
279
|
-
const preference = z.object({
|
|
267
|
+
const preference = z.object({ support: z.enum(["stakes", "trellis"]) });
|
|
280
268
|
|
|
281
|
-
const
|
|
282
|
-
name: "choose-
|
|
269
|
+
const chooseSupport = defineTool({
|
|
270
|
+
name: "choose-support",
|
|
283
271
|
access: "public",
|
|
284
|
-
description: "Ask which
|
|
285
|
-
inputSchema: z.object({
|
|
286
|
-
outputSchema: z.object({
|
|
287
|
-
handler: ({
|
|
272
|
+
description: "Ask which support a person prefers for one climbing pea.",
|
|
273
|
+
inputSchema: z.object({ variety: z.string() }),
|
|
274
|
+
outputSchema: z.object({ variety: z.string(), support: z.string() }),
|
|
275
|
+
handler: ({ variety }, context) => {
|
|
288
276
|
const answer = acceptedContent(context.inputResponses, "preference", preference);
|
|
289
277
|
if (!answer) {
|
|
290
278
|
return inputRequired({
|
|
291
279
|
inputRequests: {
|
|
292
280
|
preference: inputRequired.elicit({
|
|
293
|
-
message: `Which
|
|
281
|
+
message: `Which support do you prefer for ${variety}?`,
|
|
294
282
|
requestedSchema: preference,
|
|
295
283
|
}),
|
|
296
284
|
},
|
|
297
285
|
});
|
|
298
286
|
}
|
|
299
287
|
return {
|
|
300
|
-
text: `${answer.
|
|
301
|
-
data: {
|
|
288
|
+
text: `${answer.support} selected for ${variety}`,
|
|
289
|
+
data: { variety, support: answer.support },
|
|
302
290
|
};
|
|
303
291
|
},
|
|
304
292
|
});
|
|
@@ -332,39 +320,50 @@ Use `defineTool` instead when the backend already accepts the public input and
|
|
|
332
320
|
returns the public output. `defineMappedTool` is for a real contract boundary,
|
|
333
321
|
not an identity mapping.
|
|
334
322
|
|
|
323
|
+
Preserve useful backend concepts and values by default. Select public-safe
|
|
324
|
+
fields explicitly and validate the public and backend boundaries separately,
|
|
325
|
+
but do not maintain a second list of values just to rename it. Map values only
|
|
326
|
+
for transport compatibility, security, redaction, aggregation, or genuine model
|
|
327
|
+
comprehension.
|
|
328
|
+
|
|
329
|
+
Prefer an open, bounded schema when the backend can add valid values
|
|
330
|
+
independently.
|
|
331
|
+
|
|
335
332
|
```ts
|
|
336
|
-
const backendCommand = z.object({ search: z.string() });
|
|
333
|
+
const backendCommand = z.object({ search: z.string().max(200) });
|
|
334
|
+
const catalogueTaxon = z.object({
|
|
335
|
+
id: z.number().int().positive(),
|
|
336
|
+
name: z.string().max(200),
|
|
337
|
+
rank: z.string().max(40),
|
|
338
|
+
observations_count: z.number().int().nonnegative(),
|
|
339
|
+
});
|
|
337
340
|
const backendResult = z.object({
|
|
338
341
|
record: z.object({
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
roastLevel: z.enum(["light", "medium", "dark"]),
|
|
344
|
-
flavourNotes: z.array(z.string()),
|
|
342
|
+
id: z.number().int().positive(),
|
|
343
|
+
name: z.string().max(200),
|
|
344
|
+
rank: z.string().max(40),
|
|
345
|
+
observations_count: z.number().int().nonnegative(),
|
|
345
346
|
}),
|
|
346
347
|
});
|
|
347
348
|
|
|
348
|
-
const
|
|
349
|
-
name: "get-
|
|
349
|
+
const getPeaTaxon = defineMappedTool({
|
|
350
|
+
name: "get-pea-taxon",
|
|
350
351
|
access: "public",
|
|
351
|
-
description: "Get
|
|
352
|
-
inputSchema: z.object({ name: z.string() }),
|
|
353
|
-
outputSchema:
|
|
352
|
+
description: "Get a pea taxon from the catalogue service.",
|
|
353
|
+
inputSchema: z.object({ name: z.string().max(200) }),
|
|
354
|
+
outputSchema: catalogueTaxon,
|
|
354
355
|
backendInputSchema: backendCommand,
|
|
355
356
|
backendOutputSchema: backendResult,
|
|
356
357
|
mapInput: ({ name }) => ({ search: name }),
|
|
357
|
-
adapter: async (command, { signal }) => backend.
|
|
358
|
+
adapter: async (command, { signal }) => backend.findTaxon(command, { signal }),
|
|
358
359
|
mapOutput: ({ record }) => {
|
|
359
360
|
const data = {
|
|
361
|
+
id: record.id,
|
|
360
362
|
name: record.name,
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
process: record.processCode === "W" ? "washed" as const : "natural" as const,
|
|
364
|
-
roast: record.roastLevel,
|
|
365
|
-
tastingNotes: record.flavourNotes,
|
|
363
|
+
rank: record.rank,
|
|
364
|
+
observations_count: record.observations_count,
|
|
366
365
|
};
|
|
367
|
-
return { text: `${data.name}
|
|
366
|
+
return { text: `${data.name} has rank ${data.rank} and ${data.observations_count} recorded observations.`, data };
|
|
368
367
|
},
|
|
369
368
|
});
|
|
370
369
|
```
|
|
@@ -375,10 +374,10 @@ client from `@emseepea/server/http`:
|
|
|
375
374
|
```ts
|
|
376
375
|
import { createJsonHttpClient } from "@emseepea/server/http";
|
|
377
376
|
|
|
378
|
-
const
|
|
379
|
-
const result = await
|
|
380
|
-
pathname: "/api/
|
|
381
|
-
searchParams: { q: "
|
|
377
|
+
const plantApi = createJsonHttpClient({ origin: "https://plants.example" });
|
|
378
|
+
const result = await plantApi.get({
|
|
379
|
+
pathname: "/api/taxa",
|
|
380
|
+
searchParams: { q: "pea", limit: "5" },
|
|
382
381
|
signal,
|
|
383
382
|
deadlineMs,
|
|
384
383
|
});
|
|
@@ -409,15 +408,15 @@ still owns its page, request handling, authorization, and effects.
|
|
|
409
408
|
import { defineElicitationView, renderElicitationForm } from "@emseepea/server";
|
|
410
409
|
|
|
411
410
|
const view = defineElicitationView({
|
|
412
|
-
id: "
|
|
413
|
-
heading: "Preview a
|
|
414
|
-
legend: "
|
|
415
|
-
submitLabel: "Preview
|
|
411
|
+
id: "pea-planting-plan",
|
|
412
|
+
heading: "Preview a pea planting plan",
|
|
413
|
+
legend: "Planting plan options",
|
|
414
|
+
submitLabel: "Preview plan",
|
|
416
415
|
fields: [{
|
|
417
416
|
kind: "text",
|
|
418
417
|
id: "title",
|
|
419
418
|
name: "title",
|
|
420
|
-
label: "
|
|
419
|
+
label: "Plan title",
|
|
421
420
|
required: true,
|
|
422
421
|
}],
|
|
423
422
|
state: { kind: "ready", focusTarget: "none" },
|
|
@@ -441,15 +440,15 @@ request. Otherwise the call returns one JSON response.
|
|
|
441
440
|
import { createEmseepea, defineStreamingTool } from "@emseepea/server";
|
|
442
441
|
import { z } from "zod";
|
|
443
442
|
|
|
444
|
-
const
|
|
445
|
-
name: "
|
|
443
|
+
const germination = defineStreamingTool({
|
|
444
|
+
name: "run-germination-trial",
|
|
446
445
|
access: "public",
|
|
447
|
-
description: "Run one sample
|
|
448
|
-
inputSchema: z.object({
|
|
446
|
+
description: "Run one sample pea germination trial.",
|
|
447
|
+
inputSchema: z.object({ tray: z.string() }),
|
|
449
448
|
outputSchema: z.object({ status: z.literal("complete") }),
|
|
450
449
|
async handler(_input, { reportProgress, signal }) {
|
|
451
450
|
signal.throwIfAborted();
|
|
452
|
-
await reportProgress({ progress: 1, total: 1, message: "
|
|
451
|
+
await reportProgress({ progress: 1, total: 1, message: "sprout" });
|
|
453
452
|
return { text: "complete", data: { status: "complete" } };
|
|
454
453
|
},
|
|
455
454
|
});
|
|
@@ -465,17 +464,16 @@ rejected.
|
|
|
465
464
|
|
|
466
465
|
### Use Progress Behind a Proxy
|
|
467
466
|
|
|
468
|
-
|
|
469
|
-
HTTPS proxy. This addition is not yet in a published npm release.
|
|
467
|
+
Public tools can also report progress behind a trusted HTTPS proxy.
|
|
470
468
|
|
|
471
|
-
Using the `
|
|
469
|
+
Using the `germination` tool above, configure the proxy's exact address and the public
|
|
472
470
|
host and origin your clients use:
|
|
473
471
|
|
|
474
472
|
```ts
|
|
475
473
|
const app = createEmseepea({
|
|
476
|
-
name: "
|
|
474
|
+
name: "pea-guide",
|
|
477
475
|
version: "1.0.0",
|
|
478
|
-
tools: [
|
|
476
|
+
tools: [germination],
|
|
479
477
|
deployment: {
|
|
480
478
|
mode: "production-behind-proxy",
|
|
481
479
|
trustedProxyAddresses: ["10.0.0.10"],
|
|
@@ -544,33 +542,33 @@ import {
|
|
|
544
542
|
} from "@emseepea/server";
|
|
545
543
|
import { z } from "zod";
|
|
546
544
|
|
|
547
|
-
const guideUri = "guide://
|
|
545
|
+
const guideUri = "guide://peas/getting-started";
|
|
548
546
|
|
|
549
547
|
const guide = defineResource({
|
|
550
548
|
name: "getting-started",
|
|
551
549
|
uri: guideUri,
|
|
552
550
|
mimeType: "text/markdown",
|
|
553
551
|
cacheHint: { ttlMs: 30_000 },
|
|
554
|
-
handler: () => ({ contents: [{ uri: guideUri, text: "#
|
|
552
|
+
handler: () => ({ contents: [{ uri: guideUri, text: "# Grow peas safely" }] }),
|
|
555
553
|
});
|
|
556
554
|
|
|
557
555
|
const methodGuide = defineResourceTemplate({
|
|
558
556
|
name: "method-guide",
|
|
559
|
-
uriTemplate: "guide://
|
|
557
|
+
uriTemplate: "guide://peas/planting/{method}",
|
|
560
558
|
mimeType: "text/markdown",
|
|
561
559
|
complete: {
|
|
562
|
-
method: (value) => ["
|
|
560
|
+
method: (value) => ["container", "raised-bed"].filter((method) => method.startsWith(value)),
|
|
563
561
|
},
|
|
564
562
|
handler: ({ uri, variables }) => ({
|
|
565
563
|
contents: [{ uri, text: `# ${String(variables.method)}` }],
|
|
566
564
|
}),
|
|
567
565
|
});
|
|
568
566
|
|
|
569
|
-
const
|
|
570
|
-
name: "
|
|
567
|
+
const growing = definePrompt({
|
|
568
|
+
name: "growing-guide",
|
|
571
569
|
argsSchema: z.object({ topic: z.string().min(1) }),
|
|
572
570
|
complete: {
|
|
573
|
-
topic: (value) => ["
|
|
571
|
+
topic: (value) => ["sowing-depth", "plant-spacing"].filter((topic) => topic.startsWith(value)),
|
|
574
572
|
},
|
|
575
573
|
handler: ({ topic }) => ({
|
|
576
574
|
messages: [{ role: "user", content: { type: "text", text: `Explain ${topic}.` } }],
|
|
@@ -578,10 +576,10 @@ const brew = definePrompt({
|
|
|
578
576
|
});
|
|
579
577
|
|
|
580
578
|
const app = createEmseepea({
|
|
581
|
-
name: "
|
|
579
|
+
name: "peas",
|
|
582
580
|
version: "1.0.0",
|
|
583
581
|
resources: [guide, methodGuide],
|
|
584
|
-
prompts: [
|
|
582
|
+
prompts: [growing],
|
|
585
583
|
cacheHints: {
|
|
586
584
|
"resources/list": { ttlMs: 60_000, cacheScope: "public" },
|
|
587
585
|
"resources/read": { ttlMs: 5_000, cacheScope: "private" },
|
|
@@ -632,10 +630,10 @@ the token itself.
|
|
|
632
630
|
|
|
633
631
|
```ts
|
|
634
632
|
const lookup = defineTool({
|
|
635
|
-
name: "lookup-private-
|
|
633
|
+
name: "lookup-private-seed-lot",
|
|
636
634
|
access: "protected",
|
|
637
|
-
requiredScopes: ["
|
|
638
|
-
description: "Look up a private
|
|
635
|
+
requiredScopes: ["seeds:read"],
|
|
636
|
+
description: "Look up a private pea seed lot.",
|
|
639
637
|
inputSchema: z.object({ id: z.string() }),
|
|
640
638
|
outputSchema: z.object({ id: z.string(), clientId: z.string() }),
|
|
641
639
|
handler: ({ id }, { principal }) => ({
|
|
@@ -645,14 +643,14 @@ const lookup = defineTool({
|
|
|
645
643
|
});
|
|
646
644
|
|
|
647
645
|
const app = createEmseepea({
|
|
648
|
-
name: "
|
|
646
|
+
name: "seed-inventory",
|
|
649
647
|
version: "1.0.0",
|
|
650
648
|
tools: [lookup],
|
|
651
649
|
oauth: {
|
|
652
650
|
verifier,
|
|
653
651
|
metadata: {
|
|
654
652
|
resourceServerUrl: new URL("https://api.example/mcp"),
|
|
655
|
-
scopesSupported: ["
|
|
653
|
+
scopesSupported: ["seeds:read"],
|
|
656
654
|
oauthMetadata,
|
|
657
655
|
},
|
|
658
656
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emseepea/server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Fastify-first framework for Model Context Protocol 2026-07-28 servers over Streamable HTTP",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
"bugs": "https://github.com/emseepea/emseepea/issues",
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public",
|
|
15
|
-
"provenance": true
|
|
16
|
-
"tag": "next"
|
|
15
|
+
"provenance": true
|
|
17
16
|
},
|
|
18
17
|
"type": "module",
|
|
19
18
|
"exports": {
|