@emseepea/create-api-backed-server 0.0.1 → 0.0.3

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
@@ -1,22 +1,69 @@
1
1
  # `@emseepea/create-api-backed-server`
2
2
 
3
- Create a private, standalone Em See Pea project whose tool reads a public web
4
- API. The package builds its starter from the maintained
5
- [API-backed server example](https://github.com/emseepea/emseepea/tree/main/examples/backend-no-ui).
6
-
7
- ## Create the Project
8
-
9
- This initializer is queued for the next pre-alpha release and is not yet
10
- available from npm.
3
+ This directory is both the maintained example and its public npm initializer.
11
4
 
12
5
  ```sh
13
6
  npm init @emseepea/api-backed-server@next -- my-server
14
7
  ```
15
8
 
9
+ <!-- generated-project-readme -->
10
+
11
+ ## Public Web Service Backend Example
12
+
13
+ Choose this example when your MCP tool needs to read a public web service and
14
+ return selected, checked data in a clear result for an assistant.
15
+
16
+ This example exposes one read-only `search-coffee-catalog` tool. A normal run
17
+ searches [BrewMark's public coffee catalogue](https://brewmark.io/developers/api-docs)
18
+ and returns at most five coffees.
19
+
20
+ Unlike the [first public tool example](https://github.com/emseepea/emseepea/tree/main/examples/basic-no-ui),
21
+ this tool adapts a separate service. The public input and result use the Model Context Protocol
22
+ (MCP). BrewMark's query and response are checked before the result is returned.
23
+
24
+ The tool preserves BrewMark's useful field names and values. It does not keep a
25
+ second list of roast levels, so BrewMark can add a valid level without requiring
26
+ an MCP release. Separate public and backend schemas still select and check every
27
+ field that can reach the caller.
28
+
29
+ The caller can choose a search term and roast filter. The caller cannot change
30
+ the website, path, result limit, sort order, credentials, or HTTP rules. Search
31
+ terms are sent to BrewMark. The example does not send authentication details or
32
+ change data. Do not include personal, secret, or confidential information in a
33
+ search term.
34
+
35
+ The automated checks use invented coffee records through the same MCP server.
36
+ They do not depend on BrewMark being available and do not make a speed or uptime
37
+ claim for BrewMark. Normal runs use BrewMark's fair-use public web service.
38
+
39
+ ## Run
40
+
41
+ From this directory:
42
+
16
43
  ```sh
17
- cd my-server
18
44
  npm install
19
- npm test
20
- npm run lint
45
+ npm run build
21
46
  npm start
22
47
  ```
48
+
49
+ The endpoint is `http://127.0.0.1:3000/mcp`.
50
+
51
+ ## Check This Example
52
+
53
+ [Ordinary tests](test/) live in `test/`.
54
+ The [AI tool-choice and understanding test](eval/meaning.test.mjs) lives separately in `eval/`.
55
+ The commands below run each suite independently.
56
+
57
+ Run its build, mapping, validation, and MCP checks:
58
+
59
+ ```sh
60
+ npm test
61
+ ```
62
+
63
+ Check that Claude chooses catalogue search and understands the rating scales:
64
+
65
+ ```sh
66
+ npm run test:llm
67
+ ```
68
+
69
+ If Claude is not already signed in, run `claude auth login` first.
@@ -1,7 +1,7 @@
1
1
  # Public Web Service Backend Example
2
2
 
3
3
  Choose this example when your MCP tool needs to read a public web service and
4
- translate its data into a clear result for an assistant.
4
+ return selected, checked data in a clear result for an assistant.
5
5
 
6
6
  This example exposes one read-only `search-coffee-catalog` tool. A normal run
7
7
  searches [BrewMark's public coffee catalogue](https://brewmark.io/developers/api-docs)
@@ -11,6 +11,11 @@ Unlike the [first public tool example](https://github.com/emseepea/emseepea/tree
11
11
  this tool adapts a separate service. The public input and result use the Model Context Protocol
12
12
  (MCP). BrewMark's query and response are checked before the result is returned.
13
13
 
14
+ The tool preserves BrewMark's useful field names and values. It does not keep a
15
+ second list of roast levels, so BrewMark can add a valid level without requiring
16
+ an MCP release. Separate public and backend schemas still select and check every
17
+ field that can reach the caller.
18
+
14
19
  The caller can choose a search term and roast filter. The caller cannot change
15
20
  the website, path, result limit, sort order, credentials, or HTTP rules. Search
16
21
  terms are sent to BrewMark. The example does not send authentication details or
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "emseepea-starter",
3
3
  "version": "0.0.0",
4
- "private": true,
4
+ "description": "Create an Em See Pea server backed by a public web API.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -13,10 +13,6 @@
13
13
  "test:llm:built": "emseepea-test eval",
14
14
  "lint": "oxlint src test eval test-support"
15
15
  },
16
- "dependencies": {
17
- "@emseepea/server": "0.0.4",
18
- "zod": "4.4.3"
19
- },
20
16
  "devDependencies": {
21
17
  "@emseepea/testing": "0.2.1",
22
18
  "@modelcontextprotocol/client": "2.0.0",
@@ -26,5 +22,10 @@
26
22
  },
27
23
  "engines": {
28
24
  "node": ">=22"
25
+ },
26
+ "private": true,
27
+ "dependencies": {
28
+ "@emseepea/server": "0.2.1",
29
+ "zod": "4.4.3"
29
30
  }
30
31
  }
@@ -0,0 +1,11 @@
1
+ import { createEmseepea, discoverCapabilities } from "@emseepea/server";
2
+ import type { JsonHttpClient } from "@emseepea/server/http";
3
+
4
+ export async function createBackendExample(client: JsonHttpClient): Promise<ReturnType<typeof createEmseepea>> {
5
+ return createEmseepea({
6
+ name: "emseepea-backend-no-ui",
7
+ version: "0.0.0",
8
+ instructions: "Use search-coffee-catalog to search BrewMark's public coffee catalogue.",
9
+ ...await discoverCapabilities(new URL("./capabilities/", import.meta.url), { client }),
10
+ });
11
+ }
@@ -0,0 +1,103 @@
1
+ import { defineMappedTool, type CapabilityModuleFactory } from "@emseepea/server";
2
+ import type { JsonHttpClient } from "@emseepea/server/http";
3
+ import { z } from "zod";
4
+
5
+ export interface BackendExampleContext { readonly client: JsonHttpClient }
6
+
7
+ const roastLevel = z.string().trim().min(1).max(40);
8
+ const searchInput = z.object({
9
+ query: z.string().trim().min(2).max(80),
10
+ roastLevel: roastLevel.describe("Optional BrewMark roast level, such as LIGHT or MEDIUM_LIGHT.").optional(),
11
+ });
12
+ const coffee = z.object({
13
+ name: z.string().max(200), roasterName: z.string().max(200), roastLevel,
14
+ origin: z.string().max(200).nullable(), processingMethod: z.string().max(100).nullable(),
15
+ flavorProfile: z.string().max(500).nullable(), acidityLevel: z.number().int().min(1).max(5).nullable(),
16
+ bodyLevel: z.number().int().min(1).max(5).nullable(),
17
+ });
18
+ const searchReport = z.object({
19
+ query: z.string().max(80), roastLevelFilter: roastLevel.nullable(), returnedCount: z.number().int().min(0).max(5),
20
+ moreMatchesAvailable: z.boolean(),
21
+ ratingScale: z.object({
22
+ acidity: z.literal("1 = low acidity; 5 = high acidity"),
23
+ body: z.literal("1 = light body; 5 = full body"),
24
+ }),
25
+ coffees: z.array(coffee).max(5), source: z.literal("BrewMark"), sourceUrl: z.literal("https://brewmark.io"),
26
+ });
27
+ const backendCommand = z.object({
28
+ pathname: z.literal("/api/coffees"),
29
+ searchParams: z.object({
30
+ q: z.string().min(2).max(80), roastLevel: roastLevel.optional(),
31
+ sort: z.literal("alpha"), limit: z.literal("5"),
32
+ }),
33
+ });
34
+ const backendCoffee = z.object({
35
+ name: z.string().max(200), roasterName: z.string().max(200), roastLevel,
36
+ origin: z.string().max(200).nullable(), processingMethod: z.string().max(100).nullable(),
37
+ flavorProfile: z.string().max(500).nullable(), acidityLevel: z.number().int().min(1).max(5).nullable(),
38
+ bodyLevel: z.number().int().min(1).max(5).nullable(),
39
+ });
40
+ const backendPayload = z.object({
41
+ data: z.array(backendCoffee).max(5), cursor: z.string().max(2_048).nullable(), hasMore: z.boolean(),
42
+ });
43
+ const backendResult = z.object({ request: backendCommand, payload: backendPayload });
44
+
45
+ export default (({ client }) => defineMappedTool({
46
+ name: "search-coffee-catalog",
47
+ access: "public",
48
+ description: "Search BrewMark's public coffee catalogue and explain its acidity and body ratings.",
49
+ inputSchema: searchInput,
50
+ outputSchema: searchReport,
51
+ backendInputSchema: backendCommand,
52
+ backendOutputSchema: backendResult,
53
+ mapInput: ({ query, roastLevel }) => ({
54
+ pathname: "/api/coffees" as const,
55
+ searchParams: {
56
+ q: query,
57
+ ...(roastLevel ? { roastLevel } : {}),
58
+ sort: "alpha" as const,
59
+ limit: "5" as const,
60
+ },
61
+ }),
62
+ async adapter(request, { signal, deadlineMs }) {
63
+ return { request, payload: await client.get({ ...request, signal, deadlineMs }) };
64
+ },
65
+ mapOutput: ({ request, payload }) => {
66
+ const coffees = payload.data.map((record) => ({
67
+ name: record.name, roasterName: record.roasterName, roastLevel: record.roastLevel,
68
+ origin: record.origin, processingMethod: record.processingMethod,
69
+ flavorProfile: record.flavorProfile, acidityLevel: record.acidityLevel, bodyLevel: record.bodyLevel,
70
+ }));
71
+ const data = {
72
+ query: request.searchParams.q,
73
+ roastLevelFilter: request.searchParams.roastLevel ?? null,
74
+ returnedCount: coffees.length,
75
+ moreMatchesAvailable: payload.hasMore,
76
+ ratingScale: {
77
+ acidity: "1 = low acidity; 5 = high acidity" as const,
78
+ body: "1 = light body; 5 = full body" as const,
79
+ },
80
+ coffees,
81
+ source: "BrewMark" as const,
82
+ sourceUrl: "https://brewmark.io" as const,
83
+ };
84
+ const lines = coffees.map((record) => [
85
+ `${record.name} by ${record.roasterName}`,
86
+ `origin: ${record.origin ?? "not provided"}`,
87
+ `roast level: ${record.roastLevel}`,
88
+ `acidity: ${record.acidityLevel ?? "not provided"}`,
89
+ `body: ${record.bodyLevel ?? "not provided"}`,
90
+ ].join("; "));
91
+ return {
92
+ text: [
93
+ `BrewMark returned ${coffees.length} coffee${coffees.length === 1 ? "" : "s"} for “${data.query}”.`,
94
+ `More matches available: ${data.moreMatchesAvailable ? "yes" : "no"}.`,
95
+ "Acidity: 1 = low acidity; 5 = high acidity.",
96
+ "Body: 1 = light body; 5 = full body.",
97
+ ...lines,
98
+ "Source: https://brewmark.io",
99
+ ].join("\n"),
100
+ data,
101
+ };
102
+ },
103
+ })) satisfies CapabilityModuleFactory<BackendExampleContext>;
@@ -7,7 +7,7 @@ const client = createJsonHttpClient({
7
7
  maxResponseBytes: 128 * 1024,
8
8
  });
9
9
  const running = await serveEmseepea(
10
- createBackendExample(client),
10
+ await createBackendExample(client),
11
11
  { port: Number.parseInt(process.env.PORT ?? "3000", 10) },
12
12
  );
13
13
 
@@ -5,10 +5,10 @@ import { serveEmseepea } from "@emseepea/server";
5
5
  import { createBackendExample } from "../dist/app.js";
6
6
  import { brewmarkFixture } from "../test-support/brewmark-fixture.mjs";
7
7
 
8
- test("the backend example maps, checks, and explains BrewMark data", async () => {
8
+ test("the backend example checks and passes through selected BrewMark values", async () => {
9
9
  const requests = [];
10
10
  let response = brewmarkFixture;
11
- const app = createBackendExample({
11
+ const app = await createBackendExample({
12
12
  async get(options) {
13
13
  requests.push(options);
14
14
  if (response instanceof Error) throw response;
@@ -38,12 +38,12 @@ test("the backend example maps, checks, and explains BrewMark data", async () =>
38
38
 
39
39
  const result = await client.callTool({
40
40
  name: "search-coffee-catalog",
41
- arguments: { query: " natural ", roast: "light" },
41
+ arguments: { query: " natural ", roastLevel: "LIGHT" },
42
42
  });
43
43
  assert.equal(result.isError, false);
44
44
  assert.deepEqual(result.structuredContent, {
45
45
  query: "natural",
46
- roastFilter: "light",
46
+ roastLevelFilter: "LIGHT",
47
47
  returnedCount: 1,
48
48
  moreMatchesAvailable: true,
49
49
  ratingScale: {
@@ -53,11 +53,11 @@ test("the backend example maps, checks, and explains BrewMark data", async () =>
53
53
  coffees: [
54
54
  {
55
55
  name: "Riverlight Natural",
56
- roaster: "North Star Sample Roasters",
56
+ roasterName: "North Star Sample Roasters",
57
+ roastLevel: "LIGHT",
57
58
  origin: "Burundi",
58
- roast: "light",
59
59
  processingMethod: "Natural",
60
- flavourNotes: "Blackberry, hibiscus",
60
+ flavorProfile: "Blackberry, hibiscus",
61
61
  acidityLevel: 5,
62
62
  bodyLevel: 2,
63
63
  },
@@ -81,12 +81,13 @@ test("the backend example maps, checks, and explains BrewMark data", async () =>
81
81
  data: [{
82
82
  name: "Details Pending",
83
83
  roasterName: "Sample Coffee",
84
- roastLevel: "LIGHT",
84
+ roastLevel: "OMNI_ROAST",
85
85
  origin: null,
86
86
  processingMethod: null,
87
87
  flavorProfile: null,
88
88
  acidityLevel: null,
89
89
  bodyLevel: null,
90
+ privateNote: "do not expose",
90
91
  }],
91
92
  cursor: null,
92
93
  hasMore: false,
@@ -98,14 +99,15 @@ test("the backend example maps, checks, and explains BrewMark data", async () =>
98
99
  assert.equal(missingDetails.isError, false);
99
100
  assert.deepEqual(missingDetails.structuredContent.coffees[0], {
100
101
  name: "Details Pending",
101
- roaster: "Sample Coffee",
102
+ roasterName: "Sample Coffee",
103
+ roastLevel: "OMNI_ROAST",
102
104
  origin: null,
103
- roast: "light",
104
105
  processingMethod: null,
105
- flavourNotes: null,
106
+ flavorProfile: null,
106
107
  acidityLevel: null,
107
108
  bodyLevel: null,
108
109
  });
110
+ assert.equal("privateNote" in missingDetails.structuredContent.coffees[0], false);
109
111
  assert.match(missingDetails.content[0].text, /origin: not provided/);
110
112
  assert.match(missingDetails.content[0].text, /acidity: not provided/);
111
113
  assert.match(missingDetails.content[0].text, /body: not provided/);
@@ -3,7 +3,7 @@ import { serveEmseepea } from "@emseepea/server";
3
3
  import { createBackendExample } from "../dist/app.js";
4
4
  import { brewmarkFixture } from "./brewmark-fixture.mjs";
5
5
 
6
- const app = createBackendExample({
6
+ const app = await createBackendExample({
7
7
  async get({ pathname, searchParams }) {
8
8
  assert.equal(pathname, "/api/coffees");
9
9
  const { q, ...options } = searchParams;
package/package.json CHANGED
@@ -1,22 +1,38 @@
1
1
  {
2
2
  "name": "@emseepea/create-api-backed-server",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Create an Em See Pea server backed by a public web API.",
5
5
  "license": "MIT",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/emseepea/emseepea.git",
9
- "directory": "packages/create-api-backed-server"
6
+ "type": "module",
7
+ "starterDependencies": ["@emseepea/server", "zod"],
8
+ "scripts": {
9
+ "build": "npm run build:example && npm run build:initializer",
10
+ "build:example": "tsc -p tsconfig.json",
11
+ "build:initializer": "node ../../scripts/build-initializer.mjs",
12
+ "start": "node dist/server.js",
13
+ "test": "npm run build && npm run test:built",
14
+ "test:built": "node --test test/*.test.mjs",
15
+ "test:llm": "npm run build && npm run test:llm:built",
16
+ "test:llm:built": "emseepea-test eval",
17
+ "lint": "oxlint src test eval test-support",
18
+ "prepack": "npm run build:initializer"
19
+ },
20
+ "devDependencies": {
21
+ "@emseepea/server": "0.2.1",
22
+ "zod": "4.4.3",
23
+ "@emseepea/testing": "0.2.1",
24
+ "@modelcontextprotocol/client": "2.0.0",
25
+ "@types/node": "24.13.3",
26
+ "typescript": "6.0.3",
27
+ "oxlint": "1.80.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
10
31
  },
32
+ "repository": { "type": "git", "url": "git+https://github.com/emseepea/emseepea.git", "directory": "examples/backend-no-ui" },
11
33
  "homepage": "https://emseepea.github.io/emseepea/examples/",
12
34
  "bugs": "https://github.com/emseepea/emseepea/issues",
13
35
  "publishConfig": { "access": "public", "provenance": true, "tag": "next" },
14
- "type": "module",
15
- "bin": { "create-api-backed-server": "./dist/create.mjs" },
16
- "files": ["dist"],
17
- "scripts": {
18
- "build": "node ../../scripts/build-initializer.mjs",
19
- "prepack": "npm run build"
20
- },
21
- "engines": { "node": ">=22" }
36
+ "bin": { "create-api-backed-server": "./initializer-dist/create.mjs" },
37
+ "files": ["initializer-dist"]
22
38
  }
@@ -1,157 +0,0 @@
1
- import { createEmseepea, defineMappedTool } from "@emseepea/server";
2
- import type { JsonHttpClient } from "@emseepea/server/http";
3
- import { z } from "zod";
4
-
5
- const publicRoast = z.enum(["light", "medium-light", "medium", "medium-dark", "dark"]);
6
- const brewmarkRoast = z.enum(["LIGHT", "MEDIUM_LIGHT", "MEDIUM", "MEDIUM_DARK", "DARK"]);
7
- const publicToBrewmarkRoast = {
8
- light: "LIGHT",
9
- "medium-light": "MEDIUM_LIGHT",
10
- medium: "MEDIUM",
11
- "medium-dark": "MEDIUM_DARK",
12
- dark: "DARK",
13
- } as const;
14
- const brewmarkToPublicRoast = {
15
- LIGHT: "light",
16
- MEDIUM_LIGHT: "medium-light",
17
- MEDIUM: "medium",
18
- MEDIUM_DARK: "medium-dark",
19
- DARK: "dark",
20
- } as const;
21
-
22
- const searchInput = z.object({
23
- query: z.string().trim().min(2).max(80),
24
- roast: publicRoast.optional(),
25
- });
26
- const coffee = z.object({
27
- name: z.string().max(200),
28
- roaster: z.string().max(200),
29
- origin: z.string().max(200).nullable(),
30
- roast: publicRoast,
31
- processingMethod: z.string().max(100).nullable(),
32
- flavourNotes: z.string().max(500).nullable(),
33
- acidityLevel: z.number().int().min(1).max(5).nullable(),
34
- bodyLevel: z.number().int().min(1).max(5).nullable(),
35
- });
36
- const searchReport = z.object({
37
- query: z.string().max(80),
38
- roastFilter: publicRoast.nullable(),
39
- returnedCount: z.number().int().min(0).max(5),
40
- moreMatchesAvailable: z.boolean(),
41
- ratingScale: z.object({
42
- acidity: z.literal("1 = low acidity; 5 = high acidity"),
43
- body: z.literal("1 = light body; 5 = full body"),
44
- }),
45
- coffees: z.array(coffee).max(5),
46
- source: z.literal("BrewMark"),
47
- sourceUrl: z.literal("https://brewmark.io"),
48
- });
49
-
50
- const backendCommand = z.object({
51
- pathname: z.literal("/api/coffees"),
52
- searchParams: z.object({
53
- q: z.string().min(2).max(80),
54
- roastLevel: brewmarkRoast.optional(),
55
- sort: z.literal("alpha"),
56
- limit: z.literal("5"),
57
- }),
58
- });
59
- const backendCoffee = z.object({
60
- name: z.string().max(200),
61
- roasterName: z.string().max(200),
62
- roastLevel: brewmarkRoast,
63
- origin: z.string().max(200).nullable(),
64
- processingMethod: z.string().max(100).nullable(),
65
- flavorProfile: z.string().max(500).nullable(),
66
- acidityLevel: z.number().int().min(1).max(5).nullable(),
67
- bodyLevel: z.number().int().min(1).max(5).nullable(),
68
- });
69
- const backendPayload = z.object({
70
- data: z.array(backendCoffee).max(5),
71
- cursor: z.string().max(2_048).nullable(),
72
- hasMore: z.boolean(),
73
- });
74
- const backendResult = z.object({
75
- request: backendCommand,
76
- payload: backendPayload,
77
- });
78
-
79
- export function createBackendExample(client: JsonHttpClient): ReturnType<typeof createEmseepea> {
80
- const searchCoffeeCatalog = defineMappedTool({
81
- name: "search-coffee-catalog",
82
- access: "public",
83
- description: "Search BrewMark's public coffee catalogue and explain its acidity and body ratings.",
84
- inputSchema: searchInput,
85
- outputSchema: searchReport,
86
- backendInputSchema: backendCommand,
87
- backendOutputSchema: backendResult,
88
- mapInput: ({ query, roast }) => ({
89
- pathname: "/api/coffees" as const,
90
- searchParams: {
91
- q: query,
92
- ...(roast ? { roastLevel: publicToBrewmarkRoast[roast] } : {}),
93
- sort: "alpha" as const,
94
- limit: "5" as const,
95
- },
96
- }),
97
- async adapter(request, { signal, deadlineMs }) {
98
- return {
99
- request,
100
- payload: await client.get({ ...request, signal, deadlineMs }),
101
- };
102
- },
103
- mapOutput: ({ request, payload }) => {
104
- const coffees = payload.data.map((record) => ({
105
- name: record.name,
106
- roaster: record.roasterName,
107
- origin: record.origin,
108
- roast: brewmarkToPublicRoast[record.roastLevel],
109
- processingMethod: record.processingMethod,
110
- flavourNotes: record.flavorProfile,
111
- acidityLevel: record.acidityLevel,
112
- bodyLevel: record.bodyLevel,
113
- }));
114
- const roastFilter = request.searchParams.roastLevel
115
- ? brewmarkToPublicRoast[request.searchParams.roastLevel]
116
- : null;
117
- const data = {
118
- query: request.searchParams.q,
119
- roastFilter,
120
- returnedCount: coffees.length,
121
- moreMatchesAvailable: payload.hasMore,
122
- ratingScale: {
123
- acidity: "1 = low acidity; 5 = high acidity" as const,
124
- body: "1 = light body; 5 = full body" as const,
125
- },
126
- coffees,
127
- source: "BrewMark" as const,
128
- sourceUrl: "https://brewmark.io" as const,
129
- };
130
- const lines = coffees.map((record) => [
131
- `${record.name} by ${record.roaster}`,
132
- `origin: ${record.origin ?? "not provided"}`,
133
- `roast: ${record.roast}`,
134
- `acidity: ${record.acidityLevel ?? "not provided"}`,
135
- `body: ${record.bodyLevel ?? "not provided"}`,
136
- ].join("; "));
137
- return {
138
- text: [
139
- `BrewMark returned ${coffees.length} coffee${coffees.length === 1 ? "" : "s"} for “${data.query}”.`,
140
- `More matches available: ${data.moreMatchesAvailable ? "yes" : "no"}.`,
141
- "Acidity: 1 = low acidity; 5 = high acidity.",
142
- "Body: 1 = light body; 5 = full body.",
143
- ...lines,
144
- "Source: https://brewmark.io",
145
- ].join("\n"),
146
- data,
147
- };
148
- },
149
- });
150
-
151
- return createEmseepea({
152
- name: "emseepea-backend-no-ui",
153
- version: "0.0.0",
154
- instructions: "Use search-coffee-catalog to search BrewMark's public coffee catalogue.",
155
- tools: [searchCoffeeCatalog],
156
- });
157
- }
File without changes
File without changes