kloudy-mcp 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # kloudy-mcp
2
2
 
3
- Kloudy 的本地 stdio MCP 服务。它通过 HTTPS 调用 Kloudy API-X,并通过 API-A 检索公共 Wiki 证据;不直接连接数据库、向量库或 Kloudy 内容仓库。
3
+ Kloudy 的本地 stdio 与远程 Streamable HTTP MCP 服务。它通过 HTTPS 调用 Kloudy API-X,并通过 API-A 检索公共 Wiki 证据;不直接连接数据库、向量库或 Kloudy 内容仓库。
4
4
 
5
5
  ## 使用
6
6
 
@@ -36,6 +36,16 @@ npx -y kloudy-mcp logout
36
36
  codex mcp add kloudy -- npx -y kloudy-mcp serve
37
37
  ```
38
38
 
39
+ `serve`、`login` 和 `logout` 面向本地单用户客户端。线上 Agent 应为每个 HTTP 请求启动独立 `serve-request` 进程,使用已验证的请求身份;该命令只供受信父进程调用,不使用本地授权文件,不能在多个用户之间复用进程。
40
+
41
+ 远程服务使用:
42
+
43
+ ```bash
44
+ kloudy-mcp serve-http
45
+ ```
46
+
47
+ `serve-http` 提供无状态 `/mcp`、路径型 OAuth Protected Resource Metadata 和本机健康接口。它不读取本地登录文件;每个请求必须携带 Bearer Token,并通过受信内部授权上下文端点换取只在当前请求内存中使用的 User、Device 和 Character 身份。完整环境变量、协议边界和部署前置条件见 [Streamable HTTP 说明](docs/http-mcp.md)。
48
+
39
49
  默认连接 `https://shape.kloudy.cn/api-x` 和 `https://shape.kloudy.cn/api-a`。开发或测试时可通过 `KLOUDY_API_X_BASE_URL`、`KLOUDY_API_A_BASE_URL` 覆盖服务地址,通过 `KLOUDY_WEB_BASE_URL` 覆盖网页登录地址。
40
50
 
41
51
  `knowledge_search` 需要登录,返回索引版本、Wiki 证据片段和来源 URL;最终知识回答由 Codex 等 MCP 客户端根据证据生成,证据不足时应明确拒答。账号私有读取和写入工具也需要登录。Community 评论会立即公开发布,但只能删除当前 Agent Character 自己发布的评论;Blog 工具只能操作草稿;Archive 和 Wiki 投稿需要管理员审核后才会发布。
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { stderr, stdout } from 'node:process';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { ApiAClient } from '../../shared/src/api-a/client.js';
5
4
  import { ApiXClient } from '../../shared/src/api-x/client.js';
6
5
  import { loadConfig } from '../../shared/src/config.js';
7
6
  import { createMcpServer } from './mcp-server.js';
8
- import { FileStateStore } from '../../shared/src/state-store.js';
7
+ import { FileStateStore, takeRequestIdentity } from '../../shared/src/state-store.js';
9
8
  import { loginWithBrowser } from '../../shared/src/browser-login.js';
10
9
  import { getVersionCommandOutput, KLOUDY_MCP_VERSION } from './version.js';
10
+ import { createClients } from './clients.js';
11
+ import { loadHttpServerConfig } from './http-config.js';
12
+ import { installHttpServerShutdown, startHttpServer } from './http-server.js';
11
13
  async function main() {
12
14
  const [command = 'help', ...args] = process.argv.slice(2);
13
15
  const versionOutput = getVersionCommandOutput(command, args);
@@ -17,11 +19,31 @@ async function main() {
17
19
  }
18
20
  switch (command) {
19
21
  case 'serve': {
20
- const { api, knowledge } = createClients();
22
+ if (args.length > 0)
23
+ throw new Error('serve does not accept arguments');
24
+ const config = loadConfig();
25
+ const { api, knowledge } = createClients(new FileStateStore(config.stateDirectory), 'local-mcp');
26
+ const server = createMcpServer(api, knowledge);
27
+ await server.connect(new StdioServerTransport());
28
+ return;
29
+ }
30
+ case 'serve-request': {
31
+ if (args.length > 0)
32
+ throw new Error('serve-request does not accept arguments');
33
+ const { api, knowledge } = createClients(takeRequestIdentity(), 'agent-request-mcp');
21
34
  const server = createMcpServer(api, knowledge);
22
35
  await server.connect(new StdioServerTransport());
23
36
  return;
24
37
  }
38
+ case 'serve-http': {
39
+ if (args.length > 0)
40
+ throw new Error('serve-http does not accept arguments');
41
+ const config = loadHttpServerConfig();
42
+ const httpServer = await startHttpServer(config);
43
+ installHttpServerShutdown(httpServer);
44
+ stderr.write(`Kloudy MCP Streamable HTTP listening on ${config.host}:${config.port}.\n`);
45
+ return;
46
+ }
25
47
  case 'login': {
26
48
  if (args.length > 0)
27
49
  throw new Error('login does not accept arguments');
@@ -48,20 +70,14 @@ function createApiClient() {
48
70
  const config = loadConfig();
49
71
  return new ApiXClient(config, new FileStateStore(config.stateDirectory), fetch, `kloudy-mcp/${KLOUDY_MCP_VERSION}`, 'local-mcp');
50
72
  }
51
- function createClients() {
52
- const config = loadConfig();
53
- const stateStore = new FileStateStore(config.stateDirectory);
54
- return {
55
- api: new ApiXClient(config, stateStore, fetch, `kloudy-mcp/${KLOUDY_MCP_VERSION}`, 'local-mcp'),
56
- knowledge: new ApiAClient(config, stateStore, fetch, `kloudy-mcp/${KLOUDY_MCP_VERSION}`, 'local-mcp'),
57
- };
58
- }
59
73
  function helpText() {
60
74
  return [
61
- 'Kloudy local MCP',
75
+ 'Kloudy MCP',
62
76
  '',
63
77
  'Usage:',
64
78
  ' kloudy-mcp serve',
79
+ ' kloudy-mcp serve-request',
80
+ ' kloudy-mcp serve-http',
65
81
  ' kloudy-mcp login',
66
82
  ' kloudy-mcp logout',
67
83
  ' kloudy-mcp --version',
@@ -70,6 +86,17 @@ function helpText() {
70
86
  ' KLOUDY_API_X_BASE_URL Override the default https://shape.kloudy.cn/api-x address',
71
87
  ' KLOUDY_API_A_BASE_URL Override the default https://shape.kloudy.cn/api-a address',
72
88
  ' KLOUDY_WEB_BASE_URL Override the default https://shape.kloudy.cn address',
89
+ ' KLOUDY_MCP_HTTP_HOST serve-http listen host (default 127.0.0.1)',
90
+ ' KLOUDY_MCP_HTTP_PORT serve-http listen port (default 12176)',
91
+ ' KLOUDY_MCP_PUBLIC_URL OAuth resource URL (production: https://kloudy.cn/mcp)',
92
+ ' KLOUDY_MCP_AUTHORIZATION_SERVER_URL OAuth authorization server issuer',
93
+ ' KLOUDY_MCP_AUTH_CONTEXT_URL Trusted internal token context endpoint',
94
+ ' KLOUDY_MCP_AUTH_CONTEXT_SERVICE_TOKEN Service token for that endpoint',
95
+ '',
96
+ 'serve-request is for a trusted Agent parent process. It requires request-scoped identity',
97
+ 'environment variables and never reads or writes the local state file.',
98
+ 'serve-http starts the remote Streamable HTTP resource server and requires OAuth/resource',
99
+ 'configuration. It never reads or writes the local state file.',
73
100
  '',
74
101
  'Login opens a browser and requires an Agent Character. It is optional for public read-only tools.',
75
102
  '',
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { ApiAClient } from '../../shared/src/api-a/client.js';
2
+ import { ApiXClient } from '../../shared/src/api-x/client.js';
3
+ import { type AppConfig } from '../../shared/src/config.js';
4
+ import type { StateStore } from '../../shared/src/state-store.js';
5
+ export declare function createClients(stateStore: StateStore, clientKind: string, config?: AppConfig): {
6
+ api: ApiXClient;
7
+ knowledge: ApiAClient;
8
+ };
@@ -0,0 +1,12 @@
1
+ import { ApiAClient } from '../../shared/src/api-a/client.js';
2
+ import { ApiXClient } from '../../shared/src/api-x/client.js';
3
+ import { loadConfig } from '../../shared/src/config.js';
4
+ import { KLOUDY_MCP_VERSION } from './version.js';
5
+ export function createClients(stateStore, clientKind, config = loadConfig()) {
6
+ const userAgent = `kloudy-mcp/${KLOUDY_MCP_VERSION}`;
7
+ return {
8
+ api: new ApiXClient(config, stateStore, fetch, userAgent, clientKind),
9
+ knowledge: new ApiAClient(config, stateStore, fetch, userAgent, clientKind),
10
+ };
11
+ }
12
+ //# sourceMappingURL=clients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.js","sourceRoot":"","sources":["../../../src/clients.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAkB,MAAM,4BAA4B,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,UAAU,aAAa,CAC3B,UAAsB,EACtB,UAAkB,EAClB,SAAoB,UAAU,EAAE;IAEhC,MAAM,SAAS,GAAG,cAAc,kBAAkB,EAAE,CAAC;IACrD,OAAO;QACL,GAAG,EAAE,IAAI,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC;QACrE,SAAS,EAAE,IAAI,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC;KAC5E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
2
+ import type { OAuthTokenVerifier } from '@modelcontextprotocol/sdk/server/auth/provider.js';
3
+ import { RequestStateStore } from '../../shared/src/state-store.js';
4
+ import type { HttpServerConfig } from './http-config.js';
5
+ export declare class HttpAuthContextVerifier implements OAuthTokenVerifier {
6
+ private readonly config;
7
+ private readonly fetchImpl;
8
+ constructor(config: HttpServerConfig, fetchImpl?: typeof fetch);
9
+ verifyAccessToken(token: string): Promise<AuthInfo>;
10
+ }
11
+ export declare function requestStateFromAuthInfo(authInfo: AuthInfo | undefined): RequestStateStore;
@@ -0,0 +1,99 @@
1
+ import { InvalidTokenError, ServerError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
2
+ import { z } from 'zod';
3
+ import { RequestStateStore } from '../../shared/src/state-store.js';
4
+ const bearerTokenSchema = z.string().min(1).max(8192).regex(/^[\x21-\x7e]+$/);
5
+ const authContextResponseSchema = z.object({
6
+ active: z.literal(true),
7
+ clientId: z.string().min(1).max(512),
8
+ scopes: z.array(z.string().min(1).max(128)).max(64),
9
+ expiresAt: z.number().int().positive(),
10
+ resource: z.string().url(),
11
+ identity: z.object({
12
+ sessionToken: z.string().min(1).max(4096),
13
+ deviceId: z.string().min(1).max(4096),
14
+ characterId: z.string().min(1).max(4096),
15
+ }),
16
+ });
17
+ const AUTH_IDENTITY_KEY = 'kloudyRequestIdentity';
18
+ export class HttpAuthContextVerifier {
19
+ config;
20
+ fetchImpl;
21
+ constructor(config, fetchImpl = fetch) {
22
+ this.config = config;
23
+ this.fetchImpl = fetchImpl;
24
+ }
25
+ async verifyAccessToken(token) {
26
+ if (!bearerTokenSchema.safeParse(token).success)
27
+ throw invalidToken();
28
+ let response;
29
+ try {
30
+ response = await this.fetchImpl(this.config.authContextUrl, {
31
+ method: 'POST',
32
+ headers: {
33
+ Accept: 'application/json',
34
+ Authorization: `Bearer ${this.config.authContextServiceToken}`,
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ body: JSON.stringify({ accessToken: token, resource: this.config.publicUrl.href }),
38
+ signal: AbortSignal.timeout(this.config.authTimeoutMs),
39
+ });
40
+ }
41
+ catch {
42
+ throw unavailable();
43
+ }
44
+ if (!response.ok)
45
+ throw unavailable();
46
+ let payload;
47
+ try {
48
+ payload = await response.json();
49
+ }
50
+ catch {
51
+ throw unavailable();
52
+ }
53
+ const parsed = authContextResponseSchema.safeParse(payload);
54
+ if (!parsed.success)
55
+ throw invalidToken();
56
+ if (parsed.data.resource !== this.config.publicUrl.href)
57
+ throw invalidToken();
58
+ if (parsed.data.expiresAt <= Math.floor(Date.now() / 1000))
59
+ throw invalidToken();
60
+ let stateStore;
61
+ try {
62
+ stateStore = new RequestStateStore(parsed.data.identity);
63
+ }
64
+ catch {
65
+ throw invalidToken();
66
+ }
67
+ return {
68
+ token,
69
+ clientId: parsed.data.clientId,
70
+ scopes: [...parsed.data.scopes],
71
+ expiresAt: parsed.data.expiresAt,
72
+ resource: new URL(parsed.data.resource),
73
+ extra: { [AUTH_IDENTITY_KEY]: await stateStore.read() },
74
+ };
75
+ }
76
+ }
77
+ export function requestStateFromAuthInfo(authInfo) {
78
+ const identity = authInfo?.extra?.[AUTH_IDENTITY_KEY];
79
+ if (!identity || typeof identity !== 'object')
80
+ throw invalidToken();
81
+ const value = identity;
82
+ try {
83
+ return new RequestStateStore({
84
+ sessionToken: String(value.sessionToken ?? ''),
85
+ deviceId: String(value.deviceId ?? ''),
86
+ characterId: String(value.characterId ?? ''),
87
+ });
88
+ }
89
+ catch {
90
+ throw invalidToken();
91
+ }
92
+ }
93
+ function invalidToken() {
94
+ return new InvalidTokenError('Access token is invalid or expired');
95
+ }
96
+ function unavailable() {
97
+ return new ServerError('Authorization service is temporarily unavailable');
98
+ }
99
+ //# sourceMappingURL=http-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-auth.js","sourceRoot":"","sources":["../../../src/http-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface HttpServerConfig {
2
+ host: string;
3
+ port: number;
4
+ publicUrl: URL;
5
+ authorizationServerUrl: URL;
6
+ authContextUrl: URL;
7
+ authContextServiceToken: string;
8
+ authTimeoutMs: number;
9
+ requiredScopes: string[];
10
+ allowedHosts: string[];
11
+ }
12
+ export declare function loadHttpServerConfig(env?: NodeJS.ProcessEnv): HttpServerConfig;
@@ -0,0 +1,115 @@
1
+ import { z } from 'zod';
2
+ const DEFAULT_HTTP_HOST = '127.0.0.1';
3
+ const DEFAULT_HTTP_PORT = 12176;
4
+ const DEFAULT_PUBLIC_URL = `http://${DEFAULT_HTTP_HOST}:${DEFAULT_HTTP_PORT}/mcp`;
5
+ const DEFAULT_SCOPES = ['mcp:tools'];
6
+ const serviceTokenSchema = z
7
+ .string()
8
+ .min(32)
9
+ .max(4096)
10
+ .regex(/^[\x21-\x7e]+$/);
11
+ const scopeSchema = z.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/);
12
+ export function loadHttpServerConfig(env = process.env) {
13
+ const host = parseHost(env.KLOUDY_MCP_HTTP_HOST);
14
+ const port = parsePort(env.KLOUDY_MCP_HTTP_PORT);
15
+ const publicUrl = parseUrl(env.KLOUDY_MCP_PUBLIC_URL ?? DEFAULT_PUBLIC_URL, 'KLOUDY_MCP_PUBLIC_URL', true);
16
+ if (publicUrl.pathname !== '/mcp' || publicUrl.search || publicUrl.hash) {
17
+ throw new Error('KLOUDY_MCP_PUBLIC_URL must use the exact /mcp path without query or hash');
18
+ }
19
+ const authorizationServerUrl = parseRequiredUrl(env.KLOUDY_MCP_AUTHORIZATION_SERVER_URL, 'KLOUDY_MCP_AUTHORIZATION_SERVER_URL');
20
+ const authContextUrl = parseRequiredUrl(env.KLOUDY_MCP_AUTH_CONTEXT_URL, 'KLOUDY_MCP_AUTH_CONTEXT_URL');
21
+ if (authorizationServerUrl.search || authorizationServerUrl.hash) {
22
+ throw new Error('KLOUDY_MCP_AUTHORIZATION_SERVER_URL must not contain query or hash');
23
+ }
24
+ if (authContextUrl.search || authContextUrl.hash) {
25
+ throw new Error('KLOUDY_MCP_AUTH_CONTEXT_URL must not contain query or hash');
26
+ }
27
+ const authContextServiceToken = parseSecret(env.KLOUDY_MCP_AUTH_CONTEXT_SERVICE_TOKEN, 'KLOUDY_MCP_AUTH_CONTEXT_SERVICE_TOKEN');
28
+ const authTimeoutMs = parseTimeout(env.KLOUDY_MCP_AUTH_TIMEOUT_MS);
29
+ const requiredScopes = parseScopes(env.KLOUDY_MCP_REQUIRED_SCOPES);
30
+ const allowedHosts = unique([
31
+ publicUrl.hostname,
32
+ host,
33
+ '127.0.0.1',
34
+ 'localhost',
35
+ ]);
36
+ return {
37
+ host,
38
+ port,
39
+ publicUrl,
40
+ authorizationServerUrl,
41
+ authContextUrl,
42
+ authContextServiceToken,
43
+ authTimeoutMs,
44
+ requiredScopes,
45
+ allowedHosts,
46
+ };
47
+ }
48
+ function parseHost(raw) {
49
+ const value = raw ?? DEFAULT_HTTP_HOST;
50
+ if (!/^[A-Za-z0-9.:-]+$/.test(value) || value.includes('/') || value.includes('@')) {
51
+ throw new Error('KLOUDY_MCP_HTTP_HOST must be a plain host name or IP address');
52
+ }
53
+ return value;
54
+ }
55
+ function parsePort(raw) {
56
+ if (raw === undefined)
57
+ return DEFAULT_HTTP_PORT;
58
+ const value = Number(raw);
59
+ if (!Number.isInteger(value) || value < 1 || value > 65_535) {
60
+ throw new Error('KLOUDY_MCP_HTTP_PORT must be an integer between 1 and 65535');
61
+ }
62
+ return value;
63
+ }
64
+ function parseRequiredUrl(raw, variableName) {
65
+ if (!raw)
66
+ throw new Error(`${variableName} is required for serve-http`);
67
+ return parseUrl(raw, variableName, true);
68
+ }
69
+ function parseUrl(raw, variableName, allowLocalHttp) {
70
+ let url;
71
+ try {
72
+ url = new URL(raw);
73
+ }
74
+ catch {
75
+ throw new Error(`${variableName} must be a valid URL`);
76
+ }
77
+ if (url.username || url.password)
78
+ throw new Error(`${variableName} must not contain credentials`);
79
+ if (url.protocol !== 'https:' && !(allowLocalHttp && url.protocol === 'http:' && isLoopback(url.hostname))) {
80
+ throw new Error(`${variableName} must use HTTPS${allowLocalHttp ? ' or loopback HTTP' : ''}`);
81
+ }
82
+ return url;
83
+ }
84
+ function parseSecret(raw, variableName) {
85
+ const parsed = serviceTokenSchema.safeParse(raw);
86
+ if (!parsed.success)
87
+ throw new Error(`${variableName} must be a valid service credential`);
88
+ return parsed.data;
89
+ }
90
+ function parseTimeout(raw) {
91
+ if (raw === undefined)
92
+ return 5_000;
93
+ const value = Number(raw);
94
+ if (!Number.isInteger(value) || value < 1_000 || value > 30_000) {
95
+ throw new Error('KLOUDY_MCP_AUTH_TIMEOUT_MS must be an integer between 1000 and 30000');
96
+ }
97
+ return value;
98
+ }
99
+ function parseScopes(raw) {
100
+ const values = raw === undefined ? [...DEFAULT_SCOPES] : raw.split(/\s+/).filter(Boolean);
101
+ if (values.length === 0 || values.length > 32) {
102
+ throw new Error('KLOUDY_MCP_REQUIRED_SCOPES must contain between 1 and 32 scopes');
103
+ }
104
+ const parsed = z.array(scopeSchema).safeParse(unique(values));
105
+ if (!parsed.success)
106
+ throw new Error('KLOUDY_MCP_REQUIRED_SCOPES contains an invalid scope');
107
+ return parsed.data;
108
+ }
109
+ function isLoopback(hostname) {
110
+ return ['127.0.0.1', 'localhost', '::1', '[::1]'].includes(hostname);
111
+ }
112
+ function unique(values) {
113
+ return [...new Set(values)];
114
+ }
115
+ //# sourceMappingURL=http-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-config.js","sourceRoot":"","sources":["../../../src/http-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Server as NodeHttpServer } from 'node:http';
2
+ import type { OAuthTokenVerifier } from '@modelcontextprotocol/sdk/server/auth/provider.js';
3
+ import type { HttpServerConfig } from './http-config.js';
4
+ export interface HttpServerDependencies {
5
+ verifier?: OAuthTokenVerifier;
6
+ }
7
+ export declare function createHttpApp(config: HttpServerConfig, dependencies?: HttpServerDependencies): Express;
8
+ export declare function serializeAuthorizationServerIssuer(url: URL): string;
9
+ export declare function startHttpServer(config: HttpServerConfig): Promise<NodeHttpServer>;
10
+ export declare function installHttpServerShutdown(server: NodeHttpServer, timeoutMs?: number): void;
@@ -0,0 +1,106 @@
1
+ import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
2
+ import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
3
+ import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/sdk/server/auth/router.js';
4
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
5
+ import { createClients } from './clients.js';
6
+ import { HttpAuthContextVerifier, requestStateFromAuthInfo } from './http-auth.js';
7
+ import { createMcpServer } from './mcp-server.js';
8
+ export function createHttpApp(config, dependencies = {}) {
9
+ const app = createMcpExpressApp({ host: config.host, allowedHosts: config.allowedHosts });
10
+ const resourceMetadataUrl = getOAuthProtectedResourceMetadataUrl(config.publicUrl);
11
+ const verifier = dependencies.verifier ?? new HttpAuthContextVerifier(config);
12
+ const requireAuth = requireBearerAuth({
13
+ verifier,
14
+ requiredScopes: config.requiredScopes,
15
+ resourceMetadataUrl,
16
+ });
17
+ app.get('/health/live', (_request, response) => {
18
+ response.json({ status: 'live' });
19
+ });
20
+ app.get('/health/ready', (_request, response) => {
21
+ response.json({ status: 'ready', resource: config.publicUrl.href });
22
+ });
23
+ app.get(new URL(resourceMetadataUrl).pathname, (_request, response) => {
24
+ response.json({
25
+ resource: config.publicUrl.href,
26
+ authorization_servers: [serializeAuthorizationServerIssuer(config.authorizationServerUrl)],
27
+ scopes_supported: config.requiredScopes,
28
+ bearer_methods_supported: ['header'],
29
+ resource_name: 'Kloudy MCP',
30
+ });
31
+ });
32
+ app.post('/mcp', requireAuth, async (request, response) => {
33
+ const stateStore = requestStateFromAuthInfo(request.auth);
34
+ const { api, knowledge } = createClients(stateStore, 'remote-http-mcp');
35
+ const server = createMcpServer(api, knowledge);
36
+ const transport = new StreamableHTTPServerTransport();
37
+ let closed = false;
38
+ const close = async () => {
39
+ if (closed)
40
+ return;
41
+ closed = true;
42
+ await transport.close().catch(() => undefined);
43
+ await server.close().catch(() => undefined);
44
+ };
45
+ response.once('close', () => void close());
46
+ try {
47
+ // SDK 1.30.0's exact-optional declarations make this implementation require a narrow cast,
48
+ // although StreamableHTTPServerTransport implements Transport at runtime and in its declaration.
49
+ await server.connect(transport);
50
+ await transport.handleRequest(request, response, request.body);
51
+ }
52
+ catch {
53
+ if (!response.headersSent) {
54
+ response.status(500).json({
55
+ jsonrpc: '2.0',
56
+ error: { code: -32603, message: 'Internal server error' },
57
+ id: null,
58
+ });
59
+ }
60
+ }
61
+ finally {
62
+ if (response.writableEnded || response.destroyed)
63
+ await close();
64
+ }
65
+ });
66
+ for (const method of ['get', 'delete']) {
67
+ app[method]('/mcp', (_request, response) => {
68
+ response.status(405).json({
69
+ jsonrpc: '2.0',
70
+ error: { code: -32000, message: 'Method not allowed' },
71
+ id: null,
72
+ });
73
+ });
74
+ }
75
+ return app;
76
+ }
77
+ export function serializeAuthorizationServerIssuer(url) {
78
+ return url.pathname === '/' && !url.search && !url.hash ? url.origin : url.href;
79
+ }
80
+ export async function startHttpServer(config) {
81
+ const app = createHttpApp(config);
82
+ return await new Promise((resolve, reject) => {
83
+ const server = app.listen(config.port, config.host);
84
+ server.once('error', reject);
85
+ server.once('listening', () => resolve(server));
86
+ });
87
+ }
88
+ export function installHttpServerShutdown(server, timeoutMs = 60_000) {
89
+ let stopping = false;
90
+ const stop = () => {
91
+ if (stopping)
92
+ return;
93
+ stopping = true;
94
+ const forcedClose = setTimeout(() => server.closeAllConnections(), timeoutMs);
95
+ forcedClose.unref();
96
+ server.close(() => clearTimeout(forcedClose));
97
+ };
98
+ const removeHandlers = () => {
99
+ process.off('SIGINT', stop);
100
+ process.off('SIGTERM', stop);
101
+ };
102
+ process.once('SIGINT', stop);
103
+ process.once('SIGTERM', stop);
104
+ server.once('close', removeHandlers);
105
+ }
106
+ //# sourceMappingURL=http-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../../src/http-server.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export declare const KLOUDY_MCP_VERSION = "0.1.4";
1
+ export declare const KLOUDY_MCP_VERSION = "0.1.6";
2
2
  export declare function getVersionCommandOutput(command: string, args: string[]): string | undefined;
@@ -1,4 +1,4 @@
1
- export const KLOUDY_MCP_VERSION = '0.1.4';
1
+ export const KLOUDY_MCP_VERSION = '0.1.6';
2
2
  const VERSION_COMMANDS = new Set(['version', '--version', '-v']);
3
3
  export function getVersionCommandOutput(command, args) {
4
4
  if (!VERSION_COMMANDS.has(command))
@@ -28,6 +28,9 @@ export class ApiXClient {
28
28
  const existing = await this.stateStore.read();
29
29
  if (existing.deviceId)
30
30
  return existing.deviceId;
31
+ if (this.stateStore.scope === 'request') {
32
+ throw new ApiXError('Request-scoped Kloudy identity is incomplete', 'request_identity_invalid', false);
33
+ }
31
34
  if (!this.deviceRegistration) {
32
35
  this.deviceRegistration = this.registerDevice().finally(() => {
33
36
  this.deviceRegistration = undefined;
@@ -505,6 +508,8 @@ export class ApiXClient {
505
508
  const envelope = await parseEnvelope(response);
506
509
  if (!response.ok || envelope.success === false) {
507
510
  if (envelope.code === 'device_id_invalid' && includeDevice && options.retryInvalidDevice !== false) {
511
+ if (this.stateStore.scope === 'request')
512
+ throw mapApiError(response.status, envelope);
508
513
  await this.stateStore.clearDevice();
509
514
  await this.ensureDevice();
510
515
  return this.requestWithHeaders(path, { ...options, retryInvalidDevice: false });
@@ -514,7 +519,8 @@ export class ApiXClient {
514
519
  Boolean(state.sessionToken)) {
515
520
  // 上游已经明确拒绝当前 User Token。清理账号会话以允许重新授权,
516
521
  // 但保留匿名设备,使公开查询仍可继续使用。
517
- await this.stateStore.clearSession();
522
+ if (this.stateStore.scope === 'local')
523
+ await this.stateStore.clearSession();
518
524
  }
519
525
  throw mapApiError(response.status, envelope);
520
526
  }