@emseepea/create-react-ui-server 0.0.14 → 0.0.18

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 CHANGED
@@ -16,6 +16,30 @@ npm init @emseepea/react-ui-server -- my-server
16
16
 
17
17
  <!-- generated-project-readme -->
18
18
 
19
+ ## Choose Open or Protected Access
20
+
21
+ Start open when the catalogue and operations are public.
22
+
23
+ To protect this template, pass both options to the app factory:
24
+
25
+ - `access: { access: "protected", requiredScopes: ["peas:read"] }`
26
+ - an `authentication` adapter
27
+
28
+ Keep `authentication.discovery` as `"public"` unless capability names or
29
+ schemas are sensitive. Use `"protected"` only when each principal should see a
30
+ permission-filtered catalogue. OAuth metadata remains public in both modes.
31
+
32
+ ## Add Observability
33
+
34
+ The same factory accepts `observability`.
35
+
36
+ - Use `structuredLogging` for safe structured events.
37
+ - Use `openTelemetry` for traces and metrics.
38
+
39
+ Adapters receive only redacted framework events. They never receive request
40
+ bodies, arguments, results, tokens, provider claims, or raw errors. See the
41
+ [server API](https://github.com/emseepea/emseepea/tree/main/packages/framework#authentication-and-observability) for the complete configuration.
42
+
19
43
  ## React and Tailwind UI Example
20
44
 
21
45
  Choose this example when your application already uses React and you want an
@@ -46,6 +70,13 @@ Open
46
70
 
47
71
  The page previews a sample pea planting plan only. It does not send or store a report.
48
72
 
73
+ ## Add Feedback
74
+
75
+ Install `@emseepea/feedback` when this server needs a detailed one-way
76
+ observation or a durable support conversation. Pass its tools through the
77
+ application factory's `additionalTools` option. Choose PostgreSQL, Firestore,
78
+ GitHub Issues, or Zendesk in the [feedback guide](../../packages/feedback/README.md).
79
+
49
80
  ## Check This Example
50
81
 
51
82
  [Ordinary tests](test/) live in `test/`.
@@ -1,4 +1,28 @@
1
- # React and Tailwind UI Example
1
+ # Choose Open or Protected Access
2
+
3
+ Start open when the catalogue and operations are public.
4
+
5
+ To protect this template, pass both options to the app factory:
6
+
7
+ - `access: { access: "protected", requiredScopes: ["peas:read"] }`
8
+ - an `authentication` adapter
9
+
10
+ Keep `authentication.discovery` as `"public"` unless capability names or
11
+ schemas are sensitive. Use `"protected"` only when each principal should see a
12
+ permission-filtered catalogue. OAuth metadata remains public in both modes.
13
+
14
+ ## Add Observability
15
+
16
+ The same factory accepts `observability`.
17
+
18
+ - Use `structuredLogging` for safe structured events.
19
+ - Use `openTelemetry` for traces and metrics.
20
+
21
+ Adapters receive only redacted framework events. They never receive request
22
+ bodies, arguments, results, tokens, provider claims, or raw errors. See the
23
+ [server API](https://github.com/emseepea/emseepea/tree/main/packages/framework#authentication-and-observability) for the complete configuration.
24
+
25
+ ## React and Tailwind UI Example
2
26
 
3
27
  Choose this example when your application already uses React and you want an
4
28
  accessible form without writing Tailwind configuration or component styles.
@@ -28,6 +52,13 @@ Open
28
52
 
29
53
  The page previews a sample pea planting plan only. It does not send or store a report.
30
54
 
55
+ ## Add Feedback
56
+
57
+ Install `@emseepea/feedback` when this server needs a detailed one-way
58
+ observation or a durable support conversation. Pass its tools through the
59
+ application factory's `additionalTools` option. Choose PostgreSQL, Firestore,
60
+ GitHub Issues, or Zendesk in the [feedback guide](../../packages/feedback/README.md).
61
+
31
62
  ## Check This Example
32
63
 
33
64
  [Ordinary tests](test/) live in `test/`.
@@ -1,15 +1,23 @@
1
1
  import test from "node:test";
2
2
  import {
3
3
  assertNoToolCalls,
4
+ assertNoNegativeFeedback,
4
5
  assertResponseContains,
5
6
  assertResponseMeaning,
6
- assertToolCalls,
7
+ assertToolCallsWithOptionalFeedback,
7
8
  createConversation,
8
9
  } from "@emseepea/testing/semantic";
9
10
 
11
+ const server = new URL(import.meta.resolve("@emseepea/feedback/testing-server"));
12
+ const environment = {
13
+ EMSEEPEA_EVAL_APP_MODULE: new URL("../dist/app.js", import.meta.url).href,
14
+ EMSEEPEA_EVAL_APP_FACTORY: "createReactUiServer",
15
+ };
16
+
10
17
  test("understands an all-varieties React UI preview", async (t) => {
11
18
  const chat = await createConversation(t, {
12
- server: new URL("../dist/server.js", import.meta.url),
19
+ server,
20
+ environment,
13
21
  });
14
22
 
15
23
  // This variant covers all-variety filtering and omitted tips. One exact
@@ -19,7 +27,7 @@ test("understands an all-varieties React UI preview", async (t) => {
19
27
  "Summarize the matches and say whether anything was sent, stored, or changed.",
20
28
  );
21
29
 
22
- assertToolCalls(response, [{
30
+ await assertToolCallsWithOptionalFeedback(response, [{
23
31
  name: "preview-planting-plan",
24
32
  arguments: { title: "All pea plan", peaType: "all", includeTips: false },
25
33
  }]);
@@ -36,4 +44,5 @@ test("understands an all-varieties React UI preview", async (t) => {
36
44
  );
37
45
  assertNoToolCalls(followUp);
38
46
  assertResponseContains(followUp, "3");
47
+ assertNoNegativeFeedback(response, followUp);
39
48
  });
@@ -14,9 +14,10 @@
14
14
  "lint": "oxlint src test eval test-support"
15
15
  },
16
16
  "devDependencies": {
17
+ "@emseepea/feedback": "0.2.0",
17
18
  "playwright": "1.62.1",
18
19
  "axe-core": "4.13.0",
19
- "@emseepea/testing": "0.5.3",
20
+ "@emseepea/testing": "0.9.3",
20
21
  "@types/node": "24.13.3",
21
22
  "@types/react": "19.2.18",
22
23
  "@types/react-dom": "19.2.5",
@@ -29,8 +30,8 @@
29
30
  },
30
31
  "private": true,
31
32
  "dependencies": {
32
- "@emseepea/react": "0.0.9",
33
- "@emseepea/server": "0.3.3",
33
+ "@emseepea/react": "0.0.13",
34
+ "@emseepea/server": "0.6.1",
34
35
  "@emseepea/tailwind": "0.0.2",
35
36
  "react": "19.2.8",
36
37
  "react-dom": "19.2.8"
@@ -0,0 +1,30 @@
1
+ import { parse } from "node:querystring";
2
+ import {
3
+ createEmseepea,
4
+ discoverCapabilities,
5
+ registerRoutes,
6
+ type AccessPolicy,
7
+ type EmseepeaExtensions,
8
+ } from "@emseepea/server";
9
+
10
+ export interface ReactUiServerOptions extends EmseepeaExtensions {
11
+ readonly access?: AccessPolicy;
12
+ }
13
+
14
+ export async function createReactUiServer(options: ReactUiServerOptions = {}) {
15
+ const { access = { access: "public" }, ...extensions } = options;
16
+ const app = createEmseepea({
17
+ name: "emseepea-react-ui-server",
18
+ version: "0.0.0",
19
+ instructions: "Use preview-planting-plan to preview a sample pea planting plan. It sends and stores nothing.",
20
+ ...await discoverCapabilities(new URL("./capabilities/", import.meta.url), access),
21
+ ...extensions,
22
+ });
23
+ app.addContentTypeParser(
24
+ "application/x-www-form-urlencoded",
25
+ { parseAs: "string" },
26
+ (_request, body, done) => done(null, parse(body.toString())),
27
+ );
28
+ await registerRoutes(app, new URL("./routes/", import.meta.url));
29
+ return app;
30
+ }
@@ -1,4 +1,4 @@
1
1
  import { createPreviewPlantingPlanTool } from "../ui-shared.js";
2
- import type { CapabilityModuleFactory } from "@emseepea/server";
2
+ import type { AccessPolicy, CapabilityModuleFactory } from "@emseepea/server";
3
3
 
4
- export default (() => createPreviewPlantingPlanTool()) satisfies CapabilityModuleFactory;
4
+ export default ((access) => createPreviewPlantingPlanTool(access)) satisfies CapabilityModuleFactory<AccessPolicy>;
@@ -1,22 +1,9 @@
1
- import { parse } from "node:querystring";
1
+ import { serveEmseepea } from "@emseepea/server";
2
+ import { createReactUiServer } from "./app.js";
2
3
 
3
- import { createEmseepea, discoverCapabilities, registerRoutes, serveEmseepea } from "@emseepea/server";
4
-
5
- const app = createEmseepea({
6
- name: "emseepea-react-ui-server",
7
- version: "0.0.0",
8
- instructions: "Use preview-planting-plan to preview a sample pea planting plan. It sends and stores nothing.",
9
- ...await discoverCapabilities(new URL("./capabilities/", import.meta.url)),
4
+ const running = await serveEmseepea(await createReactUiServer(), {
5
+ port: Number.parseInt(process.env.PORT ?? "3001", 10),
10
6
  });
11
-
12
- app.addContentTypeParser(
13
- "application/x-www-form-urlencoded",
14
- { parseAs: "string" },
15
- (_request, body, done) => done(null, parse(body.toString())),
16
- );
17
- await registerRoutes(app, new URL("./routes/", import.meta.url));
18
-
19
- const running = await serveEmseepea(app, { port: Number.parseInt(process.env.PORT ?? "3001", 10) });
20
7
  console.log(`Em See Pea React UI example listening at ${running.url}`);
21
8
 
22
9
  async function shutdown(): Promise<void> {
@@ -1,4 +1,9 @@
1
- import { defineElicitationView, defineTool, type ElicitationView } from "@emseepea/server";
1
+ import {
2
+ defineElicitationView,
3
+ defineTool,
4
+ type AccessPolicy,
5
+ type ElicitationView,
6
+ } from "@emseepea/server";
2
7
  import { z } from "zod";
3
8
 
4
9
  const peaTypeSchema = z.enum(["all", "shelling", "snap"])
@@ -40,10 +45,10 @@ export function previewPlantingPlan(input: z.output<typeof inputSchema>) {
40
45
  };
41
46
  }
42
47
 
43
- export function createPreviewPlantingPlanTool() {
48
+ export function createPreviewPlantingPlanTool(access: AccessPolicy = { access: "public" }) {
44
49
  return defineTool({
45
50
  name: "preview-planting-plan",
46
- access: "public",
51
+ ...access,
47
52
  title: "Preview a Pea Planting Plan",
48
53
  description: "Preview a sample pea planting plan without sending, storing, or changing anything.",
49
54
  inputSchema,
@@ -1,7 +1,12 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
 
4
- import { startMcpServer } from "@emseepea/testing";
4
+ import {
5
+ insecureTestAuthentication,
6
+ startEmseepea,
7
+ startMcpServer,
8
+ } from "@emseepea/testing";
9
+ import { createReactUiServer } from "../dist/app.js";
5
10
 
6
11
  test("describes every planting-plan tool property", async (t) => {
7
12
  const running = await startMcpServer(t, new URL("../dist/server.js", import.meta.url));
@@ -29,3 +34,16 @@ test("describes every planting-plan tool property", async (t) => {
29
34
  });
30
35
  assert.equal(result.content[0].text, JSON.stringify(result.structuredContent));
31
36
  });
37
+
38
+ test("the same UI template composes protected access and observability", async (t) => {
39
+ const events = [];
40
+ const permissions = ["plans:preview"];
41
+ const running = await startEmseepea(t, await createReactUiServer({
42
+ access: { access: "protected", requiredScopes: permissions },
43
+ authentication: insecureTestAuthentication(permissions),
44
+ observability: [{ id: "test-log", emit: (event) => events.push(event) }],
45
+ }));
46
+ const client = await running.connect("test-token");
47
+ assert.deepEqual((await client.listTools()).tools.map(({ name }) => name), ["preview-planting-plan"]);
48
+ assert.ok(events.some(({ method }) => method === "tools/list"));
49
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emseepea/create-react-ui-server",
3
- "version": "0.0.14",
3
+ "version": "0.0.18",
4
4
  "description": "Create an Em See Pea server with an accessible React form.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,15 +18,16 @@
18
18
  "prepack": "npm run build:initializer"
19
19
  },
20
20
  "devDependencies": {
21
- "@emseepea/react": "0.0.9",
22
- "@emseepea/server": "0.3.3",
21
+ "@emseepea/feedback": "0.2.0",
22
+ "@emseepea/react": "0.0.13",
23
+ "@emseepea/server": "0.6.1",
23
24
  "@emseepea/tailwind": "0.0.2",
24
25
  "react": "19.2.8",
25
26
  "react-dom": "19.2.8",
26
27
  "@emseepea/example-ui-shared": "0.0.0",
27
28
  "playwright": "1.62.1",
28
29
  "axe-core": "4.13.0",
29
- "@emseepea/testing": "0.5.3",
30
+ "@emseepea/testing": "0.9.3",
30
31
  "@types/node": "24.13.3",
31
32
  "@types/react": "19.2.18",
32
33
  "@types/react-dom": "19.2.5",