@emseepea/create-soap-backed-server 0.0.1 → 0.0.5

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
@@ -26,6 +26,30 @@ lint checks, ordinary tests, and a semantic test.
26
26
 
27
27
  <!-- generated-project-readme -->
28
28
 
29
+ ## Choose Open or Protected Access
30
+
31
+ Start open when the catalogue and operations are public.
32
+
33
+ To protect this template, pass both options to the app factory:
34
+
35
+ - `access: { access: "protected", requiredScopes: ["peas:read"] }`
36
+ - an `authentication` adapter
37
+
38
+ Keep `authentication.discovery` as `"public"` unless capability names or
39
+ schemas are sensitive. Use `"protected"` only when each principal should see a
40
+ permission-filtered catalogue. OAuth metadata remains public in both modes.
41
+
42
+ ## Add Observability
43
+
44
+ The same factory accepts `observability`.
45
+
46
+ - Use `structuredLogging` for safe structured events.
47
+ - Use `openTelemetry` for traces and metrics.
48
+
49
+ Adapters receive only redacted framework events. They never receive request
50
+ bodies, arguments, results, tokens, provider claims, or raw errors. See the
51
+ [server API](https://github.com/emseepea/emseepea/tree/main/packages/framework#authentication-and-observability) for the complete configuration.
52
+
29
53
  ## SOAP-Backed Server
30
54
 
31
55
  The committed WSDL defines the operation and imports the committed service XSD.
@@ -77,6 +101,13 @@ service XSD. Public failures contain no provider details.
77
101
  `get-pea-variety` retrieves one variety and returns described JSON. The MCP
78
102
  caller never sees SOAP XML or chooses transport details.
79
103
 
104
+ ## Add Feedback
105
+
106
+ Install `@emseepea/feedback` when this server needs a detailed one-way
107
+ observation or a durable support conversation. Pass its tools through the
108
+ application factory's `additionalTools` option. Choose PostgreSQL, Firestore,
109
+ GitHub Issues, or Zendesk in the [feedback guide](../../packages/feedback/README.md).
110
+
80
111
  ## Check This Project
81
112
 
82
113
  Run generation, lint, build, and ordinary tests without spending model tokens:
@@ -1,4 +1,28 @@
1
- # SOAP-Backed Server
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
+ ## SOAP-Backed Server
2
26
 
3
27
  The committed WSDL defines the operation and imports the committed service XSD.
4
28
  That XSD generates `src/generated/pea-service.ts`. The same XSD is imported by
@@ -49,6 +73,13 @@ service XSD. Public failures contain no provider details.
49
73
  `get-pea-variety` retrieves one variety and returns described JSON. The MCP
50
74
  caller never sees SOAP XML or chooses transport details.
51
75
 
76
+ ## Add Feedback
77
+
78
+ Install `@emseepea/feedback` when this server needs a detailed one-way
79
+ observation or a durable support conversation. Pass its tools through the
80
+ application factory's `additionalTools` option. Choose PostgreSQL, Firestore,
81
+ GitHub Issues, or Zendesk in the [feedback guide](../../packages/feedback/README.md).
82
+
52
83
  ## Check This Project
53
84
 
54
85
  Run generation, lint, build, and ordinary tests without spending model tokens:
@@ -1,25 +1,37 @@
1
1
  import test from "node:test";
2
2
  import {
3
+ assertNoNegativeFeedback,
3
4
  assertResponseMeaning,
4
- assertToolCalls,
5
+ assertToolCallsWithOptionalFeedback,
5
6
  createConversation,
6
7
  } from "@emseepea/testing/semantic";
7
8
  import { startSoapFixture } from "../test-support/soap-fixture.mjs";
8
9
 
10
+ const server = new URL(import.meta.resolve("@emseepea/feedback/testing-server"));
11
+
9
12
  test("retrieves a pea variety from SOAP through a natural request", async (t) => {
10
13
  const fixture = await startSoapFixture(t);
11
14
  const chat = await createConversation(t, {
12
- server: new URL("../dist/server.js", import.meta.url),
13
- environment: { PEA_SOAP_URL: fixture.url.href },
15
+ server,
16
+ environment: {
17
+ PEA_SOAP_URL: fixture.url.href,
18
+ EMSEEPEA_EVAL_APP_MODULE: new URL("../dist/app.js", import.meta.url).href,
19
+ EMSEEPEA_EVAL_APP_FACTORY: "createSoapExample",
20
+ EMSEEPEA_EVAL_APP_KIND: "soap",
21
+ },
14
22
  });
15
23
 
16
24
  // One turn covers the example's only model-visible decision. XML validation
17
25
  // belongs in deterministic tests and would only waste model calls here.
18
26
  const result = await chat.send("Tell me about the Sugar Ann pea variety.");
19
- assertToolCalls(result, [{ name: "get-pea-variety", arguments: { name: "Sugar Ann" } }]);
27
+ await assertToolCallsWithOptionalFeedback(result, [{
28
+ name: "get-pea-variety",
29
+ arguments: { name: "Sugar Ann" },
30
+ }]);
20
31
  await assertResponseMeaning(result, {
21
32
  expected:
22
33
  "Sugar Ann is a snap pea that typically matures in 56 days. " +
23
34
  "It is an early bush variety with compact plants and edible pods.",
24
35
  });
36
+ assertNoNegativeFeedback(result);
25
37
  });
@@ -16,7 +16,8 @@
16
16
  "lint": "oxlint src test test-types eval scripts test-support"
17
17
  },
18
18
  "devDependencies": {
19
- "@emseepea/testing": "0.5.3",
19
+ "@emseepea/feedback": "0.2.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.3.3",
30
+ "@emseepea/server": "0.6.1",
30
31
  "@types/sax": "1.2.7",
31
32
  "soap": "1.11.0",
32
33
  "xml-xsd-engine": "1.7.3",
@@ -1,11 +1,21 @@
1
1
  import { fileURLToPath } from "node:url";
2
- import { createEmseepea, discoverCapabilities } from "@emseepea/server";
2
+ import {
3
+ createEmseepea,
4
+ discoverCapabilities,
5
+ type AccessPolicy,
6
+ type EmseepeaExtensions,
7
+ } from "@emseepea/server";
3
8
  import { createClientAsync } from "soap";
4
9
  import { z } from "zod";
5
10
  import { loadSoapEnvelopeSchema } from "./soap-schema.js";
6
11
  import { ValidatingHttpClient } from "./validating-http-client.js";
7
12
 
8
- export async function createSoapExample(endpointValue: string) {
13
+ export interface SoapExampleOptions extends EmseepeaExtensions {
14
+ readonly access?: AccessPolicy;
15
+ }
16
+
17
+ export async function createSoapExample(endpointValue: string, options: SoapExampleOptions = {}) {
18
+ const { access = { access: "public" }, ...extensions } = options;
9
19
  const endpoint = new URL(z.string().url().parse(endpointValue));
10
20
  if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.hash) {
11
21
  throw new Error("SOAP endpoint must be an HTTP address without credentials or a fragment");
@@ -20,7 +30,8 @@ export async function createSoapExample(endpointValue: string) {
20
30
  name: "emseepea-soap-backed-server",
21
31
  version: "0.0.0",
22
32
  instructions: "Retrieve pea variety details from a legacy SOAP service.",
23
- ...await discoverCapabilities(new URL("./capabilities/", import.meta.url), { client }),
33
+ ...await discoverCapabilities(new URL("./capabilities/", import.meta.url), { client, access }),
34
+ ...extensions,
24
35
  });
25
36
  return { app };
26
37
  }
@@ -1,5 +1,7 @@
1
1
  import type { Client } from "soap";
2
+ import type { AccessPolicy } from "@emseepea/server";
2
3
 
3
4
  export interface SoapExampleContext {
4
5
  readonly client: Client;
6
+ readonly access: AccessPolicy;
5
7
  }
@@ -1,4 +1,4 @@
1
- import { defineTool, type CapabilityModuleFactory } from "@emseepea/server";
1
+ import { defineTool, type CapabilityModuleFactory, type ToolContext } from "@emseepea/server";
2
2
  import { z } from "zod";
3
3
  import type { GetPeaRequest, GetPeaResponse } from "../generated/pea-service.js";
4
4
  import type { SoapExampleContext } from "./context.js";
@@ -14,13 +14,13 @@ const outputSchema = z.object({
14
14
  traits: z.array(z.string().max(100)).max(5).describe("Growing or eating traits."),
15
15
  });
16
16
 
17
- export default (({ client }) => defineTool({
17
+ export default (({ client, access }) => defineTool({
18
18
  name: "get-pea-variety",
19
- access: "public",
19
+ ...access,
20
20
  description: "Get details about one pea variety.",
21
21
  inputSchema,
22
22
  outputSchema,
23
- async handler({ name }, { signal }) {
23
+ async handler({ name }, { signal }: ToolContext) {
24
24
  signal.throwIfAborted();
25
25
  const request: GetPeaRequest = { name };
26
26
  const [response] = await client.GetPeaAsync(request, { signal });
@@ -2,7 +2,11 @@ import assert from "node:assert/strict";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import test from "node:test";
4
4
 
5
- import { startMcpServer } from "@emseepea/testing";
5
+ import {
6
+ insecureTestAuthentication,
7
+ startEmseepea,
8
+ startMcpServer,
9
+ } from "@emseepea/testing";
6
10
  import { createSoapExample } from "../dist/app.js";
7
11
  import { artifactsFromSources } from "../scripts/generate-types.mjs";
8
12
  import { parseSoapEnvelopeSchema } from "../dist/soap-schema.js";
@@ -90,6 +94,22 @@ test("retrieves XSD-validated SOAP data through a described MCP schema", async (
90
94
  });
91
95
  });
92
96
 
97
+ test("the same template composes protected access and observability", async (t) => {
98
+ const fixture = await startSoapFixture(t);
99
+ const events = [];
100
+ const permissions = ["varieties:read"];
101
+ const { app } = await createSoapExample(fixture.url.href, {
102
+ access: { access: "protected", requiredScopes: permissions },
103
+ authentication: insecureTestAuthentication(permissions),
104
+ observability: [{ id: "test-log", emit: (event) => events.push(event) }],
105
+ });
106
+ const running = await startEmseepea(t, app);
107
+ const client = await running.connect("test-token");
108
+ const result = await client.callTool({ name: "get-pea-variety", arguments: { name: "Sugar Ann" } });
109
+ assert.equal(result.isError, false);
110
+ assert.ok(events.some(({ capability }) => capability === "get-pea-variety"));
111
+ });
112
+
93
113
  test("rejects unsafe and invalid SOAP responses with generic public failures", async (t) => {
94
114
  const fixture = await startSoapFixture(t);
95
115
  const running = await startMcpServer(t, new URL("../dist/server.js", import.meta.url), {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emseepea/create-soap-backed-server",
3
- "version": "0.0.1",
3
+ "version": "0.0.5",
4
4
  "description": "Create an Em See Pea server that validates a SOAP service from its XSD contract.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,11 +20,12 @@
20
20
  "prepack": "npm run build:initializer"
21
21
  },
22
22
  "devDependencies": {
23
- "@emseepea/server": "0.3.3",
23
+ "@emseepea/feedback": "0.2.0",
24
+ "@emseepea/server": "0.6.1",
24
25
  "soap": "1.11.0",
25
26
  "xml-xsd-engine": "1.7.3",
26
27
  "zod": "4.4.3",
27
- "@emseepea/testing": "0.5.3",
28
+ "@emseepea/testing": "0.9.3",
28
29
  "@types/node": "24.13.3",
29
30
  "@types/sax": "1.2.7",
30
31
  "typescript": "6.0.3",