@infimind-next/ai-hive-mcp 0.2.0 → 0.2.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/dist/api-client.d.ts +3 -0
- package/dist/api-client.js +7 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +29 -0
- package/dist/client-info.d.ts +9 -0
- package/dist/client-info.js +16 -0
- package/dist/schemas.d.ts +8 -8
- package/dist/server.d.ts +1 -1
- package/dist/server.js +3 -1
- package/dist/workbuddy-loopback.d.ts +5 -0
- package/dist/workbuddy-loopback.js +24 -8
- package/package.json +5 -2
package/dist/api-client.d.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { BearerTokenProvider } from "./auth-token-provider.js";
|
|
2
|
+
import { type McpClientInfo } from "./client-info.js";
|
|
2
3
|
type FetchImpl = typeof fetch;
|
|
3
4
|
type AiHiveApiClientInput = {
|
|
4
5
|
endpoint: string;
|
|
5
6
|
tokenProvider: BearerTokenProvider;
|
|
6
7
|
timeoutMs: number;
|
|
7
8
|
fetchImpl?: FetchImpl;
|
|
9
|
+
clientInfoProvider?: () => McpClientInfo | undefined;
|
|
8
10
|
};
|
|
9
11
|
export declare class AiHiveApiClient {
|
|
10
12
|
private readonly endpoint;
|
|
11
13
|
private readonly tokenProvider;
|
|
12
14
|
private readonly timeoutMs;
|
|
13
15
|
private readonly fetchImpl;
|
|
16
|
+
private readonly clientInfoProvider?;
|
|
14
17
|
constructor(input: AiHiveApiClientInput);
|
|
15
18
|
private withTimeout;
|
|
16
19
|
private request;
|
package/dist/api-client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeMcpClientInfo, MCP_CLIENT_INFO_HEADER, } from "./client-info.js";
|
|
1
2
|
function formatIssue(value) {
|
|
2
3
|
if (typeof value === "string") {
|
|
3
4
|
return value;
|
|
@@ -46,11 +47,13 @@ export class AiHiveApiClient {
|
|
|
46
47
|
tokenProvider;
|
|
47
48
|
timeoutMs;
|
|
48
49
|
fetchImpl;
|
|
50
|
+
clientInfoProvider;
|
|
49
51
|
constructor(input) {
|
|
50
52
|
this.endpoint = input.endpoint.replace(/\/+$/, "");
|
|
51
53
|
this.tokenProvider = input.tokenProvider;
|
|
52
54
|
this.timeoutMs = input.timeoutMs;
|
|
53
55
|
this.fetchImpl = input.fetchImpl ?? fetch;
|
|
56
|
+
this.clientInfoProvider = input.clientInfoProvider;
|
|
54
57
|
}
|
|
55
58
|
async withTimeout(operation) {
|
|
56
59
|
const controller = new AbortController();
|
|
@@ -99,11 +102,15 @@ export class AiHiveApiClient {
|
|
|
99
102
|
});
|
|
100
103
|
}
|
|
101
104
|
send(path, init, signal, token) {
|
|
105
|
+
const encodedClientInfo = encodeMcpClientInfo(this.clientInfoProvider?.());
|
|
102
106
|
return this.fetchImpl(`${this.endpoint}${path}`, {
|
|
103
107
|
...init,
|
|
104
108
|
signal,
|
|
105
109
|
headers: {
|
|
106
110
|
Authorization: `Bearer ${token}`,
|
|
111
|
+
...(encodedClientInfo
|
|
112
|
+
? { [MCP_CLIENT_INFO_HEADER]: encodedClientInfo }
|
|
113
|
+
: {}),
|
|
107
114
|
...(init.body ? { "Content-Type": "application/json" } : {}),
|
|
108
115
|
...init.headers,
|
|
109
116
|
},
|
package/dist/cli.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { McpClientConfig } from "./config.js";
|
|
|
2
2
|
import { runServer as runMcpServer } from "./server.js";
|
|
3
3
|
import { WorkBuddyCredentialStore } from "./workbuddy-credentials.js";
|
|
4
4
|
import { WorkBuddyOAuthClient } from "./workbuddy-oauth-client.js";
|
|
5
|
-
import { listenForWorkBuddyCallback } from "./workbuddy-loopback.js";
|
|
5
|
+
import { listenForWorkBuddyCallback, type WorkBuddyResultRedirects } from "./workbuddy-loopback.js";
|
|
6
6
|
export type CliDependencies = {
|
|
7
7
|
stdout(line: string): void;
|
|
8
8
|
stderr(line: string): void;
|
|
@@ -12,3 +12,4 @@ export type CliDependencies = {
|
|
|
12
12
|
runServer: typeof runMcpServer;
|
|
13
13
|
};
|
|
14
14
|
export declare function runCli(config: McpClientConfig, dependencies?: Partial<CliDependencies>): Promise<number>;
|
|
15
|
+
export declare function createWorkBuddyResultRedirects(webOrigin: string): WorkBuddyResultRedirects;
|
package/dist/cli.js
CHANGED
|
@@ -49,6 +49,14 @@ function resolveDependencies(config, overrides) {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
async function runAuth(config, dependencies) {
|
|
52
|
+
let resultRedirects;
|
|
53
|
+
try {
|
|
54
|
+
resultRedirects = createWorkBuddyResultRedirects(config.webOrigin);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
dependencies.stderr("连接 AI-HIVE 失败,请重试");
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
52
60
|
const store = dependencies.createStore(config.credentialsFile);
|
|
53
61
|
const oauth = dependencies.createOAuthClient(config.endpoint);
|
|
54
62
|
const { state, codeVerifier, codeChallenge } = createPkcePair();
|
|
@@ -58,6 +66,7 @@ async function runAuth(config, dependencies) {
|
|
|
58
66
|
listenerPromise = Promise.resolve(dependencies.listenForCallback({
|
|
59
67
|
expectedState: state,
|
|
60
68
|
timeoutMs: AUTH_CALLBACK_DEADLINE_MS,
|
|
69
|
+
resultRedirects,
|
|
61
70
|
onCode: async (code) => {
|
|
62
71
|
const payload = await oauth.exchangeAuthorizationCode({
|
|
63
72
|
code,
|
|
@@ -112,6 +121,26 @@ async function runAuth(config, dependencies) {
|
|
|
112
121
|
await listener.close().catch(() => undefined);
|
|
113
122
|
}
|
|
114
123
|
}
|
|
124
|
+
export function createWorkBuddyResultRedirects(webOrigin) {
|
|
125
|
+
const baseUrl = new URL(webOrigin);
|
|
126
|
+
if ((baseUrl.protocol !== "http:" && baseUrl.protocol !== "https:") ||
|
|
127
|
+
baseUrl.username !== "" ||
|
|
128
|
+
baseUrl.password !== "" ||
|
|
129
|
+
baseUrl.search !== "" ||
|
|
130
|
+
baseUrl.hash !== "") {
|
|
131
|
+
throw new Error("Web origin 无效");
|
|
132
|
+
}
|
|
133
|
+
const successUrl = new URL("/welcome?from=workbuddy-auth", baseUrl);
|
|
134
|
+
const failureUrl = new URL("/authorization-failed?from=workbuddy-auth&reason=callback-error", baseUrl);
|
|
135
|
+
if (successUrl.origin !== baseUrl.origin ||
|
|
136
|
+
failureUrl.origin !== baseUrl.origin) {
|
|
137
|
+
throw new Error("授权结果地址不同源");
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
successUrl: successUrl.href,
|
|
141
|
+
failureUrl: failureUrl.href,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
115
144
|
async function runStatus(config, dependencies) {
|
|
116
145
|
const store = dependencies.createStore(config.credentialsFile);
|
|
117
146
|
const deadlineAt = Date.now() + STATUS_DEADLINE_MS;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare const MCP_CLIENT_INFO_HEADER = "x-ai-hive-mcp-client-info";
|
|
3
|
+
export declare const MCP_CLIENT_INFO_HEADER_MAX_BYTES = 1024;
|
|
4
|
+
export type McpClientInfo = {
|
|
5
|
+
name: string;
|
|
6
|
+
version: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function encodeMcpClientInfo(input?: McpClientInfo): string | undefined;
|
|
9
|
+
export declare function readMcpClientInfo(server: McpServer): McpClientInfo | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const MCP_CLIENT_INFO_HEADER = "x-ai-hive-mcp-client-info";
|
|
2
|
+
export const MCP_CLIENT_INFO_HEADER_MAX_BYTES = 1024;
|
|
3
|
+
export function encodeMcpClientInfo(input) {
|
|
4
|
+
if (!input) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const encoded = Buffer.from(JSON.stringify({ name: input.name, version: input.version }), "utf8").toString("base64url");
|
|
8
|
+
return Buffer.byteLength(encoded, "ascii") <=
|
|
9
|
+
MCP_CLIENT_INFO_HEADER_MAX_BYTES
|
|
10
|
+
? encoded
|
|
11
|
+
: undefined;
|
|
12
|
+
}
|
|
13
|
+
export function readMcpClientInfo(server) {
|
|
14
|
+
const info = server.server.getClientVersion();
|
|
15
|
+
return info ? { name: info.name, version: info.version } : undefined;
|
|
16
|
+
}
|
package/dist/schemas.d.ts
CHANGED
|
@@ -10,34 +10,34 @@ export declare const mcpCreateMediaUploadOutputSchema: z.ZodObject<{
|
|
|
10
10
|
headers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
11
11
|
}, "strict", z.ZodTypeAny, {
|
|
12
12
|
method: "PUT";
|
|
13
|
-
headers: Record<string, string>;
|
|
14
13
|
url: string;
|
|
14
|
+
headers: Record<string, string>;
|
|
15
15
|
}, {
|
|
16
16
|
method: "PUT";
|
|
17
|
-
headers: Record<string, string>;
|
|
18
17
|
url: string;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
19
|
}>;
|
|
20
20
|
expiresAt: z.ZodString;
|
|
21
21
|
}, "strict", z.ZodTypeAny, {
|
|
22
|
+
mimeType: string;
|
|
22
23
|
mediaId: string;
|
|
23
24
|
mediaType: "IMAGE" | "VIDEO" | "DOCUMENT";
|
|
24
|
-
mimeType: string;
|
|
25
25
|
sizeBytes: number;
|
|
26
26
|
upload: {
|
|
27
27
|
method: "PUT";
|
|
28
|
-
headers: Record<string, string>;
|
|
29
28
|
url: string;
|
|
29
|
+
headers: Record<string, string>;
|
|
30
30
|
};
|
|
31
31
|
expiresAt: string;
|
|
32
32
|
}, {
|
|
33
|
+
mimeType: string;
|
|
33
34
|
mediaId: string;
|
|
34
35
|
mediaType: "IMAGE" | "VIDEO" | "DOCUMENT";
|
|
35
|
-
mimeType: string;
|
|
36
36
|
sizeBytes: number;
|
|
37
37
|
upload: {
|
|
38
38
|
method: "PUT";
|
|
39
|
-
headers: Record<string, string>;
|
|
40
39
|
url: string;
|
|
40
|
+
headers: Record<string, string>;
|
|
41
41
|
};
|
|
42
42
|
expiresAt: string;
|
|
43
43
|
}>;
|
|
@@ -65,12 +65,12 @@ export declare const chatTextShape: {
|
|
|
65
65
|
content: z.ZodEffects<z.ZodString, string, string>;
|
|
66
66
|
mediaIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
67
67
|
}, "strict", z.ZodTypeAny, {
|
|
68
|
-
role: "system" | "user" | "assistant";
|
|
69
68
|
content: string;
|
|
69
|
+
role: "user" | "assistant" | "system";
|
|
70
70
|
mediaIds: string[];
|
|
71
71
|
}, {
|
|
72
|
-
role: "system" | "user" | "assistant";
|
|
73
72
|
content: string;
|
|
73
|
+
role: "user" | "assistant" | "system";
|
|
74
74
|
mediaIds?: string[] | undefined;
|
|
75
75
|
}>, "many">;
|
|
76
76
|
thinkingEnabled: z.ZodOptional<z.ZodBoolean>;
|
package/dist/server.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { AiHiveApiClient } from "./api-client.js";
|
|
|
4
4
|
import { type BearerTokenProvider } from "./auth-token-provider.js";
|
|
5
5
|
import type { McpClientConfig } from "./config.js";
|
|
6
6
|
import { WorkBuddyCredentialStore } from "./workbuddy-credentials.js";
|
|
7
|
-
export declare const SERVER_VERSION = "0.2.
|
|
7
|
+
export declare const SERVER_VERSION = "0.2.3";
|
|
8
8
|
declare function content(value: unknown): {
|
|
9
9
|
content: {
|
|
10
10
|
type: "text";
|
package/dist/server.js
CHANGED
|
@@ -4,9 +4,10 @@ import { chatTextShape, generateImageShape, generateVideoShape, getGenerationTas
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { AiHiveApiClient } from "./api-client.js";
|
|
6
6
|
import { normalizeApiEndpoint, StaticBearerTokenProvider, WorkBuddyTokenProvider, } from "./auth-token-provider.js";
|
|
7
|
+
import { readMcpClientInfo } from "./client-info.js";
|
|
7
8
|
import { uploadMediaFromPath } from "./local-media.js";
|
|
8
9
|
import { WorkBuddyOAuthClient } from "./workbuddy-oauth-client.js";
|
|
9
|
-
export const SERVER_VERSION = "0.2.
|
|
10
|
+
export const SERVER_VERSION = "0.2.3";
|
|
10
11
|
const DEPRECATED_ATTACHMENT_IDS_MESSAGE = "attachmentIds 已废弃,请使用 messages[].mediaIds";
|
|
11
12
|
function content(value) {
|
|
12
13
|
return {
|
|
@@ -103,6 +104,7 @@ export function createServer(input) {
|
|
|
103
104
|
endpoint: input.endpoint,
|
|
104
105
|
tokenProvider: input.tokenProvider,
|
|
105
106
|
timeoutMs: input.timeoutMs,
|
|
107
|
+
clientInfoProvider: () => readMcpClientInfo(server),
|
|
106
108
|
});
|
|
107
109
|
for (const tool of createToolDefinitions(client)) {
|
|
108
110
|
server.tool(tool.name, tool.schema, tool.handler);
|
|
@@ -3,8 +3,13 @@ export type LoopbackListener = {
|
|
|
3
3
|
completion: Promise<void>;
|
|
4
4
|
close(): Promise<void>;
|
|
5
5
|
};
|
|
6
|
+
export type WorkBuddyResultRedirects = {
|
|
7
|
+
successUrl: string;
|
|
8
|
+
failureUrl: string;
|
|
9
|
+
};
|
|
6
10
|
export declare function listenForWorkBuddyCallback(_input: {
|
|
7
11
|
expectedState: string;
|
|
8
12
|
timeoutMs: number;
|
|
9
13
|
onCode(code: string): Promise<void>;
|
|
14
|
+
resultRedirects?: WorkBuddyResultRedirects;
|
|
10
15
|
}): Promise<LoopbackListener>;
|
|
@@ -102,7 +102,12 @@ export function listenForWorkBuddyCallback(_input) {
|
|
|
102
102
|
}
|
|
103
103
|
catch {
|
|
104
104
|
if (!settled) {
|
|
105
|
-
|
|
105
|
+
if (input.resultRedirects) {
|
|
106
|
+
await sendRedirectAndWait(response, input.resultRedirects.failureUrl);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
await sendHtmlAndWait(response, 500, failureHtml());
|
|
110
|
+
}
|
|
106
111
|
await finishFailure("WorkBuddy 授权失败,请重新连接");
|
|
107
112
|
}
|
|
108
113
|
return;
|
|
@@ -111,7 +116,12 @@ export function listenForWorkBuddyCallback(_input) {
|
|
|
111
116
|
response.destroy();
|
|
112
117
|
return;
|
|
113
118
|
}
|
|
114
|
-
|
|
119
|
+
if (input.resultRedirects) {
|
|
120
|
+
await sendRedirectAndWait(response, input.resultRedirects.successUrl);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
await sendHtmlAndWait(response, 200, successHtml());
|
|
124
|
+
}
|
|
115
125
|
await finishSuccess();
|
|
116
126
|
}
|
|
117
127
|
async function handleUnexpectedRequestFailure(response) {
|
|
@@ -179,6 +189,17 @@ function sendHtmlAndWait(response, status, body) {
|
|
|
179
189
|
response.end(body, resolve);
|
|
180
190
|
});
|
|
181
191
|
}
|
|
192
|
+
function sendRedirectAndWait(response, location) {
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
response.writeHead(302, {
|
|
195
|
+
"Cache-Control": "no-store",
|
|
196
|
+
Connection: "close",
|
|
197
|
+
Location: location,
|
|
198
|
+
"Referrer-Policy": "no-referrer",
|
|
199
|
+
});
|
|
200
|
+
response.end(resolve);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
182
203
|
function page(title, content) {
|
|
183
204
|
return `<!doctype html>
|
|
184
205
|
<html lang="zh-CN">
|
|
@@ -191,7 +212,6 @@ function page(title, content) {
|
|
|
191
212
|
body { margin: 0; min-height: 100vh; display: grid; place-items: center; font-family: sans-serif; background: #f8fafc; color: #0f172a; }
|
|
192
213
|
main { width: min(420px, calc(100% - 48px)); padding: 32px; border: 1px solid #e2e8f0; border-radius: 16px; background: white; box-shadow: 0 16px 40px rgb(15 23 42 / 8%); }
|
|
193
214
|
h1 { margin-top: 0; }
|
|
194
|
-
button { border: 0; border-radius: 8px; padding: 10px 16px; background: #2563eb; color: white; cursor: pointer; }
|
|
195
215
|
</style>
|
|
196
216
|
</head>
|
|
197
217
|
<body>
|
|
@@ -202,11 +222,7 @@ function page(title, content) {
|
|
|
202
222
|
function successHtml() {
|
|
203
223
|
return page("授权成功", `<h1>授权成功</h1>
|
|
204
224
|
<p>请返回 WorkBuddy 继续使用。</p>
|
|
205
|
-
<p
|
|
206
|
-
<button type="button" onclick="window.close()">关闭页面</button>
|
|
207
|
-
<script>
|
|
208
|
-
window.setTimeout(() => window.close(), 300);
|
|
209
|
-
</script>`);
|
|
225
|
+
<p>您可以手动关闭此页面。</p>`);
|
|
210
226
|
}
|
|
211
227
|
function failureHtml() {
|
|
212
228
|
return page("授权失败", `<h1>授权失败</h1>
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@infimind-next/ai-hive-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20.19.0"
|
|
8
|
+
},
|
|
6
9
|
"bin": {
|
|
7
10
|
"ai-hive-mcp": "dist/index.js"
|
|
8
11
|
},
|
|
@@ -18,7 +21,7 @@
|
|
|
18
21
|
},
|
|
19
22
|
"dependencies": {
|
|
20
23
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
21
|
-
"file-type": "^
|
|
24
|
+
"file-type": "^21.3.4",
|
|
22
25
|
"zod": "^3.24.0"
|
|
23
26
|
},
|
|
24
27
|
"devDependencies": {
|