@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.
Files changed (2) hide show
  1. package/README.md +98 -100
  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
  });
@@ -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
- name: z.string(),
340
- country: z.string(),
341
- variety: z.string(),
342
- processCode: z.enum(["W", "N"]),
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 getBeanDetails = defineMappedTool({
349
- name: "get-bean-details",
349
+ const getPeaTaxon = defineMappedTool({
350
+ name: "get-pea-taxon",
350
351
  access: "public",
351
- description: "Get clear coffee details from the catalogue service.",
352
- inputSchema: z.object({ name: z.string() }),
353
- outputSchema: beanDetails,
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.findCoffee(command, { signal }),
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
- origin: record.country,
362
- variety: record.variety,
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} is a ${data.roast}-roast ${data.process} coffee from ${data.origin}.`, data };
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 coffeeApi = createJsonHttpClient({ origin: "https://coffee.example" });
379
- const result = await coffeeApi.get({
380
- pathname: "/api/coffees",
381
- 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" },
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: "bean-report",
413
- heading: "Preview a bean report",
414
- legend: "Report options",
415
- submitLabel: "Preview report",
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: "Report title",
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 roast = defineStreamingTool({
445
- name: "roast-batch",
443
+ const germination = defineStreamingTool({
444
+ name: "run-germination-trial",
446
445
  access: "public",
447
- description: "Run one sample roast batch.",
448
- inputSchema: z.object({ batch: z.string() }),
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: "cool" });
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
- In the current source, public tools can also report progress behind a trusted
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 `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
472
470
  host and origin your clients use:
473
471
 
474
472
  ```ts
475
473
  const app = createEmseepea({
476
- name: "coffee-guide",
474
+ name: "pea-guide",
477
475
  version: "1.0.0",
478
- tools: [roast],
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://coffee/getting-started";
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: "# Brew safely" }] }),
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://coffee/method/{method}",
557
+ uriTemplate: "guide://peas/planting/{method}",
560
558
  mimeType: "text/markdown",
561
559
  complete: {
562
- method: (value) => ["espresso", "pour-over"].filter((method) => method.startsWith(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 brew = definePrompt({
570
- name: "brew-guide",
567
+ const growing = definePrompt({
568
+ name: "growing-guide",
571
569
  argsSchema: z.object({ topic: z.string().min(1) }),
572
570
  complete: {
573
- topic: (value) => ["grind-size", "water-temperature"].filter((topic) => topic.startsWith(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: "coffee",
579
+ name: "peas",
582
580
  version: "1.0.0",
583
581
  resources: [guide, methodGuide],
584
- prompts: [brew],
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-bean",
633
+ name: "lookup-private-seed-lot",
636
634
  access: "protected",
637
- requiredScopes: ["beans:read"],
638
- description: "Look up a private bean.",
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: "beans",
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: ["beans:read"],
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.0",
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": {