@emseepea/create-html-ui-server 0.0.15 → 0.0.19
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 +31 -0
- package/initializer-dist/template/README.md +32 -1
- package/initializer-dist/template/eval/meaning.test.mjs +12 -3
- package/initializer-dist/template/package.json +3 -2
- package/initializer-dist/template/src/app.ts +30 -0
- package/initializer-dist/template/src/capabilities/tool.preview-planting-plan.ts +2 -2
- package/initializer-dist/template/src/server.ts +4 -17
- package/initializer-dist/template/src/ui-shared.ts +8 -3
- package/initializer-dist/template/test/server.test.mjs +19 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -16,6 +16,30 @@ npm init @emseepea/html-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
|
## Native UI Example
|
|
20
44
|
|
|
21
45
|
Choose this example when you need an accessible form but do not want React or
|
|
@@ -48,6 +72,13 @@ Open
|
|
|
48
72
|
|
|
49
73
|
The page previews a sample pea planting plan only. It does not send or store a report.
|
|
50
74
|
|
|
75
|
+
## Add Feedback
|
|
76
|
+
|
|
77
|
+
Install `@emseepea/feedback` when this server needs a detailed one-way
|
|
78
|
+
observation or a durable support conversation. Pass its tools through the
|
|
79
|
+
application factory's `additionalTools` option. Choose PostgreSQL, Firestore,
|
|
80
|
+
GitHub Issues, or Zendesk in the [feedback guide](../../packages/feedback/README.md).
|
|
81
|
+
|
|
51
82
|
## Check This Example
|
|
52
83
|
|
|
53
84
|
[Ordinary tests](test/) live in `test/`.
|
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
#
|
|
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
|
+
## Native UI Example
|
|
2
26
|
|
|
3
27
|
Choose this example when you need an accessible form but do not want React or
|
|
4
28
|
another browser UI framework.
|
|
@@ -30,6 +54,13 @@ Open
|
|
|
30
54
|
|
|
31
55
|
The page previews a sample pea planting plan only. It does not send or store a report.
|
|
32
56
|
|
|
57
|
+
## Add Feedback
|
|
58
|
+
|
|
59
|
+
Install `@emseepea/feedback` when this server needs a detailed one-way
|
|
60
|
+
observation or a durable support conversation. Pass its tools through the
|
|
61
|
+
application factory's `additionalTools` option. Choose PostgreSQL, Firestore,
|
|
62
|
+
GitHub Issues, or Zendesk in the [feedback guide](../../packages/feedback/README.md).
|
|
63
|
+
|
|
33
64
|
## Check This Example
|
|
34
65
|
|
|
35
66
|
[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
|
-
|
|
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: "createHtmlUiServer",
|
|
15
|
+
};
|
|
16
|
+
|
|
10
17
|
test("does not mistake a native UI preview for a completed effect", async (t) => {
|
|
11
18
|
const chat = await createConversation(t, {
|
|
12
|
-
server
|
|
19
|
+
server,
|
|
20
|
+
environment,
|
|
13
21
|
});
|
|
14
22
|
|
|
15
23
|
// The judged turn covers the UI tool's main semantic risk: preview is not an
|
|
@@ -19,7 +27,7 @@ test("does not mistake a native UI preview for a completed effect", async (t) =>
|
|
|
19
27
|
"Summarize the matches and say whether anything was sent, stored, or changed.",
|
|
20
28
|
);
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
await assertToolCallsWithOptionalFeedback(response, [{
|
|
23
31
|
name: "preview-planting-plan",
|
|
24
32
|
arguments: { title: "Snap pea plan", peaType: "snap", includeTips: true },
|
|
25
33
|
}]);
|
|
@@ -35,4 +43,5 @@ test("does not mistake a native UI preview for a completed effect", async (t) =>
|
|
|
35
43
|
);
|
|
36
44
|
assertNoToolCalls(followUp);
|
|
37
45
|
assertResponseContains(followUp, "2");
|
|
46
|
+
assertNoNegativeFeedback(response, followUp);
|
|
38
47
|
});
|
|
@@ -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.
|
|
20
|
+
"@emseepea/testing": "0.9.3",
|
|
20
21
|
"@types/node": "24.13.3",
|
|
21
22
|
"typescript": "6.0.3",
|
|
22
23
|
"oxlint": "1.80.0"
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
},
|
|
27
28
|
"private": true,
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"@emseepea/server": "0.
|
|
30
|
+
"@emseepea/server": "0.6.1",
|
|
30
31
|
"@emseepea/tailwind": "0.0.2"
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -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 HtmlUiServerOptions extends EmseepeaExtensions {
|
|
11
|
+
readonly access?: AccessPolicy;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function createHtmlUiServer(options: HtmlUiServerOptions = {}) {
|
|
15
|
+
const { access = { access: "public" }, ...extensions } = options;
|
|
16
|
+
const app = createEmseepea({
|
|
17
|
+
name: "emseepea-html-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 {
|
|
1
|
+
import { serveEmseepea } from "@emseepea/server";
|
|
2
|
+
import { createHtmlUiServer } from "./app.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const app = createEmseepea({
|
|
6
|
-
name: "emseepea-html-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 createHtmlUiServer(), {
|
|
5
|
+
port: Number.parseInt(process.env.PORT ?? "3000", 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 ?? "3000", 10) });
|
|
20
7
|
console.log(`Em See Pea native UI example listening at ${running.url}`);
|
|
21
8
|
|
|
22
9
|
async function shutdown(): Promise<void> {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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 {
|
|
4
|
+
import {
|
|
5
|
+
insecureTestAuthentication,
|
|
6
|
+
startEmseepea,
|
|
7
|
+
startMcpServer,
|
|
8
|
+
} from "@emseepea/testing";
|
|
9
|
+
import { createHtmlUiServer } 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 createHtmlUiServer({
|
|
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-html-ui-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "Create an Em See Pea server with an accessible HTML form.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,12 +18,13 @@
|
|
|
18
18
|
"prepack": "npm run build:initializer"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@emseepea/
|
|
21
|
+
"@emseepea/feedback": "0.2.0",
|
|
22
|
+
"@emseepea/server": "0.6.1",
|
|
22
23
|
"@emseepea/tailwind": "0.0.2",
|
|
23
24
|
"@emseepea/example-ui-shared": "0.0.0",
|
|
24
25
|
"playwright": "1.62.1",
|
|
25
26
|
"axe-core": "4.13.0",
|
|
26
|
-
"@emseepea/testing": "0.
|
|
27
|
+
"@emseepea/testing": "0.9.3",
|
|
27
28
|
"@types/node": "24.13.3",
|
|
28
29
|
"typescript": "6.0.3",
|
|
29
30
|
"oxlint": "1.80.0"
|