@infimind-next/ai-hive-mcp 0.1.0
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/api-client.d.ts +32 -0
- package/dist/api-client.js +143 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +31 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -0
- package/dist/local-media.d.ts +3 -0
- package/dist/local-media.js +99 -0
- package/dist/schemas.d.ts +103 -0
- package/dist/schemas.js +78 -0
- package/dist/server.d.ts +27 -0
- package/dist/server.js +78 -0
- package/package.json +27 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type FetchImpl = typeof fetch;
|
|
2
|
+
export declare class AiHiveApiClient {
|
|
3
|
+
private readonly endpoint;
|
|
4
|
+
private readonly key;
|
|
5
|
+
private readonly timeoutMs;
|
|
6
|
+
private readonly fetchImpl;
|
|
7
|
+
constructor(input: {
|
|
8
|
+
endpoint: string;
|
|
9
|
+
key: string;
|
|
10
|
+
timeoutMs: number;
|
|
11
|
+
fetchImpl?: FetchImpl;
|
|
12
|
+
});
|
|
13
|
+
private withTimeout;
|
|
14
|
+
private request;
|
|
15
|
+
getUserInfo(): Promise<unknown>;
|
|
16
|
+
listModels(input: Record<string, unknown>): Promise<unknown>;
|
|
17
|
+
createMediaUpload(input: unknown): Promise<unknown>;
|
|
18
|
+
completeMediaUpload(mediaId: string): Promise<unknown>;
|
|
19
|
+
chatText(input: unknown): Promise<unknown>;
|
|
20
|
+
generateImage(input: unknown): Promise<unknown>;
|
|
21
|
+
generateVideo(input: unknown): Promise<unknown>;
|
|
22
|
+
getGenerationTask(input: {
|
|
23
|
+
taskId: string;
|
|
24
|
+
}): Promise<unknown>;
|
|
25
|
+
putUpload(input: {
|
|
26
|
+
url: string;
|
|
27
|
+
method: "PUT";
|
|
28
|
+
headers: Record<string, string>;
|
|
29
|
+
body: BodyInit;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
function formatIssue(value) {
|
|
2
|
+
if (typeof value === "string") {
|
|
3
|
+
return value;
|
|
4
|
+
}
|
|
5
|
+
if (typeof value === "object" && value !== null) {
|
|
6
|
+
const issue = value;
|
|
7
|
+
if (typeof issue.message === "string") {
|
|
8
|
+
const path = Array.isArray(issue.path)
|
|
9
|
+
? issue.path.map(String).join(".")
|
|
10
|
+
: "";
|
|
11
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
12
|
+
}
|
|
13
|
+
return JSON.stringify(value);
|
|
14
|
+
}
|
|
15
|
+
return String(value);
|
|
16
|
+
}
|
|
17
|
+
function errorMessage(body) {
|
|
18
|
+
if (typeof body !== "object" || body === null) {
|
|
19
|
+
return "请求失败";
|
|
20
|
+
}
|
|
21
|
+
const message = body.message;
|
|
22
|
+
if (typeof message === "string") {
|
|
23
|
+
return message;
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(message)) {
|
|
26
|
+
return message.map(formatIssue).join("; ");
|
|
27
|
+
}
|
|
28
|
+
if (typeof message === "object" && message !== null) {
|
|
29
|
+
return formatIssue(message);
|
|
30
|
+
}
|
|
31
|
+
return "请求失败";
|
|
32
|
+
}
|
|
33
|
+
function timeoutError() {
|
|
34
|
+
return new Error("请求超时,请稍后重试");
|
|
35
|
+
}
|
|
36
|
+
export class AiHiveApiClient {
|
|
37
|
+
endpoint;
|
|
38
|
+
key;
|
|
39
|
+
timeoutMs;
|
|
40
|
+
fetchImpl;
|
|
41
|
+
constructor(input) {
|
|
42
|
+
this.endpoint = input.endpoint.replace(/\/+$/, "");
|
|
43
|
+
this.key = input.key;
|
|
44
|
+
this.timeoutMs = input.timeoutMs;
|
|
45
|
+
this.fetchImpl = input.fetchImpl ?? fetch;
|
|
46
|
+
}
|
|
47
|
+
async withTimeout(operation) {
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
let timeout;
|
|
50
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
51
|
+
timeout = setTimeout(() => {
|
|
52
|
+
controller.abort();
|
|
53
|
+
reject(timeoutError());
|
|
54
|
+
}, this.timeoutMs);
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
return await Promise.race([operation(controller.signal), timeoutPromise]);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (controller.signal.aborted) {
|
|
61
|
+
throw timeoutError();
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
if (timeout) {
|
|
67
|
+
clearTimeout(timeout);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async request(path, init = {}) {
|
|
72
|
+
return this.withTimeout(async (signal) => {
|
|
73
|
+
const response = await this.fetchImpl(`${this.endpoint}${path}`, {
|
|
74
|
+
...init,
|
|
75
|
+
signal,
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${this.key}`,
|
|
78
|
+
...(init.body ? { "Content-Type": "application/json" } : {}),
|
|
79
|
+
...init.headers,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const body = await response.json().catch(() => ({}));
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new Error(errorMessage(body));
|
|
85
|
+
}
|
|
86
|
+
return body;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
getUserInfo() {
|
|
90
|
+
return this.request("/mcp/user-info");
|
|
91
|
+
}
|
|
92
|
+
listModels(input) {
|
|
93
|
+
const query = typeof input.modelType === "string"
|
|
94
|
+
? `?modelType=${encodeURIComponent(input.modelType)}`
|
|
95
|
+
: "";
|
|
96
|
+
return this.request(`/mcp/models${query}`);
|
|
97
|
+
}
|
|
98
|
+
createMediaUpload(input) {
|
|
99
|
+
return this.request("/mcp/media/upload-token", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
body: JSON.stringify(input),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
completeMediaUpload(mediaId) {
|
|
105
|
+
return this.request(`/mcp/media/${encodeURIComponent(mediaId)}/complete`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
chatText(input) {
|
|
110
|
+
return this.request("/mcp/chat/text", {
|
|
111
|
+
method: "POST",
|
|
112
|
+
body: JSON.stringify(input),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
generateImage(input) {
|
|
116
|
+
return this.request("/mcp/generation/image", {
|
|
117
|
+
method: "POST",
|
|
118
|
+
body: JSON.stringify(input),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
generateVideo(input) {
|
|
122
|
+
return this.request("/mcp/generation/video", {
|
|
123
|
+
method: "POST",
|
|
124
|
+
body: JSON.stringify(input),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
getGenerationTask(input) {
|
|
128
|
+
return this.request(`/mcp/generation/tasks/${encodeURIComponent(input.taskId)}`);
|
|
129
|
+
}
|
|
130
|
+
async putUpload(input) {
|
|
131
|
+
await this.withTimeout(async (signal) => {
|
|
132
|
+
const response = await this.fetchImpl(input.url, {
|
|
133
|
+
method: input.method,
|
|
134
|
+
headers: input.headers,
|
|
135
|
+
body: input.body,
|
|
136
|
+
signal,
|
|
137
|
+
});
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
throw new Error("文件上传失败,请稍后重试");
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const DEFAULT_ENDPOINT = "https://ai-hive.iclip.cn/api";
|
|
2
|
+
export function parseConfig(input) {
|
|
3
|
+
const args = input.argv.slice(2);
|
|
4
|
+
const values = new Map();
|
|
5
|
+
const flags = new Set();
|
|
6
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
7
|
+
const arg = args[index];
|
|
8
|
+
if (!arg?.startsWith("--")) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const name = arg.slice(2);
|
|
12
|
+
if (name === "verbose") {
|
|
13
|
+
flags.add(name);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const value = args[index + 1];
|
|
17
|
+
if (value && !value.startsWith("--")) {
|
|
18
|
+
values.set(name, value);
|
|
19
|
+
index += 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const timeoutMs = Number.parseInt(values.get("timeout-ms") ?? input.env.AI_HIVE_MCP_TIMEOUT_MS ?? "120000", 10);
|
|
23
|
+
return {
|
|
24
|
+
endpoint: values.get("endpoint") ??
|
|
25
|
+
input.env.AI_HIVE_API_ENDPOINT ??
|
|
26
|
+
DEFAULT_ENDPOINT,
|
|
27
|
+
key: values.get("key") ?? input.env.AI_HIVE_MCP_KEY ?? null,
|
|
28
|
+
timeoutMs: Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 120000,
|
|
29
|
+
verbose: flags.has("verbose") || input.env.AI_HIVE_MCP_VERBOSE === "true",
|
|
30
|
+
};
|
|
31
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseConfig } from "./config.js";
|
|
3
|
+
import { runServer } from "./server.js";
|
|
4
|
+
const config = parseConfig({ argv: process.argv, env: process.env });
|
|
5
|
+
runServer(config).catch((error) => {
|
|
6
|
+
const message = error instanceof Error ? error.message : "MCP 启动失败";
|
|
7
|
+
console.error(message);
|
|
8
|
+
process.exitCode = 1;
|
|
9
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { openAsBlob } from "node:fs";
|
|
2
|
+
import { open, stat } from "node:fs/promises";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { mcpCreateMediaUploadOutputSchema, mcpUploadMediaFromPathInputSchema, } from "./schemas.js";
|
|
5
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
6
|
+
const FILE_HEAD_BYTES = 4100;
|
|
7
|
+
const SMALL_MEDIA_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
8
|
+
const VIDEO_LIMIT_BYTES = 100 * 1024 * 1024;
|
|
9
|
+
const EXTENSION_MIME = new Map([
|
|
10
|
+
[".png", "image/png"],
|
|
11
|
+
[".jpg", "image/jpeg"],
|
|
12
|
+
[".jpeg", "image/jpeg"],
|
|
13
|
+
[".webp", "image/webp"],
|
|
14
|
+
[".mp4", "video/mp4"],
|
|
15
|
+
[".webm", "video/webm"],
|
|
16
|
+
[".mov", "video/quicktime"],
|
|
17
|
+
[".pdf", "application/pdf"],
|
|
18
|
+
[".txt", "text/plain"],
|
|
19
|
+
[".md", "text/markdown"],
|
|
20
|
+
[".csv", "text/csv"],
|
|
21
|
+
[".json", "application/json"],
|
|
22
|
+
[
|
|
23
|
+
".docx",
|
|
24
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
25
|
+
],
|
|
26
|
+
[
|
|
27
|
+
".xlsx",
|
|
28
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
29
|
+
],
|
|
30
|
+
]);
|
|
31
|
+
function extensionMime(filename) {
|
|
32
|
+
const lower = filename.toLowerCase();
|
|
33
|
+
for (const [extension, contentType] of EXTENSION_MIME) {
|
|
34
|
+
if (lower.endsWith(extension)) {
|
|
35
|
+
return contentType;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
function mediaSizeLimit(contentType) {
|
|
41
|
+
if (contentType.startsWith("video/")) {
|
|
42
|
+
return VIDEO_LIMIT_BYTES;
|
|
43
|
+
}
|
|
44
|
+
return SMALL_MEDIA_LIMIT_BYTES;
|
|
45
|
+
}
|
|
46
|
+
function assertFileSizeAllowed(sizeBytes, contentType) {
|
|
47
|
+
const limit = mediaSizeLimit(contentType);
|
|
48
|
+
if (sizeBytes > limit) {
|
|
49
|
+
throw new Error(`文件大小超过限制,${contentType.startsWith("video/") ? "视频" : "图片/文档"}最大 ${Math.floor(limit / 1024 / 1024)}MiB`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function readFileHead(path) {
|
|
53
|
+
const file = await open(path, "r").catch(() => {
|
|
54
|
+
throw new Error("文件读取失败,请检查权限");
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
const buffer = Buffer.alloc(FILE_HEAD_BYTES);
|
|
58
|
+
const { bytesRead } = await file.read(buffer, 0, FILE_HEAD_BYTES, 0);
|
|
59
|
+
return buffer.subarray(0, bytesRead);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await file.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function uploadMediaFromPath(api, rawInput) {
|
|
66
|
+
const input = mcpUploadMediaFromPathInputSchema.parse(rawInput);
|
|
67
|
+
const stats = await stat(input.path).catch(() => {
|
|
68
|
+
throw new Error("文件不存在,请检查路径");
|
|
69
|
+
});
|
|
70
|
+
if (!stats.isFile()) {
|
|
71
|
+
throw new Error("当前路径不是可上传文件");
|
|
72
|
+
}
|
|
73
|
+
const filename = input.filename ?? basename(input.path);
|
|
74
|
+
const extensionContentType = extensionMime(filename);
|
|
75
|
+
const preliminaryContentType = input.contentType ?? extensionContentType;
|
|
76
|
+
if (preliminaryContentType) {
|
|
77
|
+
assertFileSizeAllowed(stats.size, preliminaryContentType);
|
|
78
|
+
}
|
|
79
|
+
const head = await readFileHead(input.path);
|
|
80
|
+
const detected = await fileTypeFromBuffer(head);
|
|
81
|
+
const contentType = input.contentType ?? detected?.mime ?? extensionContentType;
|
|
82
|
+
if (!contentType) {
|
|
83
|
+
throw new Error("暂不支持该文件类型");
|
|
84
|
+
}
|
|
85
|
+
assertFileSizeAllowed(stats.size, contentType);
|
|
86
|
+
const upload = mcpCreateMediaUploadOutputSchema.parse(await api.createMediaUpload({
|
|
87
|
+
filename,
|
|
88
|
+
contentType,
|
|
89
|
+
sizeBytes: stats.size,
|
|
90
|
+
}));
|
|
91
|
+
const body = await openAsBlob(input.path, { type: contentType });
|
|
92
|
+
await api.putUpload({
|
|
93
|
+
url: upload.upload.url,
|
|
94
|
+
method: upload.upload.method,
|
|
95
|
+
headers: upload.upload.headers,
|
|
96
|
+
body,
|
|
97
|
+
});
|
|
98
|
+
return api.completeMediaUpload(upload.mediaId);
|
|
99
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const mcpCreateMediaUploadOutputSchema: z.ZodObject<{
|
|
3
|
+
mediaId: z.ZodString;
|
|
4
|
+
mediaType: z.ZodEnum<["IMAGE", "VIDEO", "DOCUMENT"]>;
|
|
5
|
+
mimeType: z.ZodEffects<z.ZodString, string, string>;
|
|
6
|
+
sizeBytes: z.ZodNumber;
|
|
7
|
+
upload: z.ZodObject<{
|
|
8
|
+
url: z.ZodString;
|
|
9
|
+
method: z.ZodLiteral<"PUT">;
|
|
10
|
+
headers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
11
|
+
}, "strict", z.ZodTypeAny, {
|
|
12
|
+
method: "PUT";
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
url: string;
|
|
15
|
+
}, {
|
|
16
|
+
method: "PUT";
|
|
17
|
+
headers: Record<string, string>;
|
|
18
|
+
url: string;
|
|
19
|
+
}>;
|
|
20
|
+
expiresAt: z.ZodString;
|
|
21
|
+
}, "strict", z.ZodTypeAny, {
|
|
22
|
+
mediaId: string;
|
|
23
|
+
mediaType: "IMAGE" | "VIDEO" | "DOCUMENT";
|
|
24
|
+
mimeType: string;
|
|
25
|
+
sizeBytes: number;
|
|
26
|
+
upload: {
|
|
27
|
+
method: "PUT";
|
|
28
|
+
headers: Record<string, string>;
|
|
29
|
+
url: string;
|
|
30
|
+
};
|
|
31
|
+
expiresAt: string;
|
|
32
|
+
}, {
|
|
33
|
+
mediaId: string;
|
|
34
|
+
mediaType: "IMAGE" | "VIDEO" | "DOCUMENT";
|
|
35
|
+
mimeType: string;
|
|
36
|
+
sizeBytes: number;
|
|
37
|
+
upload: {
|
|
38
|
+
method: "PUT";
|
|
39
|
+
headers: Record<string, string>;
|
|
40
|
+
url: string;
|
|
41
|
+
};
|
|
42
|
+
expiresAt: string;
|
|
43
|
+
}>;
|
|
44
|
+
export declare const mcpUploadMediaFromPathInputSchema: z.ZodObject<{
|
|
45
|
+
path: z.ZodEffects<z.ZodString, string, string>;
|
|
46
|
+
filename: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
47
|
+
contentType: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
48
|
+
}, "strict", z.ZodTypeAny, {
|
|
49
|
+
path: string;
|
|
50
|
+
filename?: string | undefined;
|
|
51
|
+
contentType?: string | undefined;
|
|
52
|
+
}, {
|
|
53
|
+
path: string;
|
|
54
|
+
filename?: string | undefined;
|
|
55
|
+
contentType?: string | undefined;
|
|
56
|
+
}>;
|
|
57
|
+
export declare const listModelsShape: {
|
|
58
|
+
modelType: z.ZodOptional<z.ZodEnum<["TEXT", "IMAGE", "VIDEO"]>>;
|
|
59
|
+
};
|
|
60
|
+
export declare const chatTextShape: {
|
|
61
|
+
publicModelId: z.ZodString;
|
|
62
|
+
routingMode: z.ZodEnum<["COST_FIRST", "SPEED_FIRST", "SUCCESS_FIRST"]>;
|
|
63
|
+
messages: z.ZodArray<z.ZodObject<{
|
|
64
|
+
role: z.ZodEnum<["system", "user", "assistant"]>;
|
|
65
|
+
content: z.ZodEffects<z.ZodString, string, string>;
|
|
66
|
+
mediaIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
67
|
+
}, "strict", z.ZodTypeAny, {
|
|
68
|
+
role: "system" | "user" | "assistant";
|
|
69
|
+
content: string;
|
|
70
|
+
mediaIds: string[];
|
|
71
|
+
}, {
|
|
72
|
+
role: "system" | "user" | "assistant";
|
|
73
|
+
content: string;
|
|
74
|
+
mediaIds?: string[] | undefined;
|
|
75
|
+
}>, "many">;
|
|
76
|
+
thinkingEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
77
|
+
attachmentIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
78
|
+
pricingSnapshot: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
79
|
+
};
|
|
80
|
+
export declare const generateImageShape: {
|
|
81
|
+
publicModelId: z.ZodString;
|
|
82
|
+
routingMode: z.ZodEnum<["COST_FIRST", "SPEED_FIRST", "SUCCESS_FIRST"]>;
|
|
83
|
+
prompt: z.ZodEffects<z.ZodString, string, string>;
|
|
84
|
+
batchSize: z.ZodDefault<z.ZodNumber>;
|
|
85
|
+
imageMediaIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
86
|
+
params: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
87
|
+
pricingSnapshot: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
88
|
+
};
|
|
89
|
+
export declare const generateVideoShape: {
|
|
90
|
+
publicModelId: z.ZodString;
|
|
91
|
+
routingMode: z.ZodEnum<["COST_FIRST", "SPEED_FIRST", "SUCCESS_FIRST"]>;
|
|
92
|
+
prompt: z.ZodEffects<z.ZodString, string, string>;
|
|
93
|
+
imageMediaIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
94
|
+
videoMediaIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
95
|
+
firstFrameMediaId: z.ZodOptional<z.ZodString>;
|
|
96
|
+
lastFrameMediaId: z.ZodOptional<z.ZodString>;
|
|
97
|
+
params: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
98
|
+
pricingSnapshot: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
99
|
+
};
|
|
100
|
+
export declare const getGenerationTaskShape: {
|
|
101
|
+
taskId: z.ZodString;
|
|
102
|
+
};
|
|
103
|
+
export type McpUploadMediaFromPathInput = z.infer<typeof mcpUploadMediaFromPathInputSchema>;
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const nonBlankStringSchema = z
|
|
3
|
+
.string()
|
|
4
|
+
.refine((value) => value.trim().length > 0, {
|
|
5
|
+
message: "Expected non-blank string",
|
|
6
|
+
});
|
|
7
|
+
const mediaIdSchema = z.string().min(1);
|
|
8
|
+
const routingModeSchema = z.enum([
|
|
9
|
+
"COST_FIRST",
|
|
10
|
+
"SPEED_FIRST",
|
|
11
|
+
"SUCCESS_FIRST",
|
|
12
|
+
]);
|
|
13
|
+
const pricingSnapshotSchema = z.record(z.unknown());
|
|
14
|
+
const paramsSchema = z.record(z.unknown()).default({});
|
|
15
|
+
export const mcpCreateMediaUploadOutputSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
mediaId: mediaIdSchema,
|
|
18
|
+
mediaType: z.enum(["IMAGE", "VIDEO", "DOCUMENT"]),
|
|
19
|
+
mimeType: nonBlankStringSchema,
|
|
20
|
+
sizeBytes: z.number().int().positive(),
|
|
21
|
+
upload: z
|
|
22
|
+
.object({
|
|
23
|
+
url: z.string().url(),
|
|
24
|
+
method: z.literal("PUT"),
|
|
25
|
+
headers: z.record(z.string().min(1), z.string().min(1)),
|
|
26
|
+
})
|
|
27
|
+
.strict(),
|
|
28
|
+
expiresAt: z.string().datetime(),
|
|
29
|
+
})
|
|
30
|
+
.strict();
|
|
31
|
+
export const mcpUploadMediaFromPathInputSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
path: nonBlankStringSchema,
|
|
34
|
+
filename: nonBlankStringSchema.optional(),
|
|
35
|
+
contentType: nonBlankStringSchema.optional(),
|
|
36
|
+
})
|
|
37
|
+
.strict();
|
|
38
|
+
const messageSchema = z
|
|
39
|
+
.object({
|
|
40
|
+
role: z.enum(["system", "user", "assistant"]),
|
|
41
|
+
content: nonBlankStringSchema,
|
|
42
|
+
mediaIds: z.array(mediaIdSchema).default([]),
|
|
43
|
+
})
|
|
44
|
+
.strict();
|
|
45
|
+
export const listModelsShape = {
|
|
46
|
+
modelType: z.enum(["TEXT", "IMAGE", "VIDEO"]).optional(),
|
|
47
|
+
};
|
|
48
|
+
export const chatTextShape = {
|
|
49
|
+
publicModelId: z.string().min(1),
|
|
50
|
+
routingMode: routingModeSchema,
|
|
51
|
+
messages: z.array(messageSchema).min(1),
|
|
52
|
+
thinkingEnabled: z.boolean().optional(),
|
|
53
|
+
attachmentIds: z.array(z.string().min(1)).default([]),
|
|
54
|
+
pricingSnapshot: pricingSnapshotSchema,
|
|
55
|
+
};
|
|
56
|
+
export const generateImageShape = {
|
|
57
|
+
publicModelId: z.string().min(1),
|
|
58
|
+
routingMode: routingModeSchema,
|
|
59
|
+
prompt: nonBlankStringSchema,
|
|
60
|
+
batchSize: z.number().int().min(1).max(10).default(1),
|
|
61
|
+
imageMediaIds: z.array(mediaIdSchema).default([]),
|
|
62
|
+
params: paramsSchema,
|
|
63
|
+
pricingSnapshot: pricingSnapshotSchema,
|
|
64
|
+
};
|
|
65
|
+
export const generateVideoShape = {
|
|
66
|
+
publicModelId: z.string().min(1),
|
|
67
|
+
routingMode: routingModeSchema,
|
|
68
|
+
prompt: nonBlankStringSchema,
|
|
69
|
+
imageMediaIds: z.array(mediaIdSchema).default([]),
|
|
70
|
+
videoMediaIds: z.array(mediaIdSchema).default([]),
|
|
71
|
+
firstFrameMediaId: mediaIdSchema.optional(),
|
|
72
|
+
lastFrameMediaId: mediaIdSchema.optional(),
|
|
73
|
+
params: paramsSchema,
|
|
74
|
+
pricingSnapshot: pricingSnapshotSchema,
|
|
75
|
+
};
|
|
76
|
+
export const getGenerationTaskShape = {
|
|
77
|
+
taskId: z.string().min(1),
|
|
78
|
+
};
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { AiHiveApiClient } from "./api-client.js";
|
|
4
|
+
declare function content(value: unknown): {
|
|
5
|
+
content: {
|
|
6
|
+
type: "text";
|
|
7
|
+
text: string;
|
|
8
|
+
}[];
|
|
9
|
+
};
|
|
10
|
+
type ApiClientLike = Pick<AiHiveApiClient, "getUserInfo" | "listModels" | "createMediaUpload" | "completeMediaUpload" | "chatText" | "generateImage" | "generateVideo" | "getGenerationTask" | "putUpload">;
|
|
11
|
+
export type ToolDefinition = {
|
|
12
|
+
name: string;
|
|
13
|
+
schema: z.ZodRawShape;
|
|
14
|
+
handler: (args: Record<string, unknown>) => Promise<ReturnType<typeof content>>;
|
|
15
|
+
};
|
|
16
|
+
export declare function createToolDefinitions(client: () => ApiClientLike): ToolDefinition[];
|
|
17
|
+
export declare function createServer(input: {
|
|
18
|
+
endpoint: string;
|
|
19
|
+
key: string | null;
|
|
20
|
+
timeoutMs: number;
|
|
21
|
+
}): McpServer;
|
|
22
|
+
export declare function runServer(input: {
|
|
23
|
+
endpoint: string;
|
|
24
|
+
key: string | null;
|
|
25
|
+
timeoutMs: number;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
export {};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { chatTextShape, generateImageShape, generateVideoShape, getGenerationTaskShape, listModelsShape, } from "./schemas.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { AiHiveApiClient } from "./api-client.js";
|
|
6
|
+
import { uploadMediaFromPath } from "./local-media.js";
|
|
7
|
+
function requireKey(key) {
|
|
8
|
+
if (!key) {
|
|
9
|
+
throw new Error("MCP API Key 未配置,请在配置中传入 --key 或 AI_HIVE_MCP_KEY");
|
|
10
|
+
}
|
|
11
|
+
return key;
|
|
12
|
+
}
|
|
13
|
+
function content(value) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function createToolDefinitions(client) {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
name: "get_user_info",
|
|
22
|
+
schema: {},
|
|
23
|
+
handler: async () => content(await client().getUserInfo()),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "list_models",
|
|
27
|
+
schema: listModelsShape,
|
|
28
|
+
handler: async (args) => content(await client().listModels(args)),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "upload_media_from_path",
|
|
32
|
+
schema: {
|
|
33
|
+
path: z.string().min(1),
|
|
34
|
+
filename: z.string().min(1).optional(),
|
|
35
|
+
contentType: z.string().min(1).optional(),
|
|
36
|
+
},
|
|
37
|
+
handler: async (args) => content(await uploadMediaFromPath(client(), args)),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "chat_text",
|
|
41
|
+
schema: chatTextShape,
|
|
42
|
+
handler: async (args) => content(await client().chatText(args)),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "generate_image",
|
|
46
|
+
schema: generateImageShape,
|
|
47
|
+
handler: async (args) => content(await client().generateImage(args)),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "generate_video",
|
|
51
|
+
schema: generateVideoShape,
|
|
52
|
+
handler: async (args) => content(await client().generateVideo(args)),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "get_generation_task",
|
|
56
|
+
schema: getGenerationTaskShape,
|
|
57
|
+
handler: async (args) => content(await client().getGenerationTask({
|
|
58
|
+
taskId: String(args.taskId),
|
|
59
|
+
})),
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
export function createServer(input) {
|
|
64
|
+
const server = new McpServer({ name: "ai-hive", version: "0.1.0" });
|
|
65
|
+
const client = () => new AiHiveApiClient({
|
|
66
|
+
endpoint: input.endpoint,
|
|
67
|
+
key: requireKey(input.key),
|
|
68
|
+
timeoutMs: input.timeoutMs,
|
|
69
|
+
});
|
|
70
|
+
for (const tool of createToolDefinitions(client)) {
|
|
71
|
+
server.tool(tool.name, tool.schema, tool.handler);
|
|
72
|
+
}
|
|
73
|
+
return server;
|
|
74
|
+
}
|
|
75
|
+
export async function runServer(input) {
|
|
76
|
+
const server = createServer(input);
|
|
77
|
+
await server.connect(new StdioServerTransport());
|
|
78
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@infimind-next/ai-hive-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-hive-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc -p tsconfig.json",
|
|
14
|
+
"lint": "eslint \"src/**/*.ts\"",
|
|
15
|
+
"prepack": "pnpm run build",
|
|
16
|
+
"test": "vitest run"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
20
|
+
"file-type": "^22.0.1",
|
|
21
|
+
"zod": "^3.24.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.0.0",
|
|
25
|
+
"vitest": "^3.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|