@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.
Files changed (2) hide show
  1. package/README.md +84 -103
  2. 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 beanDetails = z.object({
22
+ const peaVariety = z.object({
23
23
  name: z.string(),
24
- origin: z.string(),
25
- variety: z.string(),
26
- process: z.enum(["washed", "natural"]),
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 getBeanDetails = defineTool({
32
- name: "get-bean-details",
29
+ const getPeaVariety = defineTool({
30
+ name: "get-pea-variety",
33
31
  access: "public",
34
- description: "Get the origin, variety, process, roast, and tasting notes for one coffee.",
32
+ description: "Get the type, growth habit, and maturity time for one pea variety.",
35
33
  inputSchema: z.object({ name: z.string() }),
36
- outputSchema: beanDetails,
34
+ outputSchema: peaVariety,
37
35
  handler: ({ name }) => {
38
36
  const data = {
39
37
  name,
40
- origin: "Sample Highlands",
41
- variety: "Bourbon",
42
- process: "natural" as const,
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 medium-roast natural Bourbon from Sample Highlands.`, data };
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: "coffee-guide", version: "1.0.0", tools: [getBeanDetails] });
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: "coffee-guide",
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-bean-details.ts`
70
+ - `tool.get-pea-variety.ts`
77
71
  - `resource.getting-started.ts`
78
- - `prompt.brew-guide.ts`
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-bean-details",
84
+ name: "get-pea-variety",
91
85
  access: "public",
92
- description: "Get one coffee bean.",
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 getBeanDetails = defineTool({
205
- name: "get-bean-details",
206
- title: "Get bean details",
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 coffee.",
209
- icons: [{ src: "https://coffee.example/icons/bean.png", mimeType: "image/png" }],
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: beanDetails,
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: "coffee-guide",
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 coffee preference:
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({ roast: z.enum(["light", "medium", "dark"]) });
267
+ const preference = z.object({ support: z.enum(["stakes", "trellis"]) });
280
268
 
281
- const chooseRoast = defineTool({
282
- name: "choose-roast",
269
+ const chooseSupport = defineTool({
270
+ name: "choose-support",
283
271
  access: "public",
284
- description: "Ask which roast a person prefers for one coffee.",
285
- inputSchema: z.object({ coffee: z.string() }),
286
- outputSchema: z.object({ coffee: z.string(), roast: z.string() }),
287
- handler: ({ coffee }, context) => {
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 roast do you prefer for ${coffee}?`,
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.roast} roast selected for ${coffee}`,
301
- data: { coffee, roast: answer.roast },
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 catalogueBean = z.object({
334
+ const catalogueTaxon = z.object({
335
+ id: z.number().int().positive(),
347
336
  name: z.string().max(200),
348
- country: z.string().max(100),
349
- variety: z.string().max(100),
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
- country: z.string().max(100),
358
- variety: z.string().max(100),
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 getBeanDetails = defineMappedTool({
366
- name: "get-bean-details",
349
+ const getPeaTaxon = defineMappedTool({
350
+ name: "get-pea-taxon",
367
351
  access: "public",
368
- description: "Get clear coffee details from the catalogue service.",
352
+ description: "Get a pea taxon from the catalogue service.",
369
353
  inputSchema: z.object({ name: z.string().max(200) }),
370
- outputSchema: catalogueBean,
354
+ outputSchema: catalogueTaxon,
371
355
  backendInputSchema: backendCommand,
372
356
  backendOutputSchema: backendResult,
373
357
  mapInput: ({ name }) => ({ search: name }),
374
- adapter: async (command, { signal }) => backend.findCoffee(command, { signal }),
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
- country: record.country,
379
- variety: record.variety,
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} is a ${data.roast}-roast ${data.process} coffee from ${data.country}.`, data };
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 coffeeApi = createJsonHttpClient({ origin: "https://coffee.example" });
396
- const result = await coffeeApi.get({
397
- pathname: "/api/coffees",
398
- searchParams: { q: "ethiopia", limit: "5" },
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: "bean-report",
430
- heading: "Preview a bean report",
431
- legend: "Report options",
432
- submitLabel: "Preview report",
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: "Report title",
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 roast = defineStreamingTool({
462
- name: "roast-batch",
443
+ const germination = defineStreamingTool({
444
+ name: "run-germination-trial",
463
445
  access: "public",
464
- description: "Run one sample roast batch.",
465
- inputSchema: z.object({ batch: z.string() }),
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: "cool" });
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
- In the current source, public tools can also report progress behind a trusted
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 `roast` tool above, configure the proxy's exact address and the public
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: "coffee-guide",
474
+ name: "pea-guide",
494
475
  version: "1.0.0",
495
- tools: [roast],
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://coffee/getting-started";
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: "# Brew safely" }] }),
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://coffee/method/{method}",
557
+ uriTemplate: "guide://peas/planting/{method}",
577
558
  mimeType: "text/markdown",
578
559
  complete: {
579
- method: (value) => ["espresso", "pour-over"].filter((method) => method.startsWith(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 brew = definePrompt({
587
- name: "brew-guide",
567
+ const growing = definePrompt({
568
+ name: "growing-guide",
588
569
  argsSchema: z.object({ topic: z.string().min(1) }),
589
570
  complete: {
590
- topic: (value) => ["grind-size", "water-temperature"].filter((topic) => topic.startsWith(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: "coffee",
579
+ name: "peas",
599
580
  version: "1.0.0",
600
581
  resources: [guide, methodGuide],
601
- prompts: [brew],
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-bean",
633
+ name: "lookup-private-seed-lot",
653
634
  access: "protected",
654
- requiredScopes: ["beans:read"],
655
- description: "Look up a private bean.",
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: "beans",
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: ["beans:read"],
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.1",
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": {