@aventara/mcp 0.0.0-stage → 1.0.0-rc.2

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.
@@ -0,0 +1,210 @@
1
+ export const OPERATION_FAMILIES = [
2
+ "find",
3
+ "create",
4
+ "update",
5
+ "delete",
6
+ "upsert",
7
+ ];
8
+ export const TRANSACTION_TOOL = "transaction";
9
+ const VARIANT_ORDER = {
10
+ find: ["first", "unique", "many", "count"],
11
+ create: ["one", "many", "count"],
12
+ update: ["first", "unique", "many", "count"],
13
+ delete: ["first", "unique", "many", "count"],
14
+ upsert: ["unique"],
15
+ };
16
+ const VARIANT_ARGUMENTS = {
17
+ find: {
18
+ first: "where?, orderBy?, select? | include?",
19
+ unique: "where (an identifier), select? | include?",
20
+ many: "where?, orderBy?, limit?, offset?, cursor?, select? | include?",
21
+ count: "where?",
22
+ },
23
+ create: {
24
+ one: "data, select? | include?",
25
+ many: "data (a list), select? | include?",
26
+ count: "data (a list)",
27
+ },
28
+ update: {
29
+ first: "where?, orderBy?, data, select? | include?",
30
+ unique: "where (an identifier), data, select? | include?",
31
+ many: "where?, data, select? | include?",
32
+ count: "where?, data",
33
+ },
34
+ delete: {
35
+ first: "where?, orderBy?, select? | include?",
36
+ unique: "where (an identifier), select? | include?",
37
+ many: "where?, select? | include?",
38
+ count: "where?",
39
+ },
40
+ upsert: {
41
+ unique: "where (an identifier), create, update, select? | include?",
42
+ },
43
+ };
44
+ const FAMILY_SUMMARY = {
45
+ find: "Reads records of one resource.",
46
+ create: "Creates records in one resource.",
47
+ update: "Changes existing records of one resource.",
48
+ delete: "Deletes records of one resource.",
49
+ upsert: "Updates the record an identifier names in one resource, or creates it when there is none.",
50
+ };
51
+ const ANNOTATIONS = {
52
+ find: { readOnlyHint: true, openWorldHint: false },
53
+ create: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
54
+ update: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
55
+ delete: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
56
+ upsert: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
57
+ transaction: {
58
+ readOnlyHint: false,
59
+ destructiveHint: true,
60
+ openWorldHint: false,
61
+ },
62
+ };
63
+ export const CONTRACT_RESOURCE_URI = "aventara://contract";
64
+ const ENVELOPE_SCHEMA = {
65
+ type: "object",
66
+ properties: {
67
+ data: {
68
+ description: "The operation's result (for a transaction, each step's in order), in the protocol's wire forms.",
69
+ },
70
+ code: {
71
+ type: "string",
72
+ description: "The outcome code: A1xxx for a success.",
73
+ },
74
+ cause: {
75
+ description: "Why the call failed: a message, and the issues (path, code, check, params, message) to fix before retrying. null on success.",
76
+ },
77
+ },
78
+ required: ["code"],
79
+ };
80
+ const READ_THE_CONTRACT = `Read the resource ${CONTRACT_RESOURCE_URI} first: it lists each resource's fields, which of them can be filtered, selected, created and updated, and which variants each resource offers.`;
81
+ function offersOf(surface) {
82
+ const collected = new Map();
83
+ for (const route of surface) {
84
+ if (route.kind !== "operation") {
85
+ continue;
86
+ }
87
+ const family = route.family;
88
+ const byResource = collected.get(family) ?? new Map();
89
+ collected.set(family, byResource);
90
+ const variants = byResource.get(route.resource) ?? [];
91
+ byResource.set(route.resource, variants);
92
+ variants.push(route.variant);
93
+ }
94
+ return new Map([...collected].map(([family, byResource]) => [
95
+ family,
96
+ new Map([...byResource].map(([resource, variants]) => [
97
+ resource,
98
+ ordered(family, variants),
99
+ ])),
100
+ ]));
101
+ }
102
+ function ordered(family, variants) {
103
+ const present = new Set(variants);
104
+ return VARIANT_ORDER[family].filter((variant) => present.has(variant));
105
+ }
106
+ function familyTool(family, offer) {
107
+ const resources = [...offer.keys()];
108
+ const variants = ordered(family, [...offer.values()].flat());
109
+ const offered = [...offer]
110
+ .map(([resource, list]) => `${resource} (${list.join(", ")})`)
111
+ .join("; ");
112
+ const argumentsLine = variants
113
+ .map((variant) => `${family} ${variant}: ${VARIANT_ARGUMENTS[family][variant]}`)
114
+ .join(". ");
115
+ return {
116
+ name: family,
117
+ title: `${family[0]?.toUpperCase()}${family.slice(1)} records`,
118
+ description: [
119
+ FAMILY_SUMMARY[family],
120
+ READ_THE_CONTRACT,
121
+ `Offered: ${offered}.`,
122
+ `Arguments by variant: ${argumentsLine}.`,
123
+ "A failed call answers isError with the outcome code and the issues to fix; an issue's path starts at `arguments`, this call's args. Fix them and call again.",
124
+ ].join(" "),
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ resource: {
129
+ type: "string",
130
+ enum: resources,
131
+ description: "The resource, a key of the contract's resources.",
132
+ },
133
+ variant: {
134
+ type: "string",
135
+ enum: variants,
136
+ description: `The ${family} variant; each resource offers the variants listed in the description.`,
137
+ },
138
+ args: {
139
+ type: "object",
140
+ description: "The operation's arguments as the protocol defines them (where, orderBy, limit, offset, cursor, select, include, data, create, update), in the contract's field names. {} when there are none.",
141
+ },
142
+ },
143
+ required: ["resource", "variant"],
144
+ additionalProperties: false,
145
+ },
146
+ outputSchema: ENVELOPE_SCHEMA,
147
+ annotations: ANNOTATIONS[family],
148
+ };
149
+ }
150
+ function transactionTool(offers, maxTransactionOperations) {
151
+ const families = OPERATION_FAMILIES.filter((family) => offers.has(family));
152
+ const resources = [
153
+ ...new Set([...offers.values()].flatMap((offer) => [...offer.keys()])),
154
+ ];
155
+ const variants = [
156
+ ...new Set(families.flatMap((family) => ordered(family, [...(offers.get(family)?.values() ?? [])].flat()))),
157
+ ];
158
+ return {
159
+ name: TRANSACTION_TOOL,
160
+ title: "Run operations in one transaction",
161
+ description: [
162
+ "Runs a list of operations in one database transaction: every step commits, or none does.",
163
+ READ_THE_CONTRACT,
164
+ "Each step is { resource, family, variant, args }, with the args of that family's tool.",
165
+ 'A step may use a value an earlier step returned: put { "$ref": { "operation": <the earlier step\'s index>, "path": ["<a field that step selected>"] } } where the value goes. The server adds the fingerprints the protocol requires.',
166
+ `At most ${maxTransactionOperations} steps.`,
167
+ "A failed plan answers isError with the failing step's index (cause.operation), its code and its issues; an issue's path starts at operations[<step>].args.",
168
+ ].join(" "),
169
+ inputSchema: {
170
+ type: "object",
171
+ properties: {
172
+ steps: {
173
+ type: "array",
174
+ minItems: 1,
175
+ maxItems: maxTransactionOperations,
176
+ items: {
177
+ type: "object",
178
+ properties: {
179
+ resource: { type: "string", enum: resources },
180
+ family: { type: "string", enum: families },
181
+ variant: { type: "string", enum: variants },
182
+ args: { type: "object" },
183
+ },
184
+ required: ["resource", "family", "variant"],
185
+ additionalProperties: false,
186
+ },
187
+ },
188
+ },
189
+ required: ["steps"],
190
+ additionalProperties: false,
191
+ },
192
+ outputSchema: ENVELOPE_SCHEMA,
193
+ annotations: ANNOTATIONS.transaction,
194
+ };
195
+ }
196
+ export function compileToolCatalog(source) {
197
+ const offers = offersOf(source.surface);
198
+ const tools = [];
199
+ for (const family of OPERATION_FAMILIES) {
200
+ const offer = offers.get(family);
201
+ if (offer !== undefined) {
202
+ tools.push(familyTool(family, offer));
203
+ }
204
+ }
205
+ if (offers.size > 0 &&
206
+ source.surface.some((route) => route.kind === "transactions")) {
207
+ tools.push(transactionTool(offers, source.maxTransactionOperations));
208
+ }
209
+ return Object.freeze(tools);
210
+ }
@@ -0,0 +1,27 @@
1
+ import type { AvOperationFailureResponse } from "@aventara/core";
2
+ import type { AvProtocolResponse } from "@aventara/core/protocol";
3
+ /** A tool call's result, as `tools/call` answers it. */
4
+ export type McpToolResult = {
5
+ readonly content: readonly [{
6
+ readonly type: "text";
7
+ readonly text: string;
8
+ }];
9
+ readonly structuredContent?: Readonly<Record<string, unknown>>;
10
+ readonly isError: boolean;
11
+ };
12
+ /**
13
+ * The protocol's answer to a tool call as its MCP result: the envelope's JSON
14
+ * text as text content, the envelope itself as structured content, and
15
+ * `isError` exactly when the protocol answered a failure status. Nothing is
16
+ * read into the envelope or added to it: the code, the data in its wire forms
17
+ * and the cause are the protocol's.
18
+ */
19
+ export declare function toolResultOf(response: AvProtocolResponse): McpToolResult;
20
+ /** A tool call refused before it became a protocol request: an MCP tool error with its reason. */
21
+ export declare function refusedCallResult(message: string): McpToolResult;
22
+ /**
23
+ * The server's own failure inside a tool call as its MCP result: core's generic
24
+ * `A3000` envelope as a tool error, in the form `toolResultOf` gives the
25
+ * protocol's own `A3000`. Nothing of the thrown error is in it.
26
+ */
27
+ export declare function internalFailureResult(envelope: AvOperationFailureResponse): McpToolResult;
@@ -0,0 +1,29 @@
1
+ export function toolResultOf(response) {
2
+ const envelope = parsedObject(response.body);
3
+ return {
4
+ content: [{ type: "text", text: response.body }],
5
+ ...(envelope === undefined ? {} : { structuredContent: envelope }),
6
+ isError: response.status >= 400,
7
+ };
8
+ }
9
+ export function refusedCallResult(message) {
10
+ return { content: [{ type: "text", text: message }], isError: true };
11
+ }
12
+ export function internalFailureResult(envelope) {
13
+ return {
14
+ content: [{ type: "text", text: JSON.stringify(envelope) }],
15
+ structuredContent: envelope,
16
+ isError: true,
17
+ };
18
+ }
19
+ function parsedObject(text) {
20
+ try {
21
+ const value = JSON.parse(text);
22
+ return typeof value === "object" && value !== null && !Array.isArray(value)
23
+ ? value
24
+ : undefined;
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@aventara/mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0-rc.2",
4
+ "license": "SEE LICENSE IN LICENSE",
5
+ "description": "The MCP server for an Aventara application: its agent layer as Model Context Protocol tools.",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": "^22.18.0 || >=24.2.0"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "LICENSE-ADDITIONAL-PERMISSION.md",
22
+ "CHANGELOG.md"
23
+ ],
24
+ "dependencies": {
25
+ "@aventara/core": "1.0.0-rc.2",
26
+ "@modelcontextprotocol/sdk": "1.30.1",
27
+ "zod": "^4.6.5"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "devDependencies": {
33
+ "@aventara/testing": "1.0.0-rc.2"
34
+ },
35
+ "scripts": {
36
+ "build": "node ../../scripts/build/build-output.cleaner.ts dist && tsc -p tsconfig.json --removeComments --declaration false && tsc -p tsconfig.json --emitDeclarationOnly && node ../../scripts/build/declaration-comments.sanitizer.ts dist",
37
+ "typecheck": "tsc -p tsconfig.typecheck.json --noEmit",
38
+ "test": "vitest run --typecheck --config ../../vitest.config.mts --root .",
39
+ "lint": "biome lint src"
40
+ }
6
41
  }