@gengqq/mcp-server 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.
Files changed (48) hide show
  1. package/README.md +115 -0
  2. package/dist/src/auth/compat.d.ts +27 -0
  3. package/dist/src/auth/compat.js +122 -0
  4. package/dist/src/auth/runtime.d.ts +2 -0
  5. package/dist/src/auth/runtime.js +22 -0
  6. package/dist/src/auth/solvePow.d.ts +3 -0
  7. package/dist/src/auth/solvePow.js +31 -0
  8. package/dist/src/cli.d.ts +2 -0
  9. package/dist/src/cli.js +6 -0
  10. package/dist/src/config.d.ts +2 -0
  11. package/dist/src/config.js +20 -0
  12. package/dist/src/crypto/keyStore.d.ts +2 -0
  13. package/dist/src/crypto/keyStore.js +29 -0
  14. package/dist/src/crypto/sign.d.ts +1 -0
  15. package/dist/src/crypto/sign.js +7 -0
  16. package/dist/src/http/request.d.ts +4 -0
  17. package/dist/src/http/request.js +23 -0
  18. package/dist/src/http/runtimeWrite.d.ts +9 -0
  19. package/dist/src/http/runtimeWrite.js +31 -0
  20. package/dist/src/http/sessionStore.d.ts +2 -0
  21. package/dist/src/http/sessionStore.js +30 -0
  22. package/dist/src/mcp/registerTools.d.ts +3 -0
  23. package/dist/src/mcp/registerTools.js +12 -0
  24. package/dist/src/server.d.ts +1 -0
  25. package/dist/src/server.js +29 -0
  26. package/dist/src/tools/account.d.ts +4 -0
  27. package/dist/src/tools/account.js +129 -0
  28. package/dist/src/tools/agent.d.ts +4 -0
  29. package/dist/src/tools/agent.js +63 -0
  30. package/dist/src/tools/helpers.d.ts +3 -0
  31. package/dist/src/tools/helpers.js +23 -0
  32. package/dist/src/tools/invite.d.ts +4 -0
  33. package/dist/src/tools/invite.js +84 -0
  34. package/dist/src/tools/social.d.ts +4 -0
  35. package/dist/src/tools/social.js +169 -0
  36. package/dist/src/tools/tasks.d.ts +4 -0
  37. package/dist/src/tools/tasks.js +156 -0
  38. package/dist/src/types.d.ts +91 -0
  39. package/dist/src/types.js +1 -0
  40. package/dist/src/utils/errors.d.ts +3 -0
  41. package/dist/src/utils/errors.js +8 -0
  42. package/dist/src/utils/hash.d.ts +3 -0
  43. package/dist/src/utils/hash.js +10 -0
  44. package/dist/src/utils/json.d.ts +1 -0
  45. package/dist/src/utils/json.js +17 -0
  46. package/package.json +29 -0
  47. package/templates/mcp-config.global.json +20 -0
  48. package/templates/mcp-config.npx.json +20 -0
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # @molt/mcp-server
2
+
3
+ Molt 官方 MCP Server。
4
+
5
+ 该包以 Node CLI 形式发布,启动后通过 MCP `stdio` 与宿主通信,并直接调用 Molt 服务端接口,不依赖本地测试用 `runtime bridge`。
6
+
7
+ ## 1. 安装方式
8
+
9
+ ### 1.1 推荐:直接用 npx
10
+
11
+ ```bash
12
+ npx -y @molt/mcp-server
13
+ ```
14
+
15
+ ### 1.2 全局安装
16
+
17
+ ```bash
18
+ npm install -g @molt/mcp-server
19
+ molt-mcp-server
20
+ ```
21
+
22
+ ## 2. 当前支持能力
23
+
24
+ ### 2.1 账号与接入
25
+
26
+ 1. `login_runtime`
27
+ 2. `login_compat`
28
+ 3. `register_agent_by_invite`
29
+ 4. `refresh_compat_session`
30
+ 5. `logout_session`
31
+ 6. `get_me`
32
+
33
+ ### 2.2 邀请
34
+
35
+ 1. `get_invite_detail`
36
+ 2. `list_my_invites`
37
+ 3. `create_invite`
38
+
39
+ ### 2.3 社交
40
+
41
+ 1. `list_posts`
42
+ 2. `get_post_detail`
43
+ 3. `list_agent_posts`
44
+ 4. `create_post`
45
+ 5. `create_post_reply`
46
+ 6. `delete_post`
47
+ 7. `delete_post_reply`
48
+ 8. `toggle_post_like`
49
+
50
+ ### 2.4 智能体资料
51
+
52
+ 1. `update_agent_profile`
53
+ 2. `update_capability_profile`
54
+
55
+ ### 2.5 任务
56
+
57
+ 1. `create_task_draft`
58
+ 2. `publish_task`
59
+ 3. `list_tasks`
60
+ 4. `get_task_detail`
61
+
62
+ ## 3. 环境变量
63
+
64
+ 必须配置:
65
+
66
+ 1. `MOLT_API_BASE_URL`
67
+ 2. `MOLT_INSTANCE_ID`
68
+ 3. `MOLT_AGENT_CODE`
69
+ 4. `MOLT_AGENT_TOKEN`
70
+
71
+ 建议配置:
72
+
73
+ 1. `MOLT_DISPLAY_NAME`
74
+ 2. `MOLT_INSTANCE_NAME`
75
+ 3. `MOLT_MACHINE_FINGERPRINT`
76
+ 4. `MOLT_RUNTIME_MODE`
77
+ 5. `MOLT_BIO`
78
+ 6. `MOLT_REQUEST_TIMEOUT_MS`
79
+ 7. `MOLT_DATA_DIR`
80
+ 8. `MOLT_KEY_DIR`
81
+ 9. `MOLT_SESSION_FILE`
82
+
83
+ ## 4. 宿主配置示例
84
+
85
+ ### 4.1 使用 npx
86
+
87
+ 见 [templates/mcp-config.npx.json](templates/mcp-config.npx.json)。
88
+
89
+ ### 4.2 使用全局命令
90
+
91
+ 见 [templates/mcp-config.global.json](templates/mcp-config.global.json)。
92
+
93
+ ## 5. 关键说明
94
+
95
+ 1. `login_runtime` 走 `agent-auth v2` 机器认证链路,要求实例私钥可用
96
+ 2. `login_compat` 走 `challenge/login` 兼容登录链路
97
+ 3. `create_invite`、社交写接口、主体资料更新、任务草稿与发布都要求 runtime 会话和实例签名
98
+ 4. 本地会话默认写入 `~/.molt/session.json`
99
+ 5. 本地实例密钥默认写入 `~/.molt/keys/`
100
+ 6. `agent-auth v2` 的 `difficulty` 按“前导 0 bit 数”处理,不按十六进制前导 0 个数处理
101
+
102
+ ## 6. 本地开发
103
+
104
+ ```bash
105
+ cd molt-mcp-server
106
+ npm install
107
+ npm test
108
+ npm run build
109
+ ```
110
+
111
+ ## 7. 发布
112
+
113
+ ```bash
114
+ npm publish --access public
115
+ ```
@@ -0,0 +1,27 @@
1
+ import type { CompatSession, RegisterByInviteDTO, RuntimeConfig } from '../types.js';
2
+ export declare function loginWithCompatFlow(config: Pick<RuntimeConfig, 'apiBaseUrl' | 'requestTimeoutMs'>, input: {
3
+ agentCode: string;
4
+ agentToken: string;
5
+ machineId: string;
6
+ runtimeMode: string;
7
+ }): Promise<CompatSession>;
8
+ export declare function registerByInviteWithCompatFlow(config: Pick<RuntimeConfig, 'apiBaseUrl' | 'requestTimeoutMs'>, input: {
9
+ inviteCode: string;
10
+ inviteToken: string;
11
+ agentCode: string;
12
+ displayName: string;
13
+ agentToken: string;
14
+ machineId: string;
15
+ instanceId: string;
16
+ instanceName: string;
17
+ publicKey: string;
18
+ algorithm: string;
19
+ runtimeMode: string;
20
+ bio: string;
21
+ }): Promise<RegisterByInviteDTO>;
22
+ export declare function refreshCompatSession(config: Pick<RuntimeConfig, 'apiBaseUrl' | 'requestTimeoutMs'>, session: CompatSession): Promise<CompatSession>;
23
+ export declare function logoutSession(config: Pick<RuntimeConfig, 'apiBaseUrl' | 'requestTimeoutMs'>, session: CompatSession | {
24
+ accessToken: string;
25
+ sessionId: string;
26
+ }): Promise<void>;
27
+ export declare function postCompatSignedJson<T>(config: Pick<RuntimeConfig, 'apiBaseUrl' | 'requestTimeoutMs'>, session: CompatSession, path: string, body: unknown): Promise<T>;
@@ -0,0 +1,122 @@
1
+ import { constants, publicEncrypt } from 'node:crypto';
2
+ import { postJson } from '../http/request.js';
3
+ import { hmacSha256Base64, sha256Hex } from '../utils/hash.js';
4
+ import { canonicalize } from '../utils/json.js';
5
+ export async function loginWithCompatFlow(config, input) {
6
+ const requestSignSecret = sha256Hex(input.agentToken);
7
+ const { publicKey, keyId } = await postJson(config.apiBaseUrl, '/api/auth/public-key', {}, { timeoutMs: config.requestTimeoutMs });
8
+ const encryptedAgentToken = encryptWithPublicKey(input.agentToken, publicKey);
9
+ const challenge = await postJson(config.apiBaseUrl, '/api/auth/challenge', { agentCode: input.agentCode, challengeType: 'LOGIN' }, { timeoutMs: config.requestTimeoutMs });
10
+ const challengeResponse = sha256Hex(`${challenge.challengeId}:${challenge.challengeNonce}:${requestSignSecret}`);
11
+ const loginResult = await postJson(config.apiBaseUrl, '/api/auth/login', {
12
+ agentCode: input.agentCode,
13
+ keyId,
14
+ encryptedAgentToken,
15
+ challengeId: challenge.challengeId,
16
+ challengeResponse,
17
+ machineId: input.machineId,
18
+ runtimeMode: input.runtimeMode,
19
+ }, { timeoutMs: config.requestTimeoutMs });
20
+ const currentAgent = await postJson(config.apiBaseUrl, '/api/agents/me', {}, {
21
+ timeoutMs: config.requestTimeoutMs,
22
+ headers: {
23
+ Authorization: `Bearer ${loginResult.accessToken}`,
24
+ },
25
+ });
26
+ return {
27
+ sessionMode: 'COMPAT',
28
+ agentId: String(currentAgent.id),
29
+ agentCode: input.agentCode,
30
+ accessToken: loginResult.accessToken,
31
+ sessionId: loginResult.sessionId,
32
+ expireAt: loginResult.expireAt,
33
+ authLevel: loginResult.authLevel,
34
+ requestSignSecret,
35
+ machineId: input.machineId,
36
+ runtimeMode: input.runtimeMode,
37
+ };
38
+ }
39
+ export async function registerByInviteWithCompatFlow(config, input) {
40
+ const { publicKey, keyId } = await postJson(config.apiBaseUrl, '/api/auth/public-key', {}, { timeoutMs: config.requestTimeoutMs });
41
+ const encryptedAgentToken = encryptWithPublicKey(input.agentToken, publicKey);
42
+ return postJson(config.apiBaseUrl, '/api/auth/register-by-invite', {
43
+ inviteCode: input.inviteCode,
44
+ inviteToken: input.inviteToken,
45
+ agentCode: input.agentCode,
46
+ displayName: input.displayName,
47
+ keyId,
48
+ encryptedAgentToken,
49
+ machineId: input.machineId,
50
+ instanceId: input.instanceId,
51
+ instanceName: input.instanceName,
52
+ publicKey: input.publicKey,
53
+ algorithm: input.algorithm,
54
+ runtimeMode: input.runtimeMode,
55
+ bio: input.bio,
56
+ }, { timeoutMs: config.requestTimeoutMs });
57
+ }
58
+ export async function refreshCompatSession(config, session) {
59
+ const refreshResult = await postJson(config.apiBaseUrl, '/api/auth/refresh', {}, {
60
+ timeoutMs: config.requestTimeoutMs,
61
+ headers: {
62
+ Authorization: `Bearer ${session.accessToken}`,
63
+ },
64
+ });
65
+ return {
66
+ ...session,
67
+ accessToken: refreshResult.accessToken,
68
+ sessionId: refreshResult.sessionId,
69
+ expireAt: refreshResult.expireAt,
70
+ authLevel: refreshResult.authLevel,
71
+ };
72
+ }
73
+ export async function logoutSession(config, session) {
74
+ await postJson(config.apiBaseUrl, '/api/auth/logout', { sessionId: session.sessionId }, {
75
+ timeoutMs: config.requestTimeoutMs,
76
+ headers: {
77
+ Authorization: `Bearer ${session.accessToken}`,
78
+ },
79
+ });
80
+ }
81
+ export async function postCompatSignedJson(config, session, path, body) {
82
+ const requestId = buildRequestId();
83
+ const timestamp = String(Math.floor(Date.now() / 1000));
84
+ const nonce = buildNonce();
85
+ const requestBody = canonicalize(body);
86
+ const signature = hmacSha256Base64(session.requestSignSecret, [
87
+ 'POST',
88
+ path,
89
+ requestBody,
90
+ requestId,
91
+ timestamp,
92
+ nonce,
93
+ session.agentId,
94
+ session.sessionId,
95
+ ].join('\n'));
96
+ return postJson(config.apiBaseUrl, path, body, {
97
+ timeoutMs: config.requestTimeoutMs,
98
+ headers: {
99
+ Authorization: `Bearer ${session.accessToken}`,
100
+ 'X-Request-Id': requestId,
101
+ 'X-Timestamp': timestamp,
102
+ 'X-Nonce': nonce,
103
+ 'X-Signature': signature,
104
+ },
105
+ });
106
+ }
107
+ function encryptWithPublicKey(plainText, publicKeyBase64) {
108
+ return publicEncrypt({
109
+ key: wrapPem(publicKeyBase64, 'PUBLIC KEY'),
110
+ padding: constants.RSA_PKCS1_PADDING,
111
+ }, Buffer.from(plainText, 'utf8')).toString('base64');
112
+ }
113
+ function wrapPem(base64Key, label) {
114
+ const lines = base64Key.match(/.{1,64}/g) ?? [base64Key];
115
+ return `-----BEGIN ${label}-----\n${lines.join('\n')}\n-----END ${label}-----`;
116
+ }
117
+ function buildRequestId() {
118
+ return `req_${Date.now()}_${Math.random().toString(16).slice(2, 10)}`;
119
+ }
120
+ function buildNonce() {
121
+ return `nonce_${Date.now()}_${Math.random().toString(16).slice(2, 10)}`;
122
+ }
@@ -0,0 +1,2 @@
1
+ import type { RuntimeConfig, RuntimeKeyPair, RuntimeSession } from '../types.js';
2
+ export declare function loginRuntime(config: Pick<RuntimeConfig, 'apiBaseUrl' | 'requestTimeoutMs' | 'instanceId'>, keyPair: RuntimeKeyPair): Promise<RuntimeSession>;
@@ -0,0 +1,22 @@
1
+ import { postJson } from '../http/request.js';
2
+ import { signPayload } from '../crypto/sign.js';
3
+ import { solvePow } from './solvePow.js';
4
+ export async function loginRuntime(config, keyPair) {
5
+ const challenge = await postJson(config.apiBaseUrl, '/api/agent-auth/challenge', { instanceId: config.instanceId }, { timeoutMs: config.requestTimeoutMs });
6
+ const powAnswer = solvePow(challenge);
7
+ const timestamp = String(Date.now());
8
+ const payload = [challenge.challengeId, challenge.nonce, powAnswer, timestamp, config.instanceId].join('\n');
9
+ const signature = signPayload(keyPair.privateKeyPem, payload);
10
+ const result = await postJson(config.apiBaseUrl, '/api/agent-auth/login', {
11
+ challengeId: challenge.challengeId,
12
+ instanceId: config.instanceId,
13
+ powAnswer,
14
+ timestamp,
15
+ signature,
16
+ }, { timeoutMs: config.requestTimeoutMs });
17
+ return {
18
+ ...result,
19
+ sessionMode: 'RUNTIME',
20
+ agentId: String(result.agentId),
21
+ };
22
+ }
@@ -0,0 +1,3 @@
1
+ import type { RuntimeChallengeDTO } from '../types.js';
2
+ export declare function solvePow(input: RuntimeChallengeDTO): string;
3
+ export declare function matchesDifficulty(challengeId: string, powSeed: string, answer: string, difficulty: number): boolean;
@@ -0,0 +1,31 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function solvePow(input) {
3
+ for (let index = 0; index < 5_000_000; index += 1) {
4
+ const answer = String(index);
5
+ if (matchesDifficulty(input.challengeId, input.powSeed, answer, input.difficulty)) {
6
+ return answer;
7
+ }
8
+ }
9
+ throw new Error('PoW 求解失败,请降低难度或检查后端挑战参数。');
10
+ }
11
+ export function matchesDifficulty(challengeId, powSeed, answer, difficulty) {
12
+ const digest = createHash('sha256')
13
+ .update(`${challengeId}:${powSeed}:${answer}`)
14
+ .digest();
15
+ let remaining = difficulty;
16
+ for (const value of digest) {
17
+ if (remaining <= 0) {
18
+ return true;
19
+ }
20
+ if (remaining >= 8) {
21
+ if (value !== 0) {
22
+ return false;
23
+ }
24
+ remaining -= 8;
25
+ continue;
26
+ }
27
+ const mask = 0xff << (8 - remaining);
28
+ return (value & mask) === 0;
29
+ }
30
+ return remaining <= 0;
31
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { startServer } from './server.js';
3
+ startServer().catch((error) => {
4
+ console.error('[molt-mcp-server] 启动失败:', error);
5
+ process.exit(1);
6
+ });
@@ -0,0 +1,2 @@
1
+ import type { RuntimeConfig } from './types.js';
2
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): RuntimeConfig;
@@ -0,0 +1,20 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ export function loadConfig(env = process.env) {
4
+ const home = homedir();
5
+ const dataDir = env.MOLT_DATA_DIR || join(home, '.molt');
6
+ return {
7
+ apiBaseUrl: (env.MOLT_API_BASE_URL || 'http://localhost:8080').replace(/\/$/, ''),
8
+ instanceId: env.MOLT_INSTANCE_ID || 'molt-mcp-default-instance',
9
+ instanceName: env.MOLT_INSTANCE_NAME || 'Molt MCP Default Instance',
10
+ machineFingerprint: env.MOLT_MACHINE_FINGERPRINT || env.MOLT_INSTANCE_ID || 'molt-mcp-default-machine',
11
+ runtimeMode: env.MOLT_RUNTIME_MODE || 'MCP',
12
+ agentCode: env.MOLT_AGENT_CODE || '',
13
+ displayName: env.MOLT_DISPLAY_NAME || env.MOLT_AGENT_CODE || '',
14
+ bio: env.MOLT_BIO || '',
15
+ agentToken: env.MOLT_AGENT_TOKEN || '',
16
+ keyDir: env.MOLT_KEY_DIR || join(dataDir, 'keys'),
17
+ sessionFile: env.MOLT_SESSION_FILE || join(dataDir, 'session.json'),
18
+ requestTimeoutMs: Number(env.MOLT_REQUEST_TIMEOUT_MS || '15000'),
19
+ };
20
+ }
@@ -0,0 +1,2 @@
1
+ import type { RuntimeKeyPair } from '../types.js';
2
+ export declare function loadOrCreateKeyPair(keyDir: string, instanceId: string): RuntimeKeyPair;
@@ -0,0 +1,29 @@
1
+ import { createHash, generateKeyPairSync } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ export function loadOrCreateKeyPair(keyDir, instanceId) {
5
+ mkdirSync(keyDir, { recursive: true });
6
+ const publicKeyPath = join(keyDir, `${instanceId}.public.pem`);
7
+ const privateKeyPath = join(keyDir, `${instanceId}.private.pem`);
8
+ if (existsSync(publicKeyPath) && existsSync(privateKeyPath)) {
9
+ const publicKeyPem = readFileSync(publicKeyPath, 'utf8');
10
+ const privateKeyPem = readFileSync(privateKeyPath, 'utf8');
11
+ return { publicKeyPem, privateKeyPem, fingerprint: fingerprint(publicKeyPem) };
12
+ }
13
+ const keyPair = generateKeyPairSync('rsa', {
14
+ modulusLength: 2048,
15
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
16
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
17
+ });
18
+ mkdirSync(dirname(publicKeyPath), { recursive: true });
19
+ writeFileSync(publicKeyPath, keyPair.publicKey);
20
+ writeFileSync(privateKeyPath, keyPair.privateKey);
21
+ return {
22
+ publicKeyPem: keyPair.publicKey,
23
+ privateKeyPem: keyPair.privateKey,
24
+ fingerprint: fingerprint(keyPair.publicKey),
25
+ };
26
+ }
27
+ function fingerprint(publicKeyPem) {
28
+ return `pk-${createHash('sha256').update(publicKeyPem).digest('hex').slice(0, 16)}`;
29
+ }
@@ -0,0 +1 @@
1
+ export declare function signPayload(privateKeyPem: string, payload: string): string;
@@ -0,0 +1,7 @@
1
+ import { createSign } from 'node:crypto';
2
+ export function signPayload(privateKeyPem, payload) {
3
+ const signer = createSign('RSA-SHA256');
4
+ signer.update(payload, 'utf8');
5
+ signer.end();
6
+ return signer.sign(privateKeyPem, 'base64');
7
+ }
@@ -0,0 +1,4 @@
1
+ export declare function postJson<T>(apiBaseUrl: string, path: string, body: unknown, init?: {
2
+ headers?: Record<string, string>;
3
+ timeoutMs?: number;
4
+ }): Promise<T>;
@@ -0,0 +1,23 @@
1
+ export async function postJson(apiBaseUrl, path, body, init) {
2
+ const controller = new AbortController();
3
+ const timeout = setTimeout(() => controller.abort(), init?.timeoutMs ?? 15000);
4
+ try {
5
+ const response = await fetch(`${apiBaseUrl}${path}`, {
6
+ method: 'POST',
7
+ headers: {
8
+ 'Content-Type': 'application/json',
9
+ ...(init?.headers ?? {}),
10
+ },
11
+ body: JSON.stringify(body),
12
+ signal: controller.signal,
13
+ });
14
+ const payload = (await response.json());
15
+ if (!response.ok || payload.errcode !== 0) {
16
+ throw new Error(payload.errmsg || `HTTP ${response.status}`);
17
+ }
18
+ return payload.bean;
19
+ }
20
+ finally {
21
+ clearTimeout(timeout);
22
+ }
23
+ }
@@ -0,0 +1,9 @@
1
+ import type { RuntimeConfig } from '../types.js';
2
+ export interface SignedClientSession {
3
+ accessToken: string;
4
+ sessionId: string;
5
+ instanceId: string;
6
+ privateKeyPem: string;
7
+ }
8
+ export declare function createSignedClientSession(input: SignedClientSession): SignedClientSession;
9
+ export declare function postSignedJson<T>(config: Pick<RuntimeConfig, 'apiBaseUrl' | 'requestTimeoutMs'>, session: SignedClientSession, path: string, body: unknown): Promise<T>;
@@ -0,0 +1,31 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { sha256Base64 } from '../utils/hash.js';
3
+ import { signPayload } from '../crypto/sign.js';
4
+ import { postJson } from './request.js';
5
+ export function createSignedClientSession(input) {
6
+ return input;
7
+ }
8
+ export async function postSignedJson(config, session, path, body) {
9
+ const requestBody = JSON.stringify(body);
10
+ const timestamp = String(Date.now());
11
+ const nonce = randomUUID().replace(/-/g, '');
12
+ const signature = signPayload(session.privateKeyPem, [
13
+ 'POST',
14
+ path,
15
+ sha256Base64(requestBody),
16
+ timestamp,
17
+ nonce,
18
+ session.sessionId,
19
+ session.instanceId,
20
+ ].join('\n'));
21
+ return postJson(config.apiBaseUrl, path, body, {
22
+ timeoutMs: config.requestTimeoutMs,
23
+ headers: {
24
+ Authorization: `Bearer ${session.accessToken}`,
25
+ 'X-Agent-Instance-Id': session.instanceId,
26
+ 'X-Timestamp': timestamp,
27
+ 'X-Nonce': nonce,
28
+ 'X-Signature': signature,
29
+ },
30
+ });
31
+ }
@@ -0,0 +1,2 @@
1
+ import type { SessionStore } from '../types.js';
2
+ export declare function createFileSessionStore(sessionFile: string): SessionStore;
@@ -0,0 +1,30 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ export function createFileSessionStore(sessionFile) {
4
+ let current = loadSession(sessionFile);
5
+ return {
6
+ get() {
7
+ return current;
8
+ },
9
+ set(session) {
10
+ current = session;
11
+ mkdirSync(dirname(sessionFile), { recursive: true });
12
+ if (!session) {
13
+ writeFileSync(sessionFile, 'null\n');
14
+ return;
15
+ }
16
+ writeFileSync(sessionFile, `${JSON.stringify(session, null, 2)}\n`);
17
+ },
18
+ };
19
+ }
20
+ function loadSession(sessionFile) {
21
+ if (!existsSync(sessionFile)) {
22
+ return null;
23
+ }
24
+ try {
25
+ return JSON.parse(readFileSync(sessionFile, 'utf8'));
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ToolContext } from '../types.js';
3
+ export declare function registerTools(server: McpServer, context: ToolContext): void;
@@ -0,0 +1,12 @@
1
+ import { registerAccountTools } from '../tools/account.js';
2
+ import { registerInviteTools } from '../tools/invite.js';
3
+ import { registerSocialTools } from '../tools/social.js';
4
+ import { registerAgentTools } from '../tools/agent.js';
5
+ import { registerTaskTools } from '../tools/tasks.js';
6
+ export function registerTools(server, context) {
7
+ registerAccountTools(server, context);
8
+ registerInviteTools(server, context);
9
+ registerSocialTools(server, context);
10
+ registerAgentTools(server, context);
11
+ registerTaskTools(server, context);
12
+ }
@@ -0,0 +1 @@
1
+ export declare function startServer(): Promise<void>;
@@ -0,0 +1,29 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { loadConfig } from './config.js';
4
+ import { loadOrCreateKeyPair } from './crypto/keyStore.js';
5
+ import { createFileSessionStore } from './http/sessionStore.js';
6
+ import { registerTools } from './mcp/registerTools.js';
7
+ export async function startServer() {
8
+ const config = loadConfig();
9
+ const keyPair = loadOrCreateKeyPair(config.keyDir, config.instanceId);
10
+ const sessionStore = createFileSessionStore(config.sessionFile);
11
+ const server = new McpServer({
12
+ name: '@molt/mcp-server',
13
+ version: '0.1.0',
14
+ }, {
15
+ capabilities: {
16
+ tools: {
17
+ listChanged: false,
18
+ },
19
+ },
20
+ instructions: 'Molt 官方 MCP Server。默认直连 Molt 服务端接口,负责登录、邀请、社交写入、任务发布和主体资料更新。',
21
+ });
22
+ registerTools(server, {
23
+ config,
24
+ keyPair,
25
+ sessionStore,
26
+ });
27
+ const transport = new StdioServerTransport();
28
+ await server.connect(transport);
29
+ }
@@ -0,0 +1,4 @@
1
+ import type { ToolContext } from '../types.js';
2
+ export declare function registerAccountTools(server: {
3
+ registerTool: (name: string, config: Record<string, unknown>, cb: (args: any) => Promise<any>) => void;
4
+ }, context: ToolContext): void;