@emseepea/server 0.2.1 → 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 +84 -103
- 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
|
});
|
|
@@ -343,45 +331,39 @@ independently.
|
|
|
343
331
|
|
|
344
332
|
```ts
|
|
345
333
|
const backendCommand = z.object({ search: z.string().max(200) });
|
|
346
|
-
const
|
|
334
|
+
const catalogueTaxon = z.object({
|
|
335
|
+
id: z.number().int().positive(),
|
|
347
336
|
name: z.string().max(200),
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
process: z.string().max(100),
|
|
351
|
-
roast: z.string().max(40),
|
|
352
|
-
tastingNotes: z.array(z.string().max(100)).max(20),
|
|
337
|
+
rank: z.string().max(40),
|
|
338
|
+
observations_count: z.number().int().nonnegative(),
|
|
353
339
|
});
|
|
354
340
|
const backendResult = z.object({
|
|
355
341
|
record: z.object({
|
|
342
|
+
id: z.number().int().positive(),
|
|
356
343
|
name: z.string().max(200),
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
process: z.string().max(100),
|
|
360
|
-
roast: z.string().max(40),
|
|
361
|
-
tastingNotes: z.array(z.string().max(100)).max(20),
|
|
344
|
+
rank: z.string().max(40),
|
|
345
|
+
observations_count: z.number().int().nonnegative(),
|
|
362
346
|
}),
|
|
363
347
|
});
|
|
364
348
|
|
|
365
|
-
const
|
|
366
|
-
name: "get-
|
|
349
|
+
const getPeaTaxon = defineMappedTool({
|
|
350
|
+
name: "get-pea-taxon",
|
|
367
351
|
access: "public",
|
|
368
|
-
description: "Get
|
|
352
|
+
description: "Get a pea taxon from the catalogue service.",
|
|
369
353
|
inputSchema: z.object({ name: z.string().max(200) }),
|
|
370
|
-
outputSchema:
|
|
354
|
+
outputSchema: catalogueTaxon,
|
|
371
355
|
backendInputSchema: backendCommand,
|
|
372
356
|
backendOutputSchema: backendResult,
|
|
373
357
|
mapInput: ({ name }) => ({ search: name }),
|
|
374
|
-
adapter: async (command, { signal }) => backend.
|
|
358
|
+
adapter: async (command, { signal }) => backend.findTaxon(command, { signal }),
|
|
375
359
|
mapOutput: ({ record }) => {
|
|
376
360
|
const data = {
|
|
361
|
+
id: record.id,
|
|
377
362
|
name: record.name,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
process: record.process,
|
|
381
|
-
roast: record.roast,
|
|
382
|
-
tastingNotes: record.tastingNotes,
|
|
363
|
+
rank: record.rank,
|
|
364
|
+
observations_count: record.observations_count,
|
|
383
365
|
};
|
|
384
|
-
return { text: `${data.name}
|
|
366
|
+
return { text: `${data.name} has rank ${data.rank} and ${data.observations_count} recorded observations.`, data };
|
|
385
367
|
},
|
|
386
368
|
});
|
|
387
369
|
```
|
|
@@ -392,10 +374,10 @@ client from `@emseepea/server/http`:
|
|
|
392
374
|
```ts
|
|
393
375
|
import { createJsonHttpClient } from "@emseepea/server/http";
|
|
394
376
|
|
|
395
|
-
const
|
|
396
|
-
const result = await
|
|
397
|
-
pathname: "/api/
|
|
398
|
-
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" },
|
|
399
381
|
signal,
|
|
400
382
|
deadlineMs,
|
|
401
383
|
});
|
|
@@ -426,15 +408,15 @@ still owns its page, request handling, authorization, and effects.
|
|
|
426
408
|
import { defineElicitationView, renderElicitationForm } from "@emseepea/server";
|
|
427
409
|
|
|
428
410
|
const view = defineElicitationView({
|
|
429
|
-
id: "
|
|
430
|
-
heading: "Preview a
|
|
431
|
-
legend: "
|
|
432
|
-
submitLabel: "Preview
|
|
411
|
+
id: "pea-planting-plan",
|
|
412
|
+
heading: "Preview a pea planting plan",
|
|
413
|
+
legend: "Planting plan options",
|
|
414
|
+
submitLabel: "Preview plan",
|
|
433
415
|
fields: [{
|
|
434
416
|
kind: "text",
|
|
435
417
|
id: "title",
|
|
436
418
|
name: "title",
|
|
437
|
-
label: "
|
|
419
|
+
label: "Plan title",
|
|
438
420
|
required: true,
|
|
439
421
|
}],
|
|
440
422
|
state: { kind: "ready", focusTarget: "none" },
|
|
@@ -458,15 +440,15 @@ request. Otherwise the call returns one JSON response.
|
|
|
458
440
|
import { createEmseepea, defineStreamingTool } from "@emseepea/server";
|
|
459
441
|
import { z } from "zod";
|
|
460
442
|
|
|
461
|
-
const
|
|
462
|
-
name: "
|
|
443
|
+
const germination = defineStreamingTool({
|
|
444
|
+
name: "run-germination-trial",
|
|
463
445
|
access: "public",
|
|
464
|
-
description: "Run one sample
|
|
465
|
-
inputSchema: z.object({
|
|
446
|
+
description: "Run one sample pea germination trial.",
|
|
447
|
+
inputSchema: z.object({ tray: z.string() }),
|
|
466
448
|
outputSchema: z.object({ status: z.literal("complete") }),
|
|
467
449
|
async handler(_input, { reportProgress, signal }) {
|
|
468
450
|
signal.throwIfAborted();
|
|
469
|
-
await reportProgress({ progress: 1, total: 1, message: "
|
|
451
|
+
await reportProgress({ progress: 1, total: 1, message: "sprout" });
|
|
470
452
|
return { text: "complete", data: { status: "complete" } };
|
|
471
453
|
},
|
|
472
454
|
});
|
|
@@ -482,17 +464,16 @@ rejected.
|
|
|
482
464
|
|
|
483
465
|
### Use Progress Behind a Proxy
|
|
484
466
|
|
|
485
|
-
|
|
486
|
-
HTTPS proxy. This addition is not yet in a published npm release.
|
|
467
|
+
Public tools can also report progress behind a trusted HTTPS proxy.
|
|
487
468
|
|
|
488
|
-
Using the `
|
|
469
|
+
Using the `germination` tool above, configure the proxy's exact address and the public
|
|
489
470
|
host and origin your clients use:
|
|
490
471
|
|
|
491
472
|
```ts
|
|
492
473
|
const app = createEmseepea({
|
|
493
|
-
name: "
|
|
474
|
+
name: "pea-guide",
|
|
494
475
|
version: "1.0.0",
|
|
495
|
-
tools: [
|
|
476
|
+
tools: [germination],
|
|
496
477
|
deployment: {
|
|
497
478
|
mode: "production-behind-proxy",
|
|
498
479
|
trustedProxyAddresses: ["10.0.0.10"],
|
|
@@ -561,33 +542,33 @@ import {
|
|
|
561
542
|
} from "@emseepea/server";
|
|
562
543
|
import { z } from "zod";
|
|
563
544
|
|
|
564
|
-
const guideUri = "guide://
|
|
545
|
+
const guideUri = "guide://peas/getting-started";
|
|
565
546
|
|
|
566
547
|
const guide = defineResource({
|
|
567
548
|
name: "getting-started",
|
|
568
549
|
uri: guideUri,
|
|
569
550
|
mimeType: "text/markdown",
|
|
570
551
|
cacheHint: { ttlMs: 30_000 },
|
|
571
|
-
handler: () => ({ contents: [{ uri: guideUri, text: "#
|
|
552
|
+
handler: () => ({ contents: [{ uri: guideUri, text: "# Grow peas safely" }] }),
|
|
572
553
|
});
|
|
573
554
|
|
|
574
555
|
const methodGuide = defineResourceTemplate({
|
|
575
556
|
name: "method-guide",
|
|
576
|
-
uriTemplate: "guide://
|
|
557
|
+
uriTemplate: "guide://peas/planting/{method}",
|
|
577
558
|
mimeType: "text/markdown",
|
|
578
559
|
complete: {
|
|
579
|
-
method: (value) => ["
|
|
560
|
+
method: (value) => ["container", "raised-bed"].filter((method) => method.startsWith(value)),
|
|
580
561
|
},
|
|
581
562
|
handler: ({ uri, variables }) => ({
|
|
582
563
|
contents: [{ uri, text: `# ${String(variables.method)}` }],
|
|
583
564
|
}),
|
|
584
565
|
});
|
|
585
566
|
|
|
586
|
-
const
|
|
587
|
-
name: "
|
|
567
|
+
const growing = definePrompt({
|
|
568
|
+
name: "growing-guide",
|
|
588
569
|
argsSchema: z.object({ topic: z.string().min(1) }),
|
|
589
570
|
complete: {
|
|
590
|
-
topic: (value) => ["
|
|
571
|
+
topic: (value) => ["sowing-depth", "plant-spacing"].filter((topic) => topic.startsWith(value)),
|
|
591
572
|
},
|
|
592
573
|
handler: ({ topic }) => ({
|
|
593
574
|
messages: [{ role: "user", content: { type: "text", text: `Explain ${topic}.` } }],
|
|
@@ -595,10 +576,10 @@ const brew = definePrompt({
|
|
|
595
576
|
});
|
|
596
577
|
|
|
597
578
|
const app = createEmseepea({
|
|
598
|
-
name: "
|
|
579
|
+
name: "peas",
|
|
599
580
|
version: "1.0.0",
|
|
600
581
|
resources: [guide, methodGuide],
|
|
601
|
-
prompts: [
|
|
582
|
+
prompts: [growing],
|
|
602
583
|
cacheHints: {
|
|
603
584
|
"resources/list": { ttlMs: 60_000, cacheScope: "public" },
|
|
604
585
|
"resources/read": { ttlMs: 5_000, cacheScope: "private" },
|
|
@@ -649,10 +630,10 @@ the token itself.
|
|
|
649
630
|
|
|
650
631
|
```ts
|
|
651
632
|
const lookup = defineTool({
|
|
652
|
-
name: "lookup-private-
|
|
633
|
+
name: "lookup-private-seed-lot",
|
|
653
634
|
access: "protected",
|
|
654
|
-
requiredScopes: ["
|
|
655
|
-
description: "Look up a private
|
|
635
|
+
requiredScopes: ["seeds:read"],
|
|
636
|
+
description: "Look up a private pea seed lot.",
|
|
656
637
|
inputSchema: z.object({ id: z.string() }),
|
|
657
638
|
outputSchema: z.object({ id: z.string(), clientId: z.string() }),
|
|
658
639
|
handler: ({ id }, { principal }) => ({
|
|
@@ -662,14 +643,14 @@ const lookup = defineTool({
|
|
|
662
643
|
});
|
|
663
644
|
|
|
664
645
|
const app = createEmseepea({
|
|
665
|
-
name: "
|
|
646
|
+
name: "seed-inventory",
|
|
666
647
|
version: "1.0.0",
|
|
667
648
|
tools: [lookup],
|
|
668
649
|
oauth: {
|
|
669
650
|
verifier,
|
|
670
651
|
metadata: {
|
|
671
652
|
resourceServerUrl: new URL("https://api.example/mcp"),
|
|
672
|
-
scopesSupported: ["
|
|
653
|
+
scopesSupported: ["seeds:read"],
|
|
673
654
|
oauthMetadata,
|
|
674
655
|
},
|
|
675
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": {
|