@lobu/gateway 3.0.9 → 3.0.12
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/platform.d.ts.map +1 -1
- package/dist/api/platform.js +7 -26
- package/dist/api/platform.js.map +1 -1
- package/dist/auth/mcp/proxy.d.ts +14 -0
- package/dist/auth/mcp/proxy.d.ts.map +1 -1
- package/dist/auth/mcp/proxy.js +149 -13
- package/dist/auth/mcp/proxy.js.map +1 -1
- package/dist/cli/gateway.d.ts.map +1 -1
- package/dist/cli/gateway.js +29 -0
- package/dist/cli/gateway.js.map +1 -1
- package/dist/connections/chat-instance-manager.d.ts.map +1 -1
- package/dist/connections/chat-instance-manager.js +2 -1
- package/dist/connections/chat-instance-manager.js.map +1 -1
- package/dist/connections/interaction-bridge.d.ts +9 -2
- package/dist/connections/interaction-bridge.d.ts.map +1 -1
- package/dist/connections/interaction-bridge.js +121 -261
- package/dist/connections/interaction-bridge.js.map +1 -1
- package/dist/interactions.d.ts +9 -43
- package/dist/interactions.d.ts.map +1 -1
- package/dist/interactions.js +10 -52
- package/dist/interactions.js.map +1 -1
- package/dist/routes/public/agent.d.ts +4 -0
- package/dist/routes/public/agent.d.ts.map +1 -1
- package/dist/routes/public/agent.js +21 -0
- package/dist/routes/public/agent.js.map +1 -1
- package/dist/services/core-services.d.ts.map +1 -1
- package/dist/services/core-services.js +4 -0
- package/dist/services/core-services.js.map +1 -1
- package/package.json +9 -9
- package/src/__tests__/agent-config-routes.test.ts +0 -254
- package/src/__tests__/agent-history-routes.test.ts +0 -72
- package/src/__tests__/agent-routes.test.ts +0 -68
- package/src/__tests__/agent-schedules-routes.test.ts +0 -59
- package/src/__tests__/agent-settings-store.test.ts +0 -323
- package/src/__tests__/bedrock-model-catalog.test.ts +0 -40
- package/src/__tests__/bedrock-openai-service.test.ts +0 -157
- package/src/__tests__/bedrock-provider-module.test.ts +0 -56
- package/src/__tests__/chat-instance-manager-slack.test.ts +0 -204
- package/src/__tests__/chat-response-bridge.test.ts +0 -131
- package/src/__tests__/config-memory-plugins.test.ts +0 -92
- package/src/__tests__/config-request-store.test.ts +0 -127
- package/src/__tests__/connection-routes.test.ts +0 -144
- package/src/__tests__/core-services-store-selection.test.ts +0 -92
- package/src/__tests__/docker-deployment.test.ts +0 -1211
- package/src/__tests__/embedded-deployment.test.ts +0 -342
- package/src/__tests__/grant-store.test.ts +0 -148
- package/src/__tests__/http-proxy.test.ts +0 -281
- package/src/__tests__/instruction-service.test.ts +0 -37
- package/src/__tests__/link-buttons.test.ts +0 -112
- package/src/__tests__/lobu.test.ts +0 -32
- package/src/__tests__/mcp-config-service.test.ts +0 -347
- package/src/__tests__/mcp-proxy.test.ts +0 -694
- package/src/__tests__/message-handler-bridge.test.ts +0 -17
- package/src/__tests__/model-selection.test.ts +0 -172
- package/src/__tests__/oauth-templates.test.ts +0 -39
- package/src/__tests__/platform-adapter-slack-send.test.ts +0 -114
- package/src/__tests__/platform-helpers-model-resolution.test.ts +0 -253
- package/src/__tests__/provider-inheritance.test.ts +0 -212
- package/src/__tests__/routes/cli-auth.test.ts +0 -337
- package/src/__tests__/routes/interactions.test.ts +0 -121
- package/src/__tests__/secret-proxy.test.ts +0 -85
- package/src/__tests__/session-manager.test.ts +0 -572
- package/src/__tests__/setup.ts +0 -133
- package/src/__tests__/skill-and-mcp-registry.test.ts +0 -203
- package/src/__tests__/slack-routes.test.ts +0 -161
- package/src/__tests__/system-config-resolver.test.ts +0 -75
- package/src/__tests__/system-message-limiter.test.ts +0 -89
- package/src/__tests__/system-skills-service.test.ts +0 -362
- package/src/__tests__/transcription-service.test.ts +0 -222
- package/src/__tests__/utils/rate-limiter.test.ts +0 -102
- package/src/__tests__/worker-connection-manager.test.ts +0 -497
- package/src/__tests__/worker-job-router.test.ts +0 -722
- package/src/api/index.ts +0 -1
- package/src/api/platform.ts +0 -292
- package/src/api/response-renderer.ts +0 -157
- package/src/auth/agent-metadata-store.ts +0 -168
- package/src/auth/api-auth-middleware.ts +0 -69
- package/src/auth/api-key-provider-module.ts +0 -213
- package/src/auth/base-provider-module.ts +0 -201
- package/src/auth/bedrock/provider-module.ts +0 -110
- package/src/auth/chatgpt/chatgpt-oauth-module.ts +0 -185
- package/src/auth/chatgpt/device-code-client.ts +0 -218
- package/src/auth/chatgpt/index.ts +0 -1
- package/src/auth/claude/oauth-module.ts +0 -280
- package/src/auth/cli/token-service.ts +0 -249
- package/src/auth/external/client.ts +0 -560
- package/src/auth/external/device-code-client.ts +0 -235
- package/src/auth/mcp/config-service.ts +0 -420
- package/src/auth/mcp/proxy.ts +0 -1086
- package/src/auth/mcp/string-substitution.ts +0 -17
- package/src/auth/mcp/tool-cache.ts +0 -90
- package/src/auth/oauth/base-client.ts +0 -267
- package/src/auth/oauth/client.ts +0 -153
- package/src/auth/oauth/credentials.ts +0 -7
- package/src/auth/oauth/providers.ts +0 -69
- package/src/auth/oauth/state-store.ts +0 -150
- package/src/auth/oauth-templates.ts +0 -179
- package/src/auth/provider-catalog.ts +0 -220
- package/src/auth/provider-model-options.ts +0 -41
- package/src/auth/settings/agent-settings-store.ts +0 -565
- package/src/auth/settings/auth-profiles-manager.ts +0 -216
- package/src/auth/settings/index.ts +0 -12
- package/src/auth/settings/model-preference-store.ts +0 -52
- package/src/auth/settings/model-selection.ts +0 -135
- package/src/auth/settings/resolved-settings-view.ts +0 -298
- package/src/auth/settings/template-utils.ts +0 -44
- package/src/auth/settings/token-service.ts +0 -88
- package/src/auth/system-env-store.ts +0 -98
- package/src/auth/user-agents-store.ts +0 -68
- package/src/channels/binding-service.ts +0 -214
- package/src/channels/index.ts +0 -4
- package/src/cli/gateway.ts +0 -1312
- package/src/cli/index.ts +0 -74
- package/src/commands/built-in-commands.ts +0 -80
- package/src/commands/command-dispatcher.ts +0 -94
- package/src/commands/command-reply-adapters.ts +0 -27
- package/src/config/file-loader.ts +0 -618
- package/src/config/index.ts +0 -588
- package/src/config/network-allowlist.ts +0 -71
- package/src/connections/chat-instance-manager.ts +0 -1284
- package/src/connections/chat-response-bridge.ts +0 -618
- package/src/connections/index.ts +0 -7
- package/src/connections/interaction-bridge.ts +0 -831
- package/src/connections/message-handler-bridge.ts +0 -440
- package/src/connections/platform-auth-methods.ts +0 -15
- package/src/connections/types.ts +0 -84
- package/src/gateway/connection-manager.ts +0 -291
- package/src/gateway/index.ts +0 -698
- package/src/gateway/job-router.ts +0 -201
- package/src/gateway-main.ts +0 -200
- package/src/index.ts +0 -41
- package/src/infrastructure/queue/index.ts +0 -12
- package/src/infrastructure/queue/queue-producer.ts +0 -148
- package/src/infrastructure/queue/redis-queue.ts +0 -361
- package/src/infrastructure/queue/types.ts +0 -133
- package/src/infrastructure/redis/system-message-limiter.ts +0 -94
- package/src/interactions/config-request-store.ts +0 -198
- package/src/interactions.ts +0 -363
- package/src/lobu.ts +0 -311
- package/src/metrics/prometheus.ts +0 -159
- package/src/modules/module-system.ts +0 -179
- package/src/orchestration/base-deployment-manager.ts +0 -900
- package/src/orchestration/deployment-utils.ts +0 -98
- package/src/orchestration/impl/docker-deployment.ts +0 -620
- package/src/orchestration/impl/embedded-deployment.ts +0 -268
- package/src/orchestration/impl/index.ts +0 -8
- package/src/orchestration/impl/k8s/deployment.ts +0 -1061
- package/src/orchestration/impl/k8s/helpers.ts +0 -610
- package/src/orchestration/impl/k8s/index.ts +0 -1
- package/src/orchestration/index.ts +0 -333
- package/src/orchestration/message-consumer.ts +0 -584
- package/src/orchestration/scheduled-wakeup.ts +0 -704
- package/src/permissions/approval-policy.ts +0 -36
- package/src/permissions/grant-store.ts +0 -219
- package/src/platform/file-handler.ts +0 -66
- package/src/platform/link-buttons.ts +0 -57
- package/src/platform/renderer-utils.ts +0 -44
- package/src/platform/response-renderer.ts +0 -84
- package/src/platform/unified-thread-consumer.ts +0 -194
- package/src/platform.ts +0 -318
- package/src/proxy/http-proxy.ts +0 -752
- package/src/proxy/proxy-manager.ts +0 -81
- package/src/proxy/secret-proxy.ts +0 -402
- package/src/proxy/token-refresh-job.ts +0 -143
- package/src/routes/internal/audio.ts +0 -141
- package/src/routes/internal/device-auth.ts +0 -652
- package/src/routes/internal/files.ts +0 -226
- package/src/routes/internal/history.ts +0 -69
- package/src/routes/internal/images.ts +0 -127
- package/src/routes/internal/interactions.ts +0 -84
- package/src/routes/internal/middleware.ts +0 -23
- package/src/routes/internal/schedule.ts +0 -226
- package/src/routes/internal/types.ts +0 -22
- package/src/routes/openapi-auto.ts +0 -239
- package/src/routes/public/agent-access.ts +0 -23
- package/src/routes/public/agent-config.ts +0 -675
- package/src/routes/public/agent-history.ts +0 -422
- package/src/routes/public/agent-schedules.ts +0 -296
- package/src/routes/public/agent.ts +0 -1086
- package/src/routes/public/agents.ts +0 -373
- package/src/routes/public/channels.ts +0 -191
- package/src/routes/public/cli-auth.ts +0 -896
- package/src/routes/public/connections.ts +0 -574
- package/src/routes/public/landing.ts +0 -16
- package/src/routes/public/oauth.ts +0 -147
- package/src/routes/public/settings-auth.ts +0 -104
- package/src/routes/public/slack.ts +0 -173
- package/src/routes/shared/agent-ownership.ts +0 -101
- package/src/routes/shared/token-verifier.ts +0 -34
- package/src/services/bedrock-model-catalog.ts +0 -217
- package/src/services/bedrock-openai-service.ts +0 -658
- package/src/services/core-services.ts +0 -1072
- package/src/services/image-generation-service.ts +0 -257
- package/src/services/instruction-service.ts +0 -318
- package/src/services/mcp-registry.ts +0 -94
- package/src/services/platform-helpers.ts +0 -287
- package/src/services/session-manager.ts +0 -262
- package/src/services/settings-resolver.ts +0 -74
- package/src/services/system-config-resolver.ts +0 -89
- package/src/services/system-skills-service.ts +0 -229
- package/src/services/transcription-service.ts +0 -684
- package/src/session.ts +0 -110
- package/src/spaces/index.ts +0 -1
- package/src/spaces/space-resolver.ts +0 -17
- package/src/stores/in-memory-agent-store.ts +0 -403
- package/src/stores/redis-agent-store.ts +0 -279
- package/src/utils/public-url.ts +0 -44
- package/src/utils/rate-limiter.ts +0 -94
- package/tsconfig.json +0 -33
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { BaseOAuth2Client } from "../oauth/base-client";
|
|
2
|
-
import type { OAuthCredentials } from "../oauth/credentials";
|
|
3
|
-
|
|
4
|
-
export const DEVICE_CODE_GRANT_TYPE =
|
|
5
|
-
"urn:ietf:params:oauth:grant-type:device_code";
|
|
6
|
-
|
|
7
|
-
export interface DeviceCodeClientConfig {
|
|
8
|
-
clientId: string;
|
|
9
|
-
clientSecret?: string;
|
|
10
|
-
tokenUrl: string;
|
|
11
|
-
deviceAuthorizationUrl: string;
|
|
12
|
-
scope: string;
|
|
13
|
-
/** RFC 8707 resource indicator included in token requests. */
|
|
14
|
-
resource?: string;
|
|
15
|
-
tokenEndpointAuthMethod?:
|
|
16
|
-
| "none"
|
|
17
|
-
| "client_secret_post"
|
|
18
|
-
| "client_secret_basic";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface DeviceAuthorizationStartResult {
|
|
22
|
-
deviceAuthId: string;
|
|
23
|
-
userCode: string;
|
|
24
|
-
verificationUri: string;
|
|
25
|
-
verificationUriComplete?: string;
|
|
26
|
-
interval: number;
|
|
27
|
-
expiresIn: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type DeviceAuthorizationPollResult =
|
|
31
|
-
| {
|
|
32
|
-
status: "pending";
|
|
33
|
-
interval?: number;
|
|
34
|
-
}
|
|
35
|
-
| {
|
|
36
|
-
status: "complete";
|
|
37
|
-
credentials: OAuthCredentials;
|
|
38
|
-
}
|
|
39
|
-
| {
|
|
40
|
-
status: "error";
|
|
41
|
-
error: string;
|
|
42
|
-
errorCode?: string;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
interface DeviceAuthorizationResponse {
|
|
46
|
-
device_code: string;
|
|
47
|
-
user_code: string;
|
|
48
|
-
verification_uri: string;
|
|
49
|
-
verification_uri_complete?: string;
|
|
50
|
-
expires_in: number;
|
|
51
|
-
interval?: number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface DeviceTokenSuccessResponse {
|
|
55
|
-
access_token: string;
|
|
56
|
-
refresh_token?: string;
|
|
57
|
-
token_type?: string;
|
|
58
|
-
expires_in?: number;
|
|
59
|
-
scope?: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface DeviceTokenErrorResponse {
|
|
63
|
-
error: string;
|
|
64
|
-
error_description?: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export class GenericDeviceCodeClient extends BaseOAuth2Client {
|
|
68
|
-
constructor(private readonly config: DeviceCodeClientConfig) {
|
|
69
|
-
super("external-device-code-client");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async requestDeviceCode(): Promise<DeviceAuthorizationStartResult> {
|
|
73
|
-
const params: Record<string, string> = {
|
|
74
|
-
client_id: this.config.clientId,
|
|
75
|
-
scope: this.config.scope,
|
|
76
|
-
};
|
|
77
|
-
if (this.config.resource) {
|
|
78
|
-
params.resource = this.config.resource;
|
|
79
|
-
}
|
|
80
|
-
const { headers, body } = this.buildAuthenticatedFormBody(params);
|
|
81
|
-
|
|
82
|
-
const response = await fetch(this.config.deviceAuthorizationUrl, {
|
|
83
|
-
method: "POST",
|
|
84
|
-
headers,
|
|
85
|
-
body: body.toString(),
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const data = await this.parseJsonResponse<
|
|
89
|
-
DeviceAuthorizationResponse | DeviceTokenErrorResponse
|
|
90
|
-
>(response);
|
|
91
|
-
|
|
92
|
-
if (!response.ok || "error" in data) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
this.formatOAuthError("Device authorization failed", data)
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
deviceAuthId: data.device_code,
|
|
100
|
-
userCode: data.user_code,
|
|
101
|
-
verificationUri: data.verification_uri,
|
|
102
|
-
verificationUriComplete: data.verification_uri_complete,
|
|
103
|
-
interval: Math.max(data.interval ?? 5, 1),
|
|
104
|
-
expiresIn: data.expires_in,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async pollForToken(
|
|
109
|
-
deviceAuthId: string,
|
|
110
|
-
intervalSeconds?: number
|
|
111
|
-
): Promise<DeviceAuthorizationPollResult> {
|
|
112
|
-
const params: Record<string, string> = {
|
|
113
|
-
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
114
|
-
device_code: deviceAuthId,
|
|
115
|
-
client_id: this.config.clientId,
|
|
116
|
-
};
|
|
117
|
-
if (this.config.resource) {
|
|
118
|
-
params.resource = this.config.resource;
|
|
119
|
-
}
|
|
120
|
-
const { headers, body } = this.buildAuthenticatedFormBody(params);
|
|
121
|
-
|
|
122
|
-
const response = await fetch(this.config.tokenUrl, {
|
|
123
|
-
method: "POST",
|
|
124
|
-
headers,
|
|
125
|
-
body: body.toString(),
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const data = await this.parseJsonResponse<
|
|
129
|
-
DeviceTokenSuccessResponse | DeviceTokenErrorResponse
|
|
130
|
-
>(response);
|
|
131
|
-
|
|
132
|
-
if (!response.ok || "error" in data) {
|
|
133
|
-
if ("error" in data) {
|
|
134
|
-
if (data.error === "authorization_pending") {
|
|
135
|
-
return { status: "pending", interval: intervalSeconds };
|
|
136
|
-
}
|
|
137
|
-
if (data.error === "slow_down") {
|
|
138
|
-
return {
|
|
139
|
-
status: "pending",
|
|
140
|
-
interval: Math.max((intervalSeconds ?? 5) + 5, 1),
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
if (data.error === "expired_token" || data.error === "access_denied") {
|
|
144
|
-
return {
|
|
145
|
-
status: "error",
|
|
146
|
-
error: data.error_description || data.error,
|
|
147
|
-
errorCode: data.error,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
throw new Error(
|
|
153
|
-
this.formatOAuthError("Device token polling failed", data)
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const credentials = this.buildCredentials(data);
|
|
158
|
-
return {
|
|
159
|
-
status: "complete",
|
|
160
|
-
credentials,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private buildCredentials(
|
|
165
|
-
tokenData: DeviceTokenSuccessResponse
|
|
166
|
-
): OAuthCredentials {
|
|
167
|
-
return {
|
|
168
|
-
accessToken: tokenData.access_token,
|
|
169
|
-
refreshToken: tokenData.refresh_token,
|
|
170
|
-
tokenType: tokenData.token_type || "Bearer",
|
|
171
|
-
expiresAt:
|
|
172
|
-
this.calculateExpiresAt(tokenData.expires_in) ?? Date.now() + 3_600_000,
|
|
173
|
-
scopes: this.parseScopes(tokenData.scope),
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private buildAuthenticatedFormBody(params: Record<string, string>): {
|
|
178
|
-
headers: Record<string, string>;
|
|
179
|
-
body: URLSearchParams;
|
|
180
|
-
} {
|
|
181
|
-
const body = new URLSearchParams(params);
|
|
182
|
-
const headers: Record<string, string> = {
|
|
183
|
-
Accept: "application/json",
|
|
184
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
185
|
-
};
|
|
186
|
-
const authMethod = this.config.tokenEndpointAuthMethod || "none";
|
|
187
|
-
|
|
188
|
-
if (authMethod === "client_secret_basic") {
|
|
189
|
-
headers.Authorization = `Basic ${Buffer.from(
|
|
190
|
-
`${this.config.clientId}:${this.config.clientSecret || ""}`
|
|
191
|
-
).toString("base64")}`;
|
|
192
|
-
} else if (
|
|
193
|
-
authMethod === "client_secret_post" &&
|
|
194
|
-
this.config.clientSecret
|
|
195
|
-
) {
|
|
196
|
-
body.set("client_secret", this.config.clientSecret);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return { headers, body };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private async parseJsonResponse<T>(response: Response): Promise<T> {
|
|
203
|
-
const contentType = response.headers.get("content-type") || "";
|
|
204
|
-
if (contentType.includes("application/json")) {
|
|
205
|
-
return (await response.json()) as T;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const text = await response.text();
|
|
209
|
-
try {
|
|
210
|
-
return JSON.parse(text) as T;
|
|
211
|
-
} catch {
|
|
212
|
-
return { error: text || response.statusText } as T;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private formatOAuthError(prefix: string, data: unknown): string {
|
|
217
|
-
if (data && typeof data === "object" && "error" in data) {
|
|
218
|
-
const record = data as {
|
|
219
|
-
error?: unknown;
|
|
220
|
-
error_description?: unknown;
|
|
221
|
-
};
|
|
222
|
-
const error =
|
|
223
|
-
typeof record.error === "string" ? record.error : "unknown_error";
|
|
224
|
-
const description =
|
|
225
|
-
typeof record.error_description === "string"
|
|
226
|
-
? record.error_description
|
|
227
|
-
: undefined;
|
|
228
|
-
return description
|
|
229
|
-
? `${prefix}: ${error} - ${description}`
|
|
230
|
-
: `${prefix}: ${error}`;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return prefix;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type McpOAuthConfig,
|
|
3
|
-
createLogger,
|
|
4
|
-
verifyWorkerToken,
|
|
5
|
-
} from "@lobu/core";
|
|
6
|
-
import type { SystemConfigResolver } from "../../services/system-config-resolver";
|
|
7
|
-
import type { AgentSettingsStore } from "../settings/agent-settings-store";
|
|
8
|
-
|
|
9
|
-
const logger = createLogger("mcp-config-service");
|
|
10
|
-
|
|
11
|
-
interface McpInput {
|
|
12
|
-
type: "promptString";
|
|
13
|
-
id: string;
|
|
14
|
-
description: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface HttpMcpServerConfig {
|
|
18
|
-
id: string;
|
|
19
|
-
upstreamUrl: string;
|
|
20
|
-
oauth?: McpOAuthConfig;
|
|
21
|
-
inputs?: McpInput[];
|
|
22
|
-
headers?: Record<string, string>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface WorkerMcpConfig {
|
|
26
|
-
mcpServers: Record<string, any>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface McpStatus {
|
|
30
|
-
id: string;
|
|
31
|
-
name: string;
|
|
32
|
-
requiresAuth: boolean;
|
|
33
|
-
requiresInput: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface LoadedConfig {
|
|
37
|
-
rawServers: Record<string, any>;
|
|
38
|
-
httpServers: Map<string, HttpMcpServerConfig>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface McpConfigServiceOptions {
|
|
42
|
-
agentSettingsStore?: AgentSettingsStore;
|
|
43
|
-
configResolver?: SystemConfigResolver;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export class McpConfigService {
|
|
47
|
-
private cache?: LoadedConfig;
|
|
48
|
-
private agentSettingsStore?: AgentSettingsStore;
|
|
49
|
-
private configResolver?: SystemConfigResolver;
|
|
50
|
-
|
|
51
|
-
constructor(options: McpConfigServiceOptions = {}) {
|
|
52
|
-
this.agentSettingsStore = options.agentSettingsStore;
|
|
53
|
-
this.configResolver = options.configResolver;
|
|
54
|
-
logger.debug(`McpConfigService initialized`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Register additional global MCP servers (e.g. from system skills).
|
|
59
|
-
*/
|
|
60
|
-
registerGlobalServers(servers: Record<string, any>): void {
|
|
61
|
-
if (!this.cache) {
|
|
62
|
-
this.cache = {
|
|
63
|
-
rawServers: {},
|
|
64
|
-
httpServers: new Map(),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const normalized = normalizeConfig({ mcpServers: servers });
|
|
69
|
-
for (const [id, raw] of Object.entries(normalized.rawServers)) {
|
|
70
|
-
if (this.cache.rawServers[id]) continue;
|
|
71
|
-
this.cache.rawServers[id] = raw;
|
|
72
|
-
}
|
|
73
|
-
for (const [id, http] of normalized.httpServers) {
|
|
74
|
-
if (this.cache.httpServers.has(id)) continue;
|
|
75
|
-
this.cache.httpServers.set(id, http);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
logger.info(
|
|
79
|
-
`Registered ${Object.keys(servers).length} global MCP(s) from system skills: ${Object.keys(servers).join(", ")}`
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Return MCP config tailored for a worker request.
|
|
85
|
-
*/
|
|
86
|
-
async getWorkerConfig(options: {
|
|
87
|
-
baseUrl: string;
|
|
88
|
-
workerToken: string;
|
|
89
|
-
deploymentName?: string;
|
|
90
|
-
}): Promise<WorkerMcpConfig> {
|
|
91
|
-
const { baseUrl, workerToken } = options;
|
|
92
|
-
const config = await this.loadConfig();
|
|
93
|
-
const workerConfig: WorkerMcpConfig = { mcpServers: {} };
|
|
94
|
-
|
|
95
|
-
const tokenData = verifyWorkerToken(workerToken);
|
|
96
|
-
if (!tokenData) {
|
|
97
|
-
logger.warn("Failed to verify worker token");
|
|
98
|
-
return workerConfig;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const { userId, agentId } = tokenData;
|
|
102
|
-
const effectiveAgentId = agentId || userId;
|
|
103
|
-
logger.info(`Building MCP config for user ${userId}`);
|
|
104
|
-
|
|
105
|
-
// Process global MCPs
|
|
106
|
-
for (const [id, serverConfig] of Object.entries(config.rawServers)) {
|
|
107
|
-
const cloned = cloneConfig(serverConfig);
|
|
108
|
-
const httpServer = config.httpServers.get(id);
|
|
109
|
-
|
|
110
|
-
if (httpServer) {
|
|
111
|
-
logger.info(`Configuring global MCP ${id}: baseUrl=${baseUrl}`);
|
|
112
|
-
cloned.url = baseUrl;
|
|
113
|
-
cloned.type = "sse";
|
|
114
|
-
cloned.headers = mergeHeaders(cloned.headers, workerToken, id);
|
|
115
|
-
logger.info(
|
|
116
|
-
`Including global MCP ${id} with URL=${cloned.url} and X-Mcp-Id header`
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
workerConfig.mcpServers[id] = cloned;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Merge per-agent MCPs from live agent settings
|
|
124
|
-
const agentSettingsMcpServers =
|
|
125
|
-
(await this.getAgentMcpServers(effectiveAgentId)) || {};
|
|
126
|
-
for (const [id, serverConfig] of Object.entries(agentSettingsMcpServers)) {
|
|
127
|
-
if (workerConfig.mcpServers[id]) {
|
|
128
|
-
logger.warn(
|
|
129
|
-
`Per-agent MCP ${id} skipped - global MCP with same ID exists`
|
|
130
|
-
);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const cloned = cloneConfig(serverConfig);
|
|
135
|
-
|
|
136
|
-
if (cloned.enabled === false) {
|
|
137
|
-
logger.debug(`Skipping disabled per-agent MCP ${id}`);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (cloned.url) {
|
|
142
|
-
logger.info(`Configuring per-agent HTTP MCP ${id}: baseUrl=${baseUrl}`);
|
|
143
|
-
cloned.originalUrl = cloned.url;
|
|
144
|
-
cloned.url = baseUrl;
|
|
145
|
-
cloned.type = "sse";
|
|
146
|
-
cloned.headers = mergeHeaders(cloned.headers, workerToken, id);
|
|
147
|
-
cloned.perAgent = true;
|
|
148
|
-
logger.info(`Including per-agent HTTP MCP ${id}`);
|
|
149
|
-
} else if (cloned.command) {
|
|
150
|
-
logger.info(`Including per-agent stdio MCP ${id}: ${cloned.command}`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
workerConfig.mcpServers[id] = cloned;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (Object.keys(agentSettingsMcpServers).length > 0) {
|
|
157
|
-
logger.info(
|
|
158
|
-
`Merged ${Object.keys(agentSettingsMcpServers).length} per-agent MCPs from settings for agent ${effectiveAgentId}`
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
logger.info(
|
|
163
|
-
`Returning worker config with ${Object.keys(workerConfig.mcpServers).length} MCPs for user ${userId}:`,
|
|
164
|
-
{
|
|
165
|
-
mcpIds: Object.keys(workerConfig.mcpServers),
|
|
166
|
-
configs: Object.entries(workerConfig.mcpServers).map(([id, cfg]) => ({
|
|
167
|
-
id,
|
|
168
|
-
type: cfg.type,
|
|
169
|
-
hasUrl: !!cfg.url,
|
|
170
|
-
hasCommand: !!cfg.command,
|
|
171
|
-
perAgent: cfg.perAgent || false,
|
|
172
|
-
})),
|
|
173
|
-
}
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
return workerConfig;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Get status of all MCPs for a specific agent
|
|
181
|
-
*/
|
|
182
|
-
async getMcpStatus(agentId: string): Promise<McpStatus[]> {
|
|
183
|
-
const httpServers = await this.getAllHttpServers(agentId);
|
|
184
|
-
const statuses: McpStatus[] = [];
|
|
185
|
-
|
|
186
|
-
for (const [id, httpServer] of httpServers) {
|
|
187
|
-
const requiresAuth = !!httpServer.oauth;
|
|
188
|
-
const requiresInput = !!(
|
|
189
|
-
httpServer.inputs && httpServer.inputs.length > 0
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
statuses.push({
|
|
193
|
-
id,
|
|
194
|
-
name: id,
|
|
195
|
-
requiresAuth,
|
|
196
|
-
requiresInput,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return statuses;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Get HTTP proxy metadata for a specific MCP server.
|
|
205
|
-
*/
|
|
206
|
-
async getHttpServer(
|
|
207
|
-
id: string,
|
|
208
|
-
agentId?: string
|
|
209
|
-
): Promise<HttpMcpServerConfig | undefined> {
|
|
210
|
-
const httpServers = await this.getAllHttpServers(agentId);
|
|
211
|
-
return httpServers.get(id);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Get all HTTP proxy metadata for all MCP servers.
|
|
216
|
-
*/
|
|
217
|
-
async getAllHttpServers(
|
|
218
|
-
agentId?: string
|
|
219
|
-
): Promise<Map<string, HttpMcpServerConfig>> {
|
|
220
|
-
const config = await this.loadConfig();
|
|
221
|
-
const merged = new Map(config.httpServers);
|
|
222
|
-
|
|
223
|
-
if (agentId) {
|
|
224
|
-
const agentMcpServers = await this.getAgentMcpServers(agentId);
|
|
225
|
-
for (const [id, serverConfig] of Object.entries(agentMcpServers)) {
|
|
226
|
-
if (merged.has(id)) continue;
|
|
227
|
-
const httpServer = toHttpServerConfig(id, serverConfig);
|
|
228
|
-
if (httpServer) {
|
|
229
|
-
merged.set(id, httpServer);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return merged;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Return global MCP server configs in settings-compatible format.
|
|
239
|
-
*/
|
|
240
|
-
async getGlobalMcpServers(): Promise<
|
|
241
|
-
Record<string, { url?: string; type?: "sse" | "stdio" }>
|
|
242
|
-
> {
|
|
243
|
-
const config = await this.loadConfig();
|
|
244
|
-
const result: Record<string, { url?: string; type?: "sse" | "stdio" }> = {};
|
|
245
|
-
for (const [id, raw] of Object.entries(config.rawServers)) {
|
|
246
|
-
const type = raw.type === "stdio" ? ("stdio" as const) : ("sse" as const);
|
|
247
|
-
result[id] = { url: raw.url, type };
|
|
248
|
-
}
|
|
249
|
-
return result;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private async getAgentMcpServers(
|
|
253
|
-
agentId: string
|
|
254
|
-
): Promise<Record<string, any>> {
|
|
255
|
-
if (!this.agentSettingsStore) {
|
|
256
|
-
return {};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
const settings = await this.agentSettingsStore.getSettings(agentId);
|
|
261
|
-
return settings?.mcpServers || {};
|
|
262
|
-
} catch (error) {
|
|
263
|
-
logger.warn(`Failed to load per-agent MCP settings for ${agentId}`, {
|
|
264
|
-
error,
|
|
265
|
-
});
|
|
266
|
-
return {};
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private async loadConfig(): Promise<LoadedConfig> {
|
|
271
|
-
if (!this.cache) {
|
|
272
|
-
let globalMcpServers: Record<string, any> = {};
|
|
273
|
-
if (this.configResolver) {
|
|
274
|
-
globalMcpServers = await this.configResolver.getGlobalMcpServers();
|
|
275
|
-
}
|
|
276
|
-
const normalized = normalizeConfig({ mcpServers: globalMcpServers });
|
|
277
|
-
this.cache = normalized;
|
|
278
|
-
}
|
|
279
|
-
return this.cache;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Parse and validate an oauth config from raw MCP server config.
|
|
285
|
-
* Handles backward compat: migrates top-level `resource` into `oauth.resource`,
|
|
286
|
-
* and treats `loginUrl` presence as `oauth: {}` (requiresAuth flag).
|
|
287
|
-
*/
|
|
288
|
-
function parseOAuthConfig(raw: any): McpOAuthConfig | undefined {
|
|
289
|
-
const hasLoginUrl = typeof raw.loginUrl === "string";
|
|
290
|
-
const hasOAuth = raw.oauth && typeof raw.oauth === "object";
|
|
291
|
-
|
|
292
|
-
if (!hasOAuth && !hasLoginUrl && typeof raw.resource !== "string") {
|
|
293
|
-
return undefined;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const config: McpOAuthConfig = {};
|
|
297
|
-
|
|
298
|
-
if (hasOAuth) {
|
|
299
|
-
const obj = raw.oauth;
|
|
300
|
-
if (typeof obj.authUrl === "string") config.authUrl = obj.authUrl;
|
|
301
|
-
if (typeof obj.tokenUrl === "string") config.tokenUrl = obj.tokenUrl;
|
|
302
|
-
if (typeof obj.clientId === "string") config.clientId = obj.clientId;
|
|
303
|
-
if (typeof obj.clientSecret === "string")
|
|
304
|
-
config.clientSecret = obj.clientSecret;
|
|
305
|
-
if (Array.isArray(obj.scopes))
|
|
306
|
-
config.scopes = obj.scopes.filter((s: unknown) => typeof s === "string");
|
|
307
|
-
if (typeof obj.deviceAuthorizationUrl === "string")
|
|
308
|
-
config.deviceAuthorizationUrl = obj.deviceAuthorizationUrl;
|
|
309
|
-
if (typeof obj.registrationUrl === "string")
|
|
310
|
-
config.registrationUrl = obj.registrationUrl;
|
|
311
|
-
if (typeof obj.resource === "string") config.resource = obj.resource;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Migrate top-level resource into oauth.resource (backward compat)
|
|
315
|
-
if (typeof raw.resource === "string" && !config.resource) {
|
|
316
|
-
config.resource = raw.resource;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return config;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function normalizeConfig(config: { mcpServers: Record<string, any> }) {
|
|
323
|
-
const rawServers: Record<string, any> = {};
|
|
324
|
-
const httpServers = new Map<string, HttpMcpServerConfig>();
|
|
325
|
-
|
|
326
|
-
for (const [id, serverConfig] of Object.entries(config.mcpServers)) {
|
|
327
|
-
if (!serverConfig || typeof serverConfig !== "object") {
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const cloned = cloneConfig(serverConfig);
|
|
332
|
-
rawServers[id] = cloned;
|
|
333
|
-
|
|
334
|
-
if (typeof cloned.url === "string" && isHttpUrl(cloned.url)) {
|
|
335
|
-
httpServers.set(id, {
|
|
336
|
-
id,
|
|
337
|
-
upstreamUrl: cloned.url,
|
|
338
|
-
oauth: parseOAuthConfig(cloned),
|
|
339
|
-
inputs: Array.isArray(cloned.inputs)
|
|
340
|
-
? cloned.inputs.filter(
|
|
341
|
-
(input: any) =>
|
|
342
|
-
input &&
|
|
343
|
-
typeof input === "object" &&
|
|
344
|
-
input.type === "promptString"
|
|
345
|
-
)
|
|
346
|
-
: undefined,
|
|
347
|
-
headers:
|
|
348
|
-
cloned.headers && typeof cloned.headers === "object"
|
|
349
|
-
? cloned.headers
|
|
350
|
-
: undefined,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return { rawServers, httpServers };
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function toHttpServerConfig(
|
|
359
|
-
id: string,
|
|
360
|
-
serverConfig: any
|
|
361
|
-
): HttpMcpServerConfig | null {
|
|
362
|
-
if (!serverConfig || typeof serverConfig !== "object") {
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (serverConfig.enabled === false) {
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const cloned = cloneConfig(serverConfig);
|
|
371
|
-
if (typeof cloned.url !== "string" || !isHttpUrl(cloned.url)) {
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return {
|
|
376
|
-
id,
|
|
377
|
-
upstreamUrl: cloned.url,
|
|
378
|
-
oauth: parseOAuthConfig(cloned),
|
|
379
|
-
inputs: Array.isArray(cloned.inputs)
|
|
380
|
-
? cloned.inputs.filter(
|
|
381
|
-
(input: any) =>
|
|
382
|
-
input && typeof input === "object" && input.type === "promptString"
|
|
383
|
-
)
|
|
384
|
-
: undefined,
|
|
385
|
-
headers:
|
|
386
|
-
cloned.headers && typeof cloned.headers === "object"
|
|
387
|
-
? cloned.headers
|
|
388
|
-
: undefined,
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function cloneConfig(config: any) {
|
|
393
|
-
return JSON.parse(JSON.stringify(config));
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function isHttpUrl(candidate: string): boolean {
|
|
397
|
-
return candidate.startsWith("http://") || candidate.startsWith("https://");
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function mergeHeaders(
|
|
401
|
-
existingHeaders: unknown,
|
|
402
|
-
workerToken: string,
|
|
403
|
-
mcpId: string
|
|
404
|
-
): Record<string, string> {
|
|
405
|
-
const normalized: Record<string, string> = {};
|
|
406
|
-
|
|
407
|
-
if (existingHeaders && typeof existingHeaders === "object") {
|
|
408
|
-
for (const [key, value] of Object.entries(existingHeaders as any)) {
|
|
409
|
-
if (typeof value === "string") {
|
|
410
|
-
normalized[key] = value;
|
|
411
|
-
} else if (value != null) {
|
|
412
|
-
normalized[key] = String(value);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
normalized.Authorization = `Bearer ${workerToken}`;
|
|
418
|
-
normalized["X-Mcp-Id"] = mcpId;
|
|
419
|
-
return normalized;
|
|
420
|
-
}
|