@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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { StorageAdapter } from "@c4a/storage";
|
|
3
|
+
import type { QueryConfig } from "@c4a/query";
|
|
4
|
+
import { registerQueryTools } from "../tools/query.js";
|
|
5
|
+
|
|
6
|
+
type ToolHandler = (args: unknown) => Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }>;
|
|
7
|
+
|
|
8
|
+
function createMockAdapter(): StorageAdapter {
|
|
9
|
+
const adapter = {
|
|
10
|
+
async initialize() {
|
|
11
|
+
return;
|
|
12
|
+
},
|
|
13
|
+
async search() {
|
|
14
|
+
return {
|
|
15
|
+
items: [
|
|
16
|
+
{
|
|
17
|
+
id: "sys-1",
|
|
18
|
+
type: "system",
|
|
19
|
+
score: 0.9,
|
|
20
|
+
snippet: "System summary",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
total: 1,
|
|
24
|
+
has_more: false,
|
|
25
|
+
search_mode: "vector",
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
} as unknown as StorageAdapter;
|
|
29
|
+
|
|
30
|
+
return adapter;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createToolRegistry() {
|
|
34
|
+
const tools = new Map<string, ToolHandler>();
|
|
35
|
+
const server = {
|
|
36
|
+
tool(name: string, _description: string, _schema: unknown, handler: ToolHandler) {
|
|
37
|
+
tools.set(name, handler);
|
|
38
|
+
},
|
|
39
|
+
} as unknown as { tool: (name: string, description: string, schema: unknown, handler: ToolHandler) => void };
|
|
40
|
+
|
|
41
|
+
return { server, tools };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("mcp-service query tools", () => {
|
|
45
|
+
test("query_batch_search returns map result", async () => {
|
|
46
|
+
const adapter = createMockAdapter();
|
|
47
|
+
const { server, tools } = createToolRegistry();
|
|
48
|
+
const queryConfig: QueryConfig = {
|
|
49
|
+
defaultRootId: "demo-root",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
registerQueryTools(server as any, { adapterReady: Promise.resolve(adapter), queryConfig });
|
|
53
|
+
|
|
54
|
+
const handler = tools.get("query_batch_search");
|
|
55
|
+
expect(handler).toBeTruthy();
|
|
56
|
+
|
|
57
|
+
const response = await handler!({
|
|
58
|
+
queries: [
|
|
59
|
+
{ name: "order-service", type: "system" },
|
|
60
|
+
{ name: "order-service", type: "component" },
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(response.isError).toBeUndefined();
|
|
65
|
+
const payload = JSON.parse(response.content[0]?.text ?? "{}") as {
|
|
66
|
+
items?: Record<string, { items?: Array<{ id: string }> }>;
|
|
67
|
+
};
|
|
68
|
+
expect(payload.items?.["order-service"]?.items?.length).toBe(1);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { StorageAdapter, Entity } from "@c4a/storage";
|
|
3
|
+
import type { StoreConfig } from "@c4a/store";
|
|
4
|
+
import { registerStoreTools } from "../tools/store.js";
|
|
5
|
+
|
|
6
|
+
type ToolHandler = (args: unknown) => Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }>;
|
|
7
|
+
|
|
8
|
+
function createMockAdapter(): StorageAdapter {
|
|
9
|
+
let entity: Entity | null = null;
|
|
10
|
+
|
|
11
|
+
const adapter = {
|
|
12
|
+
async initialize() {
|
|
13
|
+
return;
|
|
14
|
+
},
|
|
15
|
+
async transaction<T>(fn: (tx: StorageAdapter) => Promise<T>): Promise<T> {
|
|
16
|
+
return fn(adapter as StorageAdapter);
|
|
17
|
+
},
|
|
18
|
+
async list() {
|
|
19
|
+
return [];
|
|
20
|
+
},
|
|
21
|
+
async readByUuid(uuid: string) {
|
|
22
|
+
if (entity && entity.uuid === uuid) return entity;
|
|
23
|
+
return null;
|
|
24
|
+
},
|
|
25
|
+
async read(rootId: string, id: string) {
|
|
26
|
+
if (entity && entity.root_id === rootId && entity.id === id) return entity;
|
|
27
|
+
return null;
|
|
28
|
+
},
|
|
29
|
+
async save(input: Entity) {
|
|
30
|
+
entity = {
|
|
31
|
+
...input,
|
|
32
|
+
uuid: input.uuid ?? "00000000-0000-0000-0000-000000000001",
|
|
33
|
+
versions: input.versions ?? ["0.0.0"],
|
|
34
|
+
};
|
|
35
|
+
return entity;
|
|
36
|
+
},
|
|
37
|
+
} as unknown as StorageAdapter;
|
|
38
|
+
|
|
39
|
+
return adapter;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createToolRegistry() {
|
|
43
|
+
const tools = new Map<string, ToolHandler>();
|
|
44
|
+
const server = {
|
|
45
|
+
tool(name: string, _description: string, _schema: unknown, handler: ToolHandler) {
|
|
46
|
+
tools.set(name, handler);
|
|
47
|
+
},
|
|
48
|
+
} as unknown as { tool: (name: string, description: string, schema: unknown, handler: ToolHandler) => void };
|
|
49
|
+
|
|
50
|
+
return { server, tools };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("mcp-service store tools", () => {
|
|
54
|
+
test("store_save accepts document entity", async () => {
|
|
55
|
+
const adapter = createMockAdapter();
|
|
56
|
+
const { server, tools } = createToolRegistry();
|
|
57
|
+
const storeConfig: StoreConfig = {
|
|
58
|
+
defaultRootId: "demo-root",
|
|
59
|
+
isRemote: false,
|
|
60
|
+
rootDir: process.cwd(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
registerStoreTools(server as any, { adapterReady: Promise.resolve(adapter), storeConfig });
|
|
64
|
+
|
|
65
|
+
const handler = tools.get("store_save");
|
|
66
|
+
expect(handler).toBeTruthy();
|
|
67
|
+
|
|
68
|
+
const response = await handler!({
|
|
69
|
+
type: "document",
|
|
70
|
+
data: {
|
|
71
|
+
id: "doc-1",
|
|
72
|
+
subtype: "prd",
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(response.isError).toBeUndefined();
|
|
77
|
+
const payload = JSON.parse(response.content[0]?.text ?? "{}") as { success?: boolean; entity?: Entity };
|
|
78
|
+
expect(payload.success).toBe(true);
|
|
79
|
+
expect(payload.entity?.type).toBe("document");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("store_read returns document entity", async () => {
|
|
83
|
+
const adapter = createMockAdapter();
|
|
84
|
+
const { server, tools } = createToolRegistry();
|
|
85
|
+
const storeConfig: StoreConfig = {
|
|
86
|
+
defaultRootId: "demo-root",
|
|
87
|
+
isRemote: false,
|
|
88
|
+
rootDir: process.cwd(),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
registerStoreTools(server as any, { adapterReady: Promise.resolve(adapter), storeConfig });
|
|
92
|
+
|
|
93
|
+
const saveHandler = tools.get("store_save");
|
|
94
|
+
const readHandler = tools.get("store_read");
|
|
95
|
+
expect(saveHandler).toBeTruthy();
|
|
96
|
+
expect(readHandler).toBeTruthy();
|
|
97
|
+
|
|
98
|
+
await saveHandler!({
|
|
99
|
+
type: "document",
|
|
100
|
+
data: {
|
|
101
|
+
id: "doc-2",
|
|
102
|
+
subtype: "prd",
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const response = await readHandler!({
|
|
107
|
+
id: "doc-2",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(response.isError).toBeUndefined();
|
|
111
|
+
const payload = JSON.parse(response.content[0]?.text ?? "{}") as { entity?: Entity };
|
|
112
|
+
expect(payload.entity?.type).toBe("document");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("store_batch_save returns results list", async () => {
|
|
116
|
+
const adapter = createMockAdapter();
|
|
117
|
+
const { server, tools } = createToolRegistry();
|
|
118
|
+
const storeConfig: StoreConfig = {
|
|
119
|
+
defaultRootId: "demo-root",
|
|
120
|
+
isRemote: false,
|
|
121
|
+
rootDir: process.cwd(),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
registerStoreTools(server as any, { adapterReady: Promise.resolve(adapter), storeConfig });
|
|
125
|
+
|
|
126
|
+
const handler = tools.get("store_batch_save");
|
|
127
|
+
expect(handler).toBeTruthy();
|
|
128
|
+
|
|
129
|
+
const response = await handler!({
|
|
130
|
+
entities: [
|
|
131
|
+
{
|
|
132
|
+
type: "system",
|
|
133
|
+
data: { id: "sys-1", name: "System" },
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: "component",
|
|
137
|
+
data: { id: "cmp-1", name: "Component" },
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(response.isError).toBeUndefined();
|
|
143
|
+
const payload = JSON.parse(response.content[0]?.text ?? "{}") as {
|
|
144
|
+
results?: Array<{ id: string; uuid: string; status: string }>;
|
|
145
|
+
failed?: Array<{ id?: string; error: string }>;
|
|
146
|
+
};
|
|
147
|
+
expect(payload.failed?.length ?? 0).toBe(0);
|
|
148
|
+
expect(payload.results?.length).toBe(2);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("store_batch_save returns failed entry on validation error", async () => {
|
|
152
|
+
const adapter = createMockAdapter();
|
|
153
|
+
const { server, tools } = createToolRegistry();
|
|
154
|
+
const storeConfig: StoreConfig = {
|
|
155
|
+
defaultRootId: "demo-root",
|
|
156
|
+
isRemote: false,
|
|
157
|
+
rootDir: process.cwd(),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
registerStoreTools(server as any, { adapterReady: Promise.resolve(adapter), storeConfig });
|
|
161
|
+
|
|
162
|
+
const handler = tools.get("store_batch_save");
|
|
163
|
+
expect(handler).toBeTruthy();
|
|
164
|
+
|
|
165
|
+
const response = await handler!({
|
|
166
|
+
entities: [
|
|
167
|
+
{
|
|
168
|
+
type: "process",
|
|
169
|
+
data: { id: "prc-1" },
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(response.isError).toBeUndefined();
|
|
175
|
+
const payload = JSON.parse(response.content[0]?.text ?? "{}") as {
|
|
176
|
+
results?: Array<{ id: string; uuid: string; status: string }>;
|
|
177
|
+
failed?: Array<{ id?: string; error: string }>;
|
|
178
|
+
};
|
|
179
|
+
expect(payload.results?.length ?? 0).toBe(0);
|
|
180
|
+
expect(payload.failed?.length ?? 0).toBe(1);
|
|
181
|
+
});
|
|
182
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @c4a/mcp-service - MCP 服务端
|
|
4
|
+
*
|
|
5
|
+
* 提供 store_* / query_* 工具
|
|
6
|
+
*
|
|
7
|
+
* 支持两种 transport 模式:
|
|
8
|
+
* - stdio (默认): 被 Agent 自动拉起
|
|
9
|
+
* - streamable-http: Docker 部署,暴露 HTTP 端口
|
|
10
|
+
*
|
|
11
|
+
* 环境变量:
|
|
12
|
+
* - MCP_TRANSPORT: stdio | streamable-http (默认 stdio)
|
|
13
|
+
* - MCP_PORT: HTTP 端口 (默认 8051)
|
|
14
|
+
*/
|
|
15
|
+
import { createServer } from "./server.js";
|
|
16
|
+
|
|
17
|
+
const transport = process.env.MCP_TRANSPORT || "stdio";
|
|
18
|
+
const port = parseInt(process.env.MCP_PORT || "8051", 10);
|
|
19
|
+
|
|
20
|
+
async function startStdioServer() {
|
|
21
|
+
const server = createServer();
|
|
22
|
+
const { StdioServerTransport } = await import(
|
|
23
|
+
"@modelcontextprotocol/sdk/server/stdio.js"
|
|
24
|
+
);
|
|
25
|
+
const stdioTransport = new StdioServerTransport();
|
|
26
|
+
await server.connect(stdioTransport);
|
|
27
|
+
console.error("c4a-mcp-service server started (stdio)");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function startHttpServer() {
|
|
31
|
+
const { createServer: createHttpServer } = await import("node:http");
|
|
32
|
+
const { StreamableHTTPServerTransport } = await import(
|
|
33
|
+
"@modelcontextprotocol/sdk/server/streamableHttp.js"
|
|
34
|
+
);
|
|
35
|
+
const { isInitializeRequest } = await import("@modelcontextprotocol/sdk/types.js");
|
|
36
|
+
const { randomUUID } = await import("node:crypto");
|
|
37
|
+
|
|
38
|
+
const sessions: Map<
|
|
39
|
+
string,
|
|
40
|
+
{
|
|
41
|
+
transport: InstanceType<typeof StreamableHTTPServerTransport>;
|
|
42
|
+
server: ReturnType<typeof createServer>;
|
|
43
|
+
}
|
|
44
|
+
> = new Map();
|
|
45
|
+
|
|
46
|
+
const httpServer = createHttpServer(async (req, res) => {
|
|
47
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
48
|
+
|
|
49
|
+
if (url.pathname === "/health" && req.method === "GET") {
|
|
50
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
51
|
+
res.end(JSON.stringify({ status: "ok", service: "c4a-mcp-service" }));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (url.pathname === "/mcp") {
|
|
56
|
+
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
57
|
+
|
|
58
|
+
if (req.method === "POST") {
|
|
59
|
+
const chunks: Buffer[] = [];
|
|
60
|
+
for await (const chunk of req) {
|
|
61
|
+
chunks.push(chunk);
|
|
62
|
+
}
|
|
63
|
+
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
64
|
+
|
|
65
|
+
let session = sessionId ? sessions.get(sessionId) : undefined;
|
|
66
|
+
|
|
67
|
+
if (!session && isInitializeRequest(body)) {
|
|
68
|
+
const mcpServer = createServer();
|
|
69
|
+
const httpTransport = new StreamableHTTPServerTransport({
|
|
70
|
+
sessionIdGenerator: () => randomUUID(),
|
|
71
|
+
onsessioninitialized: (id) => {
|
|
72
|
+
sessions.set(id, { transport: httpTransport, server: mcpServer });
|
|
73
|
+
console.log(`Session initialized: ${id}`);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
httpTransport.onclose = () => {
|
|
78
|
+
if (httpTransport.sessionId) {
|
|
79
|
+
sessions.delete(httpTransport.sessionId);
|
|
80
|
+
console.log(`Session closed: ${httpTransport.sessionId}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
await mcpServer.connect(httpTransport);
|
|
85
|
+
session = { transport: httpTransport, server: mcpServer };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (session) {
|
|
89
|
+
await session.transport.handleRequest(req, res, body);
|
|
90
|
+
} else {
|
|
91
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
92
|
+
res.end(
|
|
93
|
+
JSON.stringify({
|
|
94
|
+
jsonrpc: "2.0",
|
|
95
|
+
error: { code: -32000, message: "Invalid session" },
|
|
96
|
+
id: null,
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
} else if (req.method === "GET") {
|
|
101
|
+
const session = sessionId ? sessions.get(sessionId) : undefined;
|
|
102
|
+
if (session) {
|
|
103
|
+
await session.transport.handleRequest(req, res);
|
|
104
|
+
} else {
|
|
105
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
106
|
+
res.end("Invalid session");
|
|
107
|
+
}
|
|
108
|
+
} else if (req.method === "DELETE") {
|
|
109
|
+
const session = sessionId ? sessions.get(sessionId) : undefined;
|
|
110
|
+
if (session) {
|
|
111
|
+
await session.transport.handleRequest(req, res);
|
|
112
|
+
} else {
|
|
113
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
114
|
+
res.end("Invalid session");
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
118
|
+
res.end("Method not allowed");
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
124
|
+
res.end("Not found");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
httpServer.listen(port, "0.0.0.0", () => {
|
|
128
|
+
console.log(`c4a-mcp-service server started (http://0.0.0.0:${port}/mcp)`);
|
|
129
|
+
console.log(`Health check: http://0.0.0.0:${port}/health`);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function main() {
|
|
134
|
+
if (transport === "streamable-http") {
|
|
135
|
+
await startHttpServer();
|
|
136
|
+
} else {
|
|
137
|
+
await startStdioServer();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
main().catch((error) => {
|
|
142
|
+
console.error("Fatal error:", error);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const EntityTypeSchema = z.enum([
|
|
4
|
+
"system",
|
|
5
|
+
"container",
|
|
6
|
+
"component",
|
|
7
|
+
"contract",
|
|
8
|
+
"product",
|
|
9
|
+
"process",
|
|
10
|
+
"sor",
|
|
11
|
+
"document",
|
|
12
|
+
"feat",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export const QuerySearchScopeSchema = EntityTypeSchema.or(z.literal("all"));
|
|
16
|
+
|
|
17
|
+
export const QuerySearchInputSchema = z.object({
|
|
18
|
+
query: z.string().describe("搜索关键词"),
|
|
19
|
+
scope: QuerySearchScopeSchema.optional().describe("实体类型筛选(all 表示不限)"),
|
|
20
|
+
type_filter: QuerySearchScopeSchema.optional().describe("实体类型筛选(兼容字段)"),
|
|
21
|
+
root_id: z.string().optional().describe("包边界 root_id(可选)"),
|
|
22
|
+
versions: z.array(z.string()).optional().describe("版本过滤(可选,命中 versions 包含)"),
|
|
23
|
+
limit: z.number().optional().default(20).describe("返回结果数量上限"),
|
|
24
|
+
offset: z.number().optional().default(0).describe("分页偏移量"),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const QueryBatchSearchQuerySchema = z.object({
|
|
28
|
+
name: z.string().describe("实体名称"),
|
|
29
|
+
type: EntityTypeSchema.optional().describe("实体类型(可选)"),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const QueryBatchSearchInputSchema = z.object({
|
|
33
|
+
queries: z.array(QueryBatchSearchQuerySchema).describe("批量查询列表"),
|
|
34
|
+
root_id: z.string().optional().describe("包边界 root_id(可选)"),
|
|
35
|
+
versions: z.array(z.string()).optional().describe("版本过滤(可选,命中 versions 包含)"),
|
|
36
|
+
limit: z.number().optional().default(20).describe("返回结果数量上限"),
|
|
37
|
+
offset: z.number().optional().default(0).describe("分页偏移量"),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const QueryDepsDirectionSchema = z.enum(["upstream", "downstream", "both"]);
|
|
41
|
+
|
|
42
|
+
export const QueryDepsInputSchema = z.object({
|
|
43
|
+
uuid: z.string().optional().describe("实体 UUID(优先使用)"),
|
|
44
|
+
root_id: z.string().optional().describe("实体 root_id(与 id 搭配)"),
|
|
45
|
+
id: z.string().optional().describe("实体 ID(与 root_id 搭配)"),
|
|
46
|
+
direction: QueryDepsDirectionSchema.optional().describe("依赖方向"),
|
|
47
|
+
depth: z.number().optional().describe("依赖深度"),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const QueryImpactInputSchema = z.object({
|
|
51
|
+
uuid: z.string().optional().describe("实体 UUID(优先使用)"),
|
|
52
|
+
root_id: z.string().optional().describe("实体 root_id(与 id 搭配)"),
|
|
53
|
+
id: z.string().optional().describe("实体 ID(与 root_id 搭配)"),
|
|
54
|
+
change_type: z.enum(["upgrade", "deprecate", "remove"]).optional().describe("变更类型"),
|
|
55
|
+
depth: z.number().optional().describe("影响深度"),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export type QuerySearchInput = z.infer<typeof QuerySearchInputSchema>;
|
|
59
|
+
export type QueryBatchSearchInput = z.infer<typeof QueryBatchSearchInputSchema>;
|
|
60
|
+
export type QueryDepsInput = z.infer<typeof QueryDepsInputSchema>;
|
|
61
|
+
export type QueryImpactInput = z.infer<typeof QueryImpactInputSchema>;
|