@c4a/mcp-service 0.3.7-alpha.1
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/dist/index.js +191658 -0
- package/package.json +48 -0
- package/src/__e2e__/e2eUtils.ts +258 -0
- package/src/__e2e__/mcpFixtures.ts +244 -0
- package/src/__e2e__/us-bq1-batch-search.e2e.test.ts +191 -0
- package/src/__e2e__/us-bs1-batch-save.e2e.test.ts +230 -0
- package/src/__e2e__/us-bv1-block-validation.e2e.test.ts +233 -0
- package/src/__e2e__/us-d1-document-save.e2e.test.ts +249 -0
- package/src/__e2e__/us-r1-derives-direction.e2e.test.ts +213 -0
- package/src/__e2e__/us-r2-corresponds-sor-only.e2e.test.ts +153 -0
- package/src/__e2e__/us-rd1-refs-redirect.e2e.test.ts +293 -0
- package/src/__e2e__/us-rl1-release-semantics.e2e.test.ts +394 -0
- package/src/__e2e__/usC1-classification.e2e.test.ts +218 -0
- package/src/__e2e__/usD1-store-delete-cascade.e2e.test.ts +265 -0
- package/src/__e2e__/usT1-entity-types.e2e.test.ts +184 -0
- package/src/__e2e__/usV4-latest-pointer-protection.e2e.test.ts +148 -0
- package/src/__e2e__/usV5-first-use.e2e.test.ts +135 -0
- package/src/__e2e__/usv1-v3-version-guard.e2e.test.ts +833 -0
- package/src/__tests__/query-tools.test.ts +70 -0
- package/src/__tests__/smoke.test.ts +9 -0
- package/src/__tests__/store-tools.test.ts +182 -0
- package/src/index.ts +144 -0
- package/src/schemas/querySchemas.ts +61 -0
- package/src/schemas/storeSchemas.ts +237 -0
- package/src/server.ts +41 -0
- package/src/shared/handler.ts +42 -0
- package/src/tools/query.ts +65 -0
- package/src/tools/store.ts +158 -0
- package/src/tools/version.ts +83 -0
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@c4a/mcp-service",
|
|
3
|
+
"version": "0.3.7-alpha.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"c4a-mcp-service": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./src/index.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./server": {
|
|
16
|
+
"types": "./src/server.ts",
|
|
17
|
+
"default": "./dist/server.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"dev": "bun --watch src/index.ts",
|
|
30
|
+
"start": "bun run src/index.ts",
|
|
31
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun",
|
|
32
|
+
"test": "bun test",
|
|
33
|
+
"lint": "tsc --noEmit",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
38
|
+
"@c4a/core": "workspace:*",
|
|
39
|
+
"@c4a/storage": "workspace:*",
|
|
40
|
+
"@c4a/store": "workspace:*",
|
|
41
|
+
"@c4a/version": "workspace:*",
|
|
42
|
+
"@c4a/query": "workspace:*",
|
|
43
|
+
"zod": "^3.23.0",
|
|
44
|
+
"typescript": "^5.4.0",
|
|
45
|
+
"@types/bun": "latest",
|
|
46
|
+
"yaml": "^2.3.4"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { mkdir, mkdtemp, rm, symlink, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { createServer as createMcpServiceServer } from "../server.js";
|
|
8
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
9
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
10
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
12
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
13
|
+
import { resetAdapter, SQLiteStore } from "@c4a/storage";
|
|
14
|
+
|
|
15
|
+
const E2E_TMP_ROOT = join(process.cwd(), ".tmp", "e2e");
|
|
16
|
+
|
|
17
|
+
export type E2eDirOptions = {
|
|
18
|
+
outsideRepo?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function withCwd<T>(dir: string, fn: () => Promise<T>): Promise<T> {
|
|
22
|
+
const previousCwd = process.cwd();
|
|
23
|
+
process.chdir(dir);
|
|
24
|
+
try {
|
|
25
|
+
return await fn();
|
|
26
|
+
} finally {
|
|
27
|
+
process.chdir(previousCwd);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function withE2eDir<T>(
|
|
32
|
+
name: string,
|
|
33
|
+
fn: (dir: string) => Promise<T>,
|
|
34
|
+
options: E2eDirOptions = {},
|
|
35
|
+
): Promise<T> {
|
|
36
|
+
await mkdir(E2E_TMP_ROOT, { recursive: true });
|
|
37
|
+
const dirName = `${name}-${Date.now()}`;
|
|
38
|
+
if (!options.outsideRepo) {
|
|
39
|
+
const dir = join(E2E_TMP_ROOT, dirName);
|
|
40
|
+
await mkdir(dir, { recursive: true });
|
|
41
|
+
try {
|
|
42
|
+
return await fn(dir);
|
|
43
|
+
} finally {
|
|
44
|
+
await rm(dir, { recursive: true, force: true });
|
|
45
|
+
resetSQLiteStore();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const externalDir = await mkdtemp(join(tmpdir(), "c4a-e2e-"));
|
|
49
|
+
const linkPath = join(E2E_TMP_ROOT, dirName);
|
|
50
|
+
await symlink(externalDir, linkPath);
|
|
51
|
+
try {
|
|
52
|
+
return await fn(externalDir);
|
|
53
|
+
} finally {
|
|
54
|
+
await unlink(linkPath);
|
|
55
|
+
await rm(externalDir, { recursive: true, force: true });
|
|
56
|
+
resetSQLiteStore();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function createMcpClient(dir: string): Promise<{ client: Client; close: () => Promise<void> }> {
|
|
61
|
+
const mcpServicePath = fileURLToPath(new URL("../index.ts", import.meta.url));
|
|
62
|
+
const transport = new StdioClientTransport({
|
|
63
|
+
command: "bun",
|
|
64
|
+
args: [mcpServicePath],
|
|
65
|
+
cwd: dir,
|
|
66
|
+
env: {
|
|
67
|
+
...process.env,
|
|
68
|
+
C4A_HOME: dir,
|
|
69
|
+
MCP_TRANSPORT: "stdio",
|
|
70
|
+
},
|
|
71
|
+
stderr: "pipe",
|
|
72
|
+
});
|
|
73
|
+
const client = new Client({ name: "c4a-mcp-service-e2e", version: "0.0.0" });
|
|
74
|
+
await client.connect(transport);
|
|
75
|
+
return {
|
|
76
|
+
client,
|
|
77
|
+
close: async () => {
|
|
78
|
+
await client.close();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function withMcpServiceServer<T>(fn: (url: string) => Promise<T>): Promise<T> {
|
|
84
|
+
const previousBackendUrl = process.env.C4A_STORAGE_BACKEND_URL;
|
|
85
|
+
delete process.env.C4A_STORAGE_BACKEND_URL;
|
|
86
|
+
try {
|
|
87
|
+
const sessions: Map<
|
|
88
|
+
string,
|
|
89
|
+
{ transport: StreamableHTTPServerTransport; server: ReturnType<typeof createMcpServiceServer> }
|
|
90
|
+
> = new Map();
|
|
91
|
+
let lastSession:
|
|
92
|
+
| { transport: StreamableHTTPServerTransport; server: ReturnType<typeof createMcpServiceServer> }
|
|
93
|
+
| null = null;
|
|
94
|
+
|
|
95
|
+
const serverCwd = process.cwd();
|
|
96
|
+
const httpServer = createServer(async (req, res) => {
|
|
97
|
+
const previousCwd = process.cwd();
|
|
98
|
+
if (previousCwd !== serverCwd) {
|
|
99
|
+
process.chdir(serverCwd);
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const url = new URL(req.url || "/", "http://localhost");
|
|
103
|
+
|
|
104
|
+
if (url.pathname === "/health" && req.method === "GET") {
|
|
105
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
106
|
+
res.end(JSON.stringify({ status: "ok", service: "c4a-service" }));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (url.pathname === "/mcp") {
|
|
111
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
112
|
+
|
|
113
|
+
if (req.method === "POST") {
|
|
114
|
+
const chunks: Buffer[] = [];
|
|
115
|
+
for await (const chunk of req) {
|
|
116
|
+
chunks.push(chunk);
|
|
117
|
+
}
|
|
118
|
+
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
119
|
+
|
|
120
|
+
let session = sessionId ? sessions.get(sessionId) : undefined;
|
|
121
|
+
|
|
122
|
+
if (!session && (isInitializeRequest(body) || !sessionId)) {
|
|
123
|
+
const mcpServer = createMcpServiceServer();
|
|
124
|
+
const httpTransport = new StreamableHTTPServerTransport({
|
|
125
|
+
sessionIdGenerator: () => randomUUID(),
|
|
126
|
+
onsessioninitialized: (id) => {
|
|
127
|
+
sessions.set(id, { transport: httpTransport, server: mcpServer });
|
|
128
|
+
lastSession = { transport: httpTransport, server: mcpServer };
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
httpTransport.onclose = () => {
|
|
132
|
+
if (httpTransport.sessionId) {
|
|
133
|
+
sessions.delete(httpTransport.sessionId);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
await mcpServer.connect(httpTransport);
|
|
138
|
+
session = { transport: httpTransport, server: mcpServer };
|
|
139
|
+
lastSession = session;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!session && sessions.size === 1) {
|
|
143
|
+
session = Array.from(sessions.values())[0];
|
|
144
|
+
}
|
|
145
|
+
if (!session && lastSession) {
|
|
146
|
+
session = lastSession;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (session) {
|
|
150
|
+
await session.transport.handleRequest(req, res, body);
|
|
151
|
+
const activeSessionId = session.transport.sessionId;
|
|
152
|
+
if (activeSessionId && !sessions.has(activeSessionId)) {
|
|
153
|
+
sessions.set(activeSessionId, session);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
157
|
+
res.end(
|
|
158
|
+
JSON.stringify({
|
|
159
|
+
jsonrpc: "2.0",
|
|
160
|
+
error: { code: -32000, message: "Invalid session" },
|
|
161
|
+
id: null,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (req.method === "GET" || req.method === "DELETE") {
|
|
168
|
+
const session = sessionId ? sessions.get(sessionId) : undefined;
|
|
169
|
+
if (session) {
|
|
170
|
+
await session.transport.handleRequest(req, res);
|
|
171
|
+
} else {
|
|
172
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
173
|
+
res.end("Invalid session");
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
179
|
+
res.end("Method not allowed");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
184
|
+
res.end("Not found");
|
|
185
|
+
} finally {
|
|
186
|
+
if (process.cwd() !== previousCwd) {
|
|
187
|
+
process.chdir(previousCwd);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await new Promise<void>((resolve) => {
|
|
193
|
+
httpServer.listen(0, "127.0.0.1", () => resolve());
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const address = httpServer.address();
|
|
197
|
+
if (!address || typeof address === "string") {
|
|
198
|
+
throw new Error("无法获取 MCP 服务端口");
|
|
199
|
+
}
|
|
200
|
+
const baseUrl = `http://127.0.0.1:${address.port}`;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
return await fn(baseUrl);
|
|
204
|
+
} finally {
|
|
205
|
+
await new Promise<void>((resolve) => {
|
|
206
|
+
httpServer.close(() => resolve());
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
} finally {
|
|
210
|
+
if (previousBackendUrl === undefined) {
|
|
211
|
+
delete process.env.C4A_STORAGE_BACKEND_URL;
|
|
212
|
+
} else {
|
|
213
|
+
process.env.C4A_STORAGE_BACKEND_URL = previousBackendUrl;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function withMcpClient<T>(baseUrl: string, fn: (client: Client) => Promise<T>): Promise<T> {
|
|
219
|
+
const client = new Client({ name: "c4a-e2e-client", version: "0.1.0" });
|
|
220
|
+
const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`));
|
|
221
|
+
await client.connect(transport);
|
|
222
|
+
try {
|
|
223
|
+
return await fn(client);
|
|
224
|
+
} finally {
|
|
225
|
+
await transport.close();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function writeProjectConfig(dir: string, rootId: string): Promise<void> {
|
|
230
|
+
const contextDir = join(dir, ".context");
|
|
231
|
+
await mkdir(contextDir, { recursive: true });
|
|
232
|
+
const content = [
|
|
233
|
+
"mode: local",
|
|
234
|
+
"local:",
|
|
235
|
+
` defaultProject: ${rootId}`,
|
|
236
|
+
" enableVectorSearch: false",
|
|
237
|
+
"",
|
|
238
|
+
].join("\n");
|
|
239
|
+
await writeFile(join(contextDir, ".c4a.yaml"), content, "utf-8");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function parseToolResponse<T>(
|
|
243
|
+
payload: { content?: Array<{ type: string; text?: string }> }
|
|
244
|
+
): T {
|
|
245
|
+
const text = payload.content?.find((item) => item.type === "text")?.text ?? payload.content?.[0]?.text ?? "";
|
|
246
|
+
if (!text) {
|
|
247
|
+
return {} as T;
|
|
248
|
+
}
|
|
249
|
+
return JSON.parse(text) as T;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function resetSQLiteStore(): void {
|
|
253
|
+
resetAdapter();
|
|
254
|
+
const storeClass = SQLiteStore as unknown as { closeInstance?: () => void };
|
|
255
|
+
if (typeof storeClass.closeInstance === "function") {
|
|
256
|
+
storeClass.closeInstance();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { parseToolResponse } from "./e2eUtils.js";
|
|
2
|
+
|
|
3
|
+
export const ROOT_ID = "demo";
|
|
4
|
+
|
|
5
|
+
export type ToolResponse<T> = {
|
|
6
|
+
isError?: boolean;
|
|
7
|
+
data: T;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type E2eEntity = {
|
|
11
|
+
uuid?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
kind?: string;
|
|
15
|
+
scope?: string;
|
|
16
|
+
perspective?: string;
|
|
17
|
+
versions?: string[];
|
|
18
|
+
data?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type StoreSaveResult = {
|
|
22
|
+
success?: boolean;
|
|
23
|
+
id?: string;
|
|
24
|
+
entity?: E2eEntity;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type StoreReadResult = {
|
|
28
|
+
entity?: E2eEntity;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type StoreListResult = {
|
|
32
|
+
items?: Array<{ uuid?: string; id?: string; type?: string; versions?: string[] }>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type VersionPublishResult = {
|
|
36
|
+
success?: boolean;
|
|
37
|
+
version?: string;
|
|
38
|
+
affected_entities?: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type ErrorResponse = {
|
|
42
|
+
code?: string;
|
|
43
|
+
message?: string;
|
|
44
|
+
details?: Record<string, unknown>;
|
|
45
|
+
recoverable_actions?: Array<{ action: string; label?: string; params?: Record<string, unknown> }>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function buildSystemData(id: string, description: string): Record<string, unknown> {
|
|
49
|
+
return {
|
|
50
|
+
schema: "c4a/v1",
|
|
51
|
+
type: "software-system",
|
|
52
|
+
system: {
|
|
53
|
+
id,
|
|
54
|
+
name: id,
|
|
55
|
+
description,
|
|
56
|
+
scope: "project",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildContainerData(
|
|
62
|
+
id: string,
|
|
63
|
+
systemId: string,
|
|
64
|
+
description: string,
|
|
65
|
+
contractRef?: string,
|
|
66
|
+
): Record<string, unknown> {
|
|
67
|
+
return {
|
|
68
|
+
schema: "c4a/v1",
|
|
69
|
+
type: "container",
|
|
70
|
+
system_id: systemId,
|
|
71
|
+
container: {
|
|
72
|
+
id,
|
|
73
|
+
name: id,
|
|
74
|
+
description,
|
|
75
|
+
scope: "project",
|
|
76
|
+
system_id: systemId,
|
|
77
|
+
technology: [{ language: "ts" }],
|
|
78
|
+
apis: contractRef ? [{ type: "openapi", ref: contractRef }] : [],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildComponentData(
|
|
84
|
+
id: string,
|
|
85
|
+
containerId: string | undefined,
|
|
86
|
+
description: string,
|
|
87
|
+
contractRef?: string,
|
|
88
|
+
): Record<string, unknown> {
|
|
89
|
+
const base: Record<string, unknown> = {
|
|
90
|
+
schema: "c4a/v1",
|
|
91
|
+
type: "component",
|
|
92
|
+
component: {
|
|
93
|
+
id,
|
|
94
|
+
name: id,
|
|
95
|
+
description,
|
|
96
|
+
scope: "project",
|
|
97
|
+
implements_contracts: contractRef ? [contractRef] : [],
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
if (containerId) {
|
|
101
|
+
base.container_id = containerId;
|
|
102
|
+
(base.component as Record<string, unknown>).container_id = containerId;
|
|
103
|
+
}
|
|
104
|
+
return base;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function buildContractData(id: string, description: string): Record<string, unknown> {
|
|
108
|
+
return {
|
|
109
|
+
schema: "c4a/v1",
|
|
110
|
+
type: "contract",
|
|
111
|
+
contract: {
|
|
112
|
+
id,
|
|
113
|
+
name: id,
|
|
114
|
+
description,
|
|
115
|
+
contract_type: "openapi",
|
|
116
|
+
status: "implemented",
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type ToolClient = {
|
|
122
|
+
callTool: (input: { name: string; arguments: Record<string, unknown> }) => Promise<unknown>;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
type ToolCallResult = {
|
|
126
|
+
isError?: boolean;
|
|
127
|
+
content?: Array<{ type: string; text?: string }>;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
function coerceToolResult(payload: unknown): ToolCallResult {
|
|
131
|
+
return payload as ToolCallResult;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function callTool<T>(
|
|
135
|
+
client: ToolClient,
|
|
136
|
+
name: string,
|
|
137
|
+
args: Record<string, unknown>,
|
|
138
|
+
): Promise<ToolResponse<T>> {
|
|
139
|
+
const result = coerceToolResult(await client.callTool({ name, arguments: args }));
|
|
140
|
+
return { isError: result.isError, data: parseToolResponse<T>(result) };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function saveEntity(
|
|
144
|
+
client: ToolClient,
|
|
145
|
+
params: {
|
|
146
|
+
type: "system" | "container" | "component" | "contract";
|
|
147
|
+
id: string;
|
|
148
|
+
data: Record<string, unknown>;
|
|
149
|
+
version?: string;
|
|
150
|
+
rootId?: string;
|
|
151
|
+
},
|
|
152
|
+
): Promise<StoreSaveResult> {
|
|
153
|
+
const { data } = await callTool<StoreSaveResult>(client, "store_save", {
|
|
154
|
+
type: params.type,
|
|
155
|
+
id: params.id,
|
|
156
|
+
data: params.data,
|
|
157
|
+
root_id: params.rootId ?? ROOT_ID,
|
|
158
|
+
version: params.version,
|
|
159
|
+
});
|
|
160
|
+
return data;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function readEntity(
|
|
164
|
+
client: ToolClient,
|
|
165
|
+
params: { id: string; version?: string; rootId?: string },
|
|
166
|
+
): Promise<StoreReadResult> {
|
|
167
|
+
const { data } = await callTool<StoreReadResult>(client, "store_read", {
|
|
168
|
+
id: params.id,
|
|
169
|
+
root_id: params.rootId ?? ROOT_ID,
|
|
170
|
+
version: params.version,
|
|
171
|
+
format: "object",
|
|
172
|
+
});
|
|
173
|
+
return data;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function listEntities(
|
|
177
|
+
client: ToolClient,
|
|
178
|
+
params: { id?: string; version?: string; type?: string; rootId?: string },
|
|
179
|
+
): Promise<StoreListResult> {
|
|
180
|
+
const { data } = await callTool<StoreListResult>(client, "store_list", {
|
|
181
|
+
root_id: params.rootId ?? ROOT_ID,
|
|
182
|
+
id: params.id,
|
|
183
|
+
version: params.version,
|
|
184
|
+
type: params.type ?? "all",
|
|
185
|
+
});
|
|
186
|
+
return data;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function publishVersion(
|
|
190
|
+
client: ToolClient,
|
|
191
|
+
params: { version: string; publishAs?: string; rootId?: string },
|
|
192
|
+
): Promise<VersionPublishResult> {
|
|
193
|
+
const { data } = await callTool<VersionPublishResult>(client, "version_publish", {
|
|
194
|
+
root_id: params.rootId ?? ROOT_ID,
|
|
195
|
+
version: params.version,
|
|
196
|
+
publish_as: params.publishAs,
|
|
197
|
+
});
|
|
198
|
+
return data;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function addVersion(
|
|
202
|
+
client: ToolClient,
|
|
203
|
+
params: { uuid: string; version: string },
|
|
204
|
+
): Promise<void> {
|
|
205
|
+
await callTool(client, "version_add", {
|
|
206
|
+
uuid: params.uuid,
|
|
207
|
+
version: params.version,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function seedReleaseReadyData(
|
|
212
|
+
client: ToolClient,
|
|
213
|
+
version?: string,
|
|
214
|
+
rootId?: string,
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
await saveEntity(client, {
|
|
217
|
+
type: "system",
|
|
218
|
+
id: "sys",
|
|
219
|
+
data: buildSystemData("sys", "System desc"),
|
|
220
|
+
version,
|
|
221
|
+
rootId,
|
|
222
|
+
});
|
|
223
|
+
await saveEntity(client, {
|
|
224
|
+
type: "contract",
|
|
225
|
+
id: "contract-api",
|
|
226
|
+
data: buildContractData("contract-api", "Contract desc"),
|
|
227
|
+
version,
|
|
228
|
+
rootId,
|
|
229
|
+
});
|
|
230
|
+
await saveEntity(client, {
|
|
231
|
+
type: "container",
|
|
232
|
+
id: "svc",
|
|
233
|
+
data: buildContainerData("svc", "sys", "Container desc", "contract-api"),
|
|
234
|
+
version,
|
|
235
|
+
rootId,
|
|
236
|
+
});
|
|
237
|
+
await saveEntity(client, {
|
|
238
|
+
type: "component",
|
|
239
|
+
id: "cmp",
|
|
240
|
+
data: buildComponentData("cmp", "svc", "Component desc", "contract-api"),
|
|
241
|
+
version,
|
|
242
|
+
rootId,
|
|
243
|
+
});
|
|
244
|
+
}
|