@bigbrain-work/mcp-connect 1.2.0 → 1.2.2
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 +8 -1
- package/package.json +5 -1
- package/src/arguments.js +5 -1
- package/src/cli.js +33 -5
- package/src/configurators.js +54 -1
- package/src/constants.js +1 -1
- package/src/proxy.js +92 -0
- package/src/remote-client.js +20 -0
package/README.md
CHANGED
|
@@ -29,7 +29,13 @@ npx -y @bigbrain-work/mcp-connect --agent openclaw
|
|
|
29
29
|
npx -y @bigbrain-work/mcp-connect --agent workbuddy
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
对于尚未内置配置适配器的 Agent
|
|
32
|
+
对于尚未内置配置适配器的 Agent,安装器不会猜测其私有配置文件,而是输出一份标准 stdio MCP 配置。该配置统一运行:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx -y @bigbrain-work/mcp-connect proxy
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
桥接器从本机安全存储读取 `SHILIU_AI_API_KEY`,再连接远程 HTTP MCP。这样 WorkBuddy、OpenClaw 以及后续其他支持 stdio MCP 的 Agent 不需要分别开发认证头适配器。支持远程 HTTP MCP 和自定义认证头的客户端仍可直接连接服务地址。
|
|
33
39
|
|
|
34
40
|
安装器会隐藏输入 API Key。配置完成后,重启对应客户端并检查连接:
|
|
35
41
|
|
|
@@ -46,6 +52,7 @@ npx -y @bigbrain-work/mcp-connect status
|
|
|
46
52
|
- Windows 上,安装器通过标准输入将 API Key 写入当前用户环境变量,避免进入终端历史和子进程命令行。
|
|
47
53
|
- macOS/Linux 上,API Key 保存在权限为 `0600` 的 `~/.config/shiliu-ai/env`,并由用户登录配置加载。
|
|
48
54
|
- `status` 命令只显示“已设置/未设置”,不会输出 API Key。
|
|
55
|
+
- 通用 `proxy` 的 stdout 只承载 MCP 协议数据,不会输出 API Key。
|
|
49
56
|
|
|
50
57
|
## 服务信息
|
|
51
58
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bigbrain-work/mcp-connect",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "石榴 AI MCP 智能配置工具,支持自动检测、通用 HTTP MCP 接入和本地 AI 剪辑工具安装。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,5 +28,9 @@
|
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
33
|
+
"zod": "^3.25.0"
|
|
34
|
+
},
|
|
31
35
|
"license": "UNLICENSED"
|
|
32
36
|
}
|
package/src/arguments.js
CHANGED
|
@@ -17,6 +17,9 @@ export function parseCliArguments(args) {
|
|
|
17
17
|
home: {
|
|
18
18
|
type: 'string',
|
|
19
19
|
},
|
|
20
|
+
url: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
},
|
|
20
23
|
help: {
|
|
21
24
|
type: 'boolean',
|
|
22
25
|
short: 'h',
|
|
@@ -31,7 +34,7 @@ export function parseCliArguments(args) {
|
|
|
31
34
|
})
|
|
32
35
|
|
|
33
36
|
const command = parsed.positionals[0] || 'install'
|
|
34
|
-
if (!['install', 'status'].includes(command)) {
|
|
37
|
+
if (!['install', 'proxy', 'status'].includes(command)) {
|
|
35
38
|
throw new Error(`不支持的命令:${command}`)
|
|
36
39
|
}
|
|
37
40
|
|
|
@@ -45,6 +48,7 @@ export function parseCliArguments(args) {
|
|
|
45
48
|
agent,
|
|
46
49
|
dryRun: parsed.values['dry-run'],
|
|
47
50
|
home: parsed.values.home,
|
|
51
|
+
url: parsed.values.url,
|
|
48
52
|
help: parsed.values.help,
|
|
49
53
|
version: parsed.values.version,
|
|
50
54
|
}
|
package/src/cli.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
SUPPORTED_AGENTS,
|
|
14
14
|
} from './constants.js'
|
|
15
15
|
import { persistApiKey, resolveApiKey } from './credentials.js'
|
|
16
|
+
import { runProxy } from './proxy.js'
|
|
16
17
|
import { printStatus } from './status.js'
|
|
17
18
|
|
|
18
19
|
function printHelp() {
|
|
@@ -21,12 +22,14 @@ function printHelp() {
|
|
|
21
22
|
用法:
|
|
22
23
|
mcp-connect
|
|
23
24
|
mcp-connect --agent <name>
|
|
25
|
+
mcp-connect proxy
|
|
24
26
|
mcp-connect status
|
|
25
27
|
|
|
26
28
|
选项:
|
|
27
29
|
-a, --agent <name> 配置指定客户端
|
|
28
30
|
--dry-run 只显示将执行的操作
|
|
29
31
|
--home <path> 指定用户目录(主要用于测试)
|
|
32
|
+
--url <url> proxy 使用的远程 MCP 地址
|
|
30
33
|
-h, --help 显示帮助
|
|
31
34
|
-v, --version 显示版本
|
|
32
35
|
|
|
@@ -36,15 +39,36 @@ function printHelp() {
|
|
|
36
39
|
|
|
37
40
|
智能配置:
|
|
38
41
|
不指定 --agent 时,自动检测并配置已安装的 ${SUPPORTED_AGENTS.join('、')}。
|
|
39
|
-
其他 Agent 名称也可传入;未内置适配器时会输出通用
|
|
42
|
+
其他 Agent 名称也可传入;未内置适配器时会输出通用 stdio MCP 配置。
|
|
43
|
+
stdio 代理会从本机安全存储读取 API Key,再转发到远程 HTTP MCP。`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function stdioProxyDefinition(platform = process.platform) {
|
|
47
|
+
if (platform === 'win32') {
|
|
48
|
+
return {
|
|
49
|
+
command: 'cmd',
|
|
50
|
+
args: ['/d', '/s', '/c', 'npx', '-y', PACKAGE_NAME, 'proxy'],
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
command: 'npx',
|
|
55
|
+
args: ['-y', PACKAGE_NAME, 'proxy'],
|
|
56
|
+
}
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
function printGenericInstructions(agent, { dryRun = false } = {}) {
|
|
43
|
-
|
|
60
|
+
const proxy = stdioProxyDefinition()
|
|
61
|
+
const config = {
|
|
62
|
+
mcpServers: {
|
|
63
|
+
[SERVER_NAME]: proxy,
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
console.log(`暂未内置 ${agent} 的自动配置适配器。请在该 Agent 中添加下面这份标准 stdio MCP 配置:
|
|
67
|
+
|
|
68
|
+
${JSON.stringify(config, null, 2)}
|
|
44
69
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
认证头:Authorization: Bearer <你的石榴 AI API Key>
|
|
70
|
+
该命令会在本机启动通用桥接器,由桥接器连接:${MCP_URL}
|
|
71
|
+
Agent 配置中不需要填写 Authorization 请求头,也不保存真实 API Key。
|
|
48
72
|
|
|
49
73
|
${dryRun ? `演练模式未保存 API Key;正式运行时会安全保存到 ${API_KEY_ENV}。` : `API Key 已安全保存到 ${API_KEY_ENV}。`}请勿将真实 API Key 写入项目代码或提交到 Git。`)
|
|
50
74
|
}
|
|
@@ -61,6 +85,10 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
61
85
|
}
|
|
62
86
|
|
|
63
87
|
const home = path.resolve(options.home || os.homedir())
|
|
88
|
+
if (options.command === 'proxy') {
|
|
89
|
+
await runProxy({ home, url: options.url || MCP_URL })
|
|
90
|
+
return
|
|
91
|
+
}
|
|
64
92
|
if (options.command === 'status') {
|
|
65
93
|
const result = await printStatus({ home })
|
|
66
94
|
if (!result.remote.ok) {
|
package/src/configurators.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
1
2
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import { spawnSync } from 'node:child_process'
|
|
@@ -42,13 +43,65 @@ function commandText(command, args) {
|
|
|
42
43
|
)).join(' ')
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
export function commandInvocation(command, args, {
|
|
47
|
+
platform = process.platform,
|
|
48
|
+
env = process.env,
|
|
49
|
+
fileExists = existsSync,
|
|
50
|
+
nodeExecutable = process.execPath,
|
|
51
|
+
} = {}) {
|
|
52
|
+
if (platform !== 'win32') {
|
|
53
|
+
return { command, args }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const searchPath = env.PATH || env.Path || ''
|
|
57
|
+
const npmDirectory = searchPath
|
|
58
|
+
.split(';')
|
|
59
|
+
.map((entry) => entry.trim().replace(/^"|"$/gu, ''))
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.find((entry) => fileExists(path.join(entry, `${command}.cmd`)))
|
|
62
|
+
|
|
63
|
+
if (!npmDirectory) {
|
|
64
|
+
return { command, args }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (command === 'claude') {
|
|
68
|
+
const executable = path.join(
|
|
69
|
+
npmDirectory,
|
|
70
|
+
'node_modules',
|
|
71
|
+
'@anthropic-ai',
|
|
72
|
+
'claude-code',
|
|
73
|
+
'bin',
|
|
74
|
+
'claude.exe',
|
|
75
|
+
)
|
|
76
|
+
if (fileExists(executable)) {
|
|
77
|
+
return { command: executable, args }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (command === 'codex') {
|
|
81
|
+
const script = path.join(
|
|
82
|
+
npmDirectory,
|
|
83
|
+
'node_modules',
|
|
84
|
+
'@openai',
|
|
85
|
+
'codex',
|
|
86
|
+
'bin',
|
|
87
|
+
'codex.js',
|
|
88
|
+
)
|
|
89
|
+
if (fileExists(script)) {
|
|
90
|
+
return { command: nodeExecutable, args: [script, ...args] }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { command, args }
|
|
95
|
+
}
|
|
96
|
+
|
|
45
97
|
function execute(command, args, { dryRun = false } = {}) {
|
|
46
98
|
if (dryRun) {
|
|
47
99
|
console.log(`[dry-run] ${commandText(command, args)}`)
|
|
48
100
|
return
|
|
49
101
|
}
|
|
50
102
|
|
|
51
|
-
const
|
|
103
|
+
const invocation = commandInvocation(command, args)
|
|
104
|
+
const result = spawnSync(invocation.command, invocation.args, {
|
|
52
105
|
encoding: 'utf8',
|
|
53
106
|
windowsHide: true,
|
|
54
107
|
shell: false,
|
package/src/constants.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const PACKAGE_NAME = '@bigbrain-work/mcp-connect'
|
|
2
|
-
export const PACKAGE_VERSION = '1.2.
|
|
2
|
+
export const PACKAGE_VERSION = '1.2.2'
|
|
3
3
|
export const SERVER_NAME = 'shiliu_mcp'
|
|
4
4
|
export const MCP_URL = 'https://api.bigbrain.work/shiliu/mcp'
|
|
5
5
|
export const API_KEY_ENV = 'SHILIU_AI_API_KEY'
|
package/src/proxy.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
3
|
+
import {
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
GetPromptRequestSchema,
|
|
6
|
+
ListPromptsRequestSchema,
|
|
7
|
+
ListResourcesRequestSchema,
|
|
8
|
+
ListResourceTemplatesRequestSchema,
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
ReadResourceRequestSchema,
|
|
11
|
+
} from '@modelcontextprotocol/sdk/types.js'
|
|
12
|
+
|
|
13
|
+
import { API_KEY_ENV, PACKAGE_VERSION } from './constants.js'
|
|
14
|
+
import { readPersistedApiKey } from './credentials.js'
|
|
15
|
+
import { connectRemote } from './remote-client.js'
|
|
16
|
+
|
|
17
|
+
function resolveProxyApiKey({ home, platform = process.platform, env = process.env }) {
|
|
18
|
+
const apiKey = readPersistedApiKey({ home, platform, env })?.trim()
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
throw new Error(`未找到 API Key,请先运行 mcp-connect 安装,或设置 ${API_KEY_ENV}`)
|
|
21
|
+
}
|
|
22
|
+
return apiKey
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function runProxy({
|
|
26
|
+
home,
|
|
27
|
+
url,
|
|
28
|
+
platform = process.platform,
|
|
29
|
+
env = process.env,
|
|
30
|
+
} = {}) {
|
|
31
|
+
const apiKey = resolveProxyApiKey({ home, platform, env })
|
|
32
|
+
const remote = await connectRemote({ apiKey, url })
|
|
33
|
+
const remoteCapabilities = remote.getServerCapabilities() ?? {}
|
|
34
|
+
const capabilities = {}
|
|
35
|
+
if (remoteCapabilities.tools) capabilities.tools = {}
|
|
36
|
+
if (remoteCapabilities.resources) capabilities.resources = {}
|
|
37
|
+
if (remoteCapabilities.prompts) capabilities.prompts = {}
|
|
38
|
+
|
|
39
|
+
const server = new Server(
|
|
40
|
+
{
|
|
41
|
+
name: 'shiliu-ai-mcp-proxy',
|
|
42
|
+
version: PACKAGE_VERSION,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
capabilities,
|
|
46
|
+
instructions: 'Authenticated stdio bridge for the Shiliu AI remote MCP service.',
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if (remoteCapabilities.tools) {
|
|
51
|
+
server.setRequestHandler(ListToolsRequestSchema, (request) => (
|
|
52
|
+
remote.listTools(request.params)
|
|
53
|
+
))
|
|
54
|
+
server.setRequestHandler(CallToolRequestSchema, (request) => (
|
|
55
|
+
remote.callTool(request.params)
|
|
56
|
+
))
|
|
57
|
+
}
|
|
58
|
+
if (remoteCapabilities.resources) {
|
|
59
|
+
server.setRequestHandler(ListResourcesRequestSchema, (request) => (
|
|
60
|
+
remote.listResources(request.params)
|
|
61
|
+
))
|
|
62
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, (request) => (
|
|
63
|
+
remote.listResourceTemplates(request.params)
|
|
64
|
+
))
|
|
65
|
+
server.setRequestHandler(ReadResourceRequestSchema, (request) => (
|
|
66
|
+
remote.readResource(request.params)
|
|
67
|
+
))
|
|
68
|
+
}
|
|
69
|
+
if (remoteCapabilities.prompts) {
|
|
70
|
+
server.setRequestHandler(ListPromptsRequestSchema, (request) => (
|
|
71
|
+
remote.listPrompts(request.params)
|
|
72
|
+
))
|
|
73
|
+
server.setRequestHandler(GetPromptRequestSchema, (request) => (
|
|
74
|
+
remote.getPrompt(request.params)
|
|
75
|
+
))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let closing = false
|
|
79
|
+
const shutdown = async () => {
|
|
80
|
+
if (closing) return
|
|
81
|
+
closing = true
|
|
82
|
+
await Promise.allSettled([server.close(), remote.close()])
|
|
83
|
+
}
|
|
84
|
+
process.once('SIGINT', shutdown)
|
|
85
|
+
process.once('SIGTERM', shutdown)
|
|
86
|
+
try {
|
|
87
|
+
await server.connect(new StdioServerTransport())
|
|
88
|
+
} catch (error) {
|
|
89
|
+
await shutdown()
|
|
90
|
+
throw error
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
2
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
3
|
+
|
|
4
|
+
import { PACKAGE_VERSION } from './constants.js'
|
|
5
|
+
|
|
6
|
+
export async function connectRemote({ apiKey, url }) {
|
|
7
|
+
const client = new Client({
|
|
8
|
+
name: 'shiliu-ai-mcp-connect',
|
|
9
|
+
version: PACKAGE_VERSION,
|
|
10
|
+
})
|
|
11
|
+
const transport = new StreamableHTTPClientTransport(new URL(url), {
|
|
12
|
+
requestInit: {
|
|
13
|
+
headers: {
|
|
14
|
+
Authorization: `Bearer ${apiKey}`,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
await client.connect(transport)
|
|
19
|
+
return client
|
|
20
|
+
}
|