@emseepea/create-api-backed-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 +33 -2
- package/initializer-dist/template/README.md +32 -1
- package/initializer-dist/template/eval/meaning.test.mjs +4 -2
- package/initializer-dist/template/package.json +3 -2
- package/initializer-dist/template/src/app.ts +17 -3
- package/initializer-dist/template/src/capabilities/tool.search-pea-taxa.ts +7 -4
- package/initializer-dist/template/test/server.test.mjs +19 -0
- package/initializer-dist/template/test-support/llm-server.mjs +7 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@ This directory is both the maintained example and its public npm initializer.
|
|
|
6
6
|
|
|
7
7
|
Use this template when a tool calls a public JSON API and must validate and
|
|
8
8
|
select the fields returned to the caller. Choose the [tool server](../tool-server/README.md)
|
|
9
|
-
when all data is local
|
|
10
|
-
|
|
9
|
+
when all data is local. Add authentication to either template when calling the
|
|
10
|
+
tool requires permission. [Compare all templates](https://emseepea.github.io/emseepea/examples/).
|
|
11
11
|
|
|
12
12
|
## Create a Project
|
|
13
13
|
|
|
@@ -17,6 +17,30 @@ npm init @emseepea/api-backed-server -- my-server
|
|
|
17
17
|
|
|
18
18
|
<!-- generated-project-readme -->
|
|
19
19
|
|
|
20
|
+
## Choose Open or Protected Access
|
|
21
|
+
|
|
22
|
+
Start open when the catalogue and operations are public.
|
|
23
|
+
|
|
24
|
+
To protect this template, pass both options to the app factory:
|
|
25
|
+
|
|
26
|
+
- `access: { access: "protected", requiredScopes: ["peas:read"] }`
|
|
27
|
+
- an `authentication` adapter
|
|
28
|
+
|
|
29
|
+
Keep `authentication.discovery` as `"public"` unless capability names or
|
|
30
|
+
schemas are sensitive. Use `"protected"` only when each principal should see a
|
|
31
|
+
permission-filtered catalogue. OAuth metadata remains public in both modes.
|
|
32
|
+
|
|
33
|
+
## Add Observability
|
|
34
|
+
|
|
35
|
+
The same factory accepts `observability`.
|
|
36
|
+
|
|
37
|
+
- Use `structuredLogging` for safe structured events.
|
|
38
|
+
- Use `openTelemetry` for traces and metrics.
|
|
39
|
+
|
|
40
|
+
Adapters receive only redacted framework events. They never receive request
|
|
41
|
+
bodies, arguments, results, tokens, provider claims, or raw errors. See the
|
|
42
|
+
[server API](https://github.com/emseepea/emseepea/tree/main/packages/framework#authentication-and-observability) for the complete configuration.
|
|
43
|
+
|
|
20
44
|
## Public Web Service Backend Example
|
|
21
45
|
|
|
22
46
|
Choose this example when your MCP tool needs to read a public web service and
|
|
@@ -57,6 +81,13 @@ npm start
|
|
|
57
81
|
|
|
58
82
|
The endpoint is `http://127.0.0.1:3000/mcp`.
|
|
59
83
|
|
|
84
|
+
## Add Feedback
|
|
85
|
+
|
|
86
|
+
Install `@emseepea/feedback` when this server needs a detailed one-way
|
|
87
|
+
observation or a durable support conversation. Pass its tools through the
|
|
88
|
+
application factory's `additionalTools` option. Choose PostgreSQL, Firestore,
|
|
89
|
+
GitHub Issues, or Zendesk in the [feedback guide](../../packages/feedback/README.md).
|
|
90
|
+
|
|
60
91
|
## Check This Example
|
|
61
92
|
|
|
62
93
|
[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
|
+
## Public Web Service Backend Example
|
|
2
26
|
|
|
3
27
|
Choose this example when your MCP tool needs to read a public web service and
|
|
4
28
|
return selected, checked data in a clear result for an assistant.
|
|
@@ -38,6 +62,13 @@ npm start
|
|
|
38
62
|
|
|
39
63
|
The endpoint is `http://127.0.0.1:3000/mcp`.
|
|
40
64
|
|
|
65
|
+
## Add Feedback
|
|
66
|
+
|
|
67
|
+
Install `@emseepea/feedback` when this server needs a detailed one-way
|
|
68
|
+
observation or a durable support conversation. Pass its tools through the
|
|
69
|
+
application factory's `additionalTools` option. Choose PostgreSQL, Firestore,
|
|
70
|
+
GitHub Issues, or Zendesk in the [feedback guide](../../packages/feedback/README.md).
|
|
71
|
+
|
|
41
72
|
## Check This Example
|
|
42
73
|
|
|
43
74
|
[Ordinary tests](test/) live in `test/`.
|
|
@@ -1,9 +1,10 @@
|
|
|
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
|
|
|
@@ -20,7 +21,7 @@ test("searches once and remembers the common name for a follow-up", async (t) =>
|
|
|
20
21
|
"and does that count estimate the wild population?",
|
|
21
22
|
);
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
await assertToolCallsWithOptionalFeedback(search, [{
|
|
24
25
|
name: "search-pea-taxa",
|
|
25
26
|
arguments: { query: "pea" },
|
|
26
27
|
}]);
|
|
@@ -35,4 +36,5 @@ test("searches once and remembers the common name for a follow-up", async (t) =>
|
|
|
35
36
|
const followUp = await chat.send("What was its common name?");
|
|
36
37
|
assertNoToolCalls(followUp);
|
|
37
38
|
assertResponseContains(followUp, "Common Pea");
|
|
39
|
+
assertNoNegativeFeedback(search, followUp);
|
|
38
40
|
});
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"lint": "oxlint src test eval test-support"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@emseepea/
|
|
17
|
+
"@emseepea/feedback": "0.2.0",
|
|
18
|
+
"@emseepea/testing": "0.9.3",
|
|
18
19
|
"@modelcontextprotocol/client": "2.0.0",
|
|
19
20
|
"@types/node": "24.13.3",
|
|
20
21
|
"typescript": "6.0.3",
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
},
|
|
26
27
|
"private": true,
|
|
27
28
|
"dependencies": {
|
|
28
|
-
"@emseepea/server": "0.
|
|
29
|
+
"@emseepea/server": "0.6.1",
|
|
29
30
|
"zod": "4.4.3"
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createEmseepea,
|
|
3
|
+
discoverCapabilities,
|
|
4
|
+
type AccessPolicy,
|
|
5
|
+
type EmseepeaExtensions,
|
|
6
|
+
} from "@emseepea/server";
|
|
2
7
|
import type { JsonHttpClient } from "@emseepea/server/http";
|
|
3
8
|
|
|
4
|
-
export
|
|
9
|
+
export interface BackendExampleOptions extends EmseepeaExtensions {
|
|
10
|
+
readonly access?: AccessPolicy;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function createBackendExample(
|
|
14
|
+
client: JsonHttpClient,
|
|
15
|
+
options: BackendExampleOptions = {},
|
|
16
|
+
): Promise<ReturnType<typeof createEmseepea>> {
|
|
17
|
+
const { access = { access: "public" }, ...extensions } = options;
|
|
5
18
|
return createEmseepea({
|
|
6
19
|
name: "emseepea-api-backed-server",
|
|
7
20
|
version: "0.0.0",
|
|
8
21
|
instructions: "Use search-pea-taxa to search iNaturalist's public taxon catalogue for pea species.",
|
|
9
|
-
...await discoverCapabilities(new URL("./capabilities/", import.meta.url), { client }),
|
|
22
|
+
...await discoverCapabilities(new URL("./capabilities/", import.meta.url), { client, access }),
|
|
23
|
+
...extensions,
|
|
10
24
|
});
|
|
11
25
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { defineMappedTool, type CapabilityModuleFactory } from "@emseepea/server";
|
|
1
|
+
import { defineMappedTool, type AccessPolicy, type CapabilityModuleFactory } from "@emseepea/server";
|
|
2
2
|
import type { JsonHttpClient } from "@emseepea/server/http";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
|
-
export interface BackendExampleContext {
|
|
5
|
+
export interface BackendExampleContext {
|
|
6
|
+
readonly client: JsonHttpClient;
|
|
7
|
+
readonly access: AccessPolicy;
|
|
8
|
+
}
|
|
6
9
|
|
|
7
10
|
const backendTaxon = z.object({
|
|
8
11
|
id: z.number().int().positive().describe("iNaturalist identifier for the taxon."),
|
|
@@ -35,9 +38,9 @@ const backendInputSchema = z.object({
|
|
|
35
38
|
});
|
|
36
39
|
const backendOutputSchema = z.object({ request: backendInputSchema, payload: backendPayload });
|
|
37
40
|
|
|
38
|
-
export default (({ client }) => defineMappedTool({
|
|
41
|
+
export default (({ client, access }) => defineMappedTool({
|
|
39
42
|
name: "search-pea-taxa",
|
|
40
|
-
access
|
|
43
|
+
...access,
|
|
41
44
|
description: "Search iNaturalist's public taxon catalogue for pea species.",
|
|
42
45
|
inputSchema,
|
|
43
46
|
outputSchema,
|
|
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
4
4
|
import { serveEmseepea } from "@emseepea/server";
|
|
5
|
+
import { insecureTestAuthentication, startEmseepea } from "@emseepea/testing";
|
|
5
6
|
import { createBackendExample } from "../dist/app.js";
|
|
6
7
|
import { inaturalistFixture } from "../test-support/inaturalist-fixture.mjs";
|
|
7
8
|
|
|
@@ -113,3 +114,21 @@ test("the API-backed example checks and passes through selected iNaturalist valu
|
|
|
113
114
|
await running.close();
|
|
114
115
|
}
|
|
115
116
|
});
|
|
117
|
+
|
|
118
|
+
test("the same template composes protected access and observability", async (t) => {
|
|
119
|
+
const events = [];
|
|
120
|
+
const permissions = ["taxa:search"];
|
|
121
|
+
const app = await createBackendExample(
|
|
122
|
+
{ get: async () => inaturalistFixture },
|
|
123
|
+
{
|
|
124
|
+
access: { access: "protected", requiredScopes: permissions },
|
|
125
|
+
authentication: insecureTestAuthentication(permissions),
|
|
126
|
+
observability: [{ id: "test-log", emit: (event) => events.push(event) }],
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
const running = await startEmseepea(t, app);
|
|
130
|
+
const client = await running.connect("test-token");
|
|
131
|
+
const result = await client.callTool({ name: "search-pea-taxa", arguments: { query: "pea" } });
|
|
132
|
+
assert.equal(result.isError, false);
|
|
133
|
+
assert.ok(events.some(({ capability }) => capability === "search-pea-taxa"));
|
|
134
|
+
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { serveEmseepea } from "@emseepea/server";
|
|
3
|
+
import { defineFeedbackSubmission } from "@emseepea/feedback";
|
|
3
4
|
import { createBackendExample } from "../dist/app.js";
|
|
4
5
|
import { inaturalistFixture } from "./inaturalist-fixture.mjs";
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const client = {
|
|
7
8
|
async get({ pathname, searchParams }) {
|
|
8
9
|
assert.equal(pathname, "/v1/taxa");
|
|
9
10
|
const { q, ...options } = searchParams;
|
|
@@ -11,7 +12,12 @@ const app = await createBackendExample({
|
|
|
11
12
|
assert.deepEqual(options, { rank: "species", per_page: "5" });
|
|
12
13
|
return inaturalistFixture;
|
|
13
14
|
},
|
|
15
|
+
};
|
|
16
|
+
const feedback = defineFeedbackSubmission({
|
|
17
|
+
access: "public",
|
|
18
|
+
backend: { submit: () => ({ id: crypto.randomUUID(), recordedAt: new Date().toISOString() }) },
|
|
14
19
|
});
|
|
20
|
+
const app = await createBackendExample(client, { additionalTools: [feedback] });
|
|
15
21
|
const running = await serveEmseepea(app, { port: 0 });
|
|
16
22
|
|
|
17
23
|
console.log(`Em See Pea API-backed fixture listening at ${running.url}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emseepea/create-api-backed-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Create an Em See Pea server backed by a public web API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,9 +18,10 @@
|
|
|
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
|
"zod": "4.4.3",
|
|
23
|
-
"@emseepea/testing": "0.
|
|
24
|
+
"@emseepea/testing": "0.9.3",
|
|
24
25
|
"@modelcontextprotocol/client": "2.0.0",
|
|
25
26
|
"@types/node": "24.13.3",
|
|
26
27
|
"typescript": "6.0.3",
|