Claude Desktop App 连接本地 LLM 完整配置指南
环境:macOS (Apple Silicon)、Claude Desktop App、LM Studio、Claude Code Router (ccr)
最后更新:2026-04-26
1. 架构概览
Claude Desktop App
│
▼ HTTP (127.0.0.1:3457)
ccr-compat-proxy (补充 /v1/models 端点 + 剥离 attribution header)
│
▼ HTTP (127.0.0.1:3456)
Claude Code Router (ccr) — 协议转换 + 路由
│
▼ HTTP (192.168.31.127:1234/v1/chat/completions)
LM Studio (本地 LLM 推理服务)
为什么需要三层?
| 组件 | 作用 | 为什么不能省 |
|---|---|---|
| ccr | 将 Anthropic Messages API 格式转换为 OpenAI Chat Completions 格式 | Claude Desktop 只发送 /v1/messages 格式,本地模型只接受 /v1/chat/completions |
| ccr-compat-proxy | 补充 /v1/models 端点 + 剥离 x-anthropic-billing-header | Claude Desktop 启动时会请求 /v1/models,ccr 没有这个端点会返回 404 |
| LM Studio | 运行本地模型,提供 OpenAI 兼容 API | 实际的 LLM 推理引擎 |
2. 前置条件
- LM Studio 已安装并在远程/本机运行,开启了 Server 模式
- Node.js (v18+) 已安装
- Claude Desktop App 已安装
3. 安装 Claude Code Router (ccr)
3.1 独立安装(推荐)
将 ccr 安装到用户目录,避免被其他应用升级覆盖:
# 安装到 ~/.npm-global/
npm install -g @musistudio/claude-code-router --prefix ~/.npm-global
# 添加到 PATH(加到 ~/.zshrc)
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
# 验证安装
ccr --version
3.2 创建启动脚本
mkdir -p ~/.local/bin
cat > ~/.local/bin/ccr << 'EOF'
#!/bin/bash
exec /opt/homebrew/bin/node ~/.npm-global/lib/node_modules/@musistudio/claude-code-router/dist/cli.js "$@"
EOF
chmod +x ~/.local/bin/ccr
4. 配置 ccr
4.1 创建配置文件
mkdir -p ~/.claude-code-router
编辑 ~/.claude-code-router/config.json:
{
"LOG": true,
"LOG_LEVEL": "info",
"API_TIMEOUT_MS": 600000,
"Providers": [
{
"name": "lmstudio",
"api_base_url": "http://192.168.31.127:1234/v1/chat/completions",
"api_key": "your-lmstudio-api-key",
"models": ["qwen/qwen3.6-35b-a3b"]
}
],
"Router": {
"default": "lmstudio,qwen/qwen3.6-35b-a3b"
}
}
字段说明:
| 字段 | 说明 |
|---|---|
Providers[].api_base_url | LM Studio 的 OpenAI 兼容 API 地址 |
Providers[].api_key | LM Studio 的 Bearer Token(可在 LM Studio Server 设置中配置) |
Providers[].models | LM Studio 中已加载的模型名称(必须与 LM Studio 显示的完全一致) |
Router.default | 格式为 provider名,模型名,指定默认使用的模型 |
API_TIMEOUT_MS | 上游请求超时时间(毫秒),建议 600000(10 分钟) |
切换模型:修改
Providers[].models和Router.default中的模型名,然后重启 ccr。
5. 创建 ccr-compat-proxy
Claude Desktop App 启动时会请求 GET /v1/models,ccr 没有这个端点。同时 Claude Code 客户端会在请求 body 中注入 x-anthropic-billing-header,需要剥离。
5.1 安装依赖
cd ~/.claude-code-router
npm init -y
npm install http-proxy
5.2 创建代理脚本
创建 ~/.claude-code-router/ccr-compat-proxy.js:
#!/usr/bin/env node
const http = require('http');
const CONFIG_PATH = process.env.HOME + '/.claude-code-router/config.json';
const PROXY_PORT = 3457;
const CCR_PORT = 3456;
function loadModels() {
try {
const config = JSON.parse(require('fs').readFileSync(CONFIG_PATH, 'utf-8'));
const models = [];
for (const provider of (config.Providers || [])) {
for (const model of (provider.models || [])) {
models.push({ id: model, object: 'model', created: Date.now(), owned_by: provider.name });
}
}
return models;
} catch (e) {
return [];
}
}
function stripAttributionHeader(bodyStr) {
try {
const body = JSON.parse(bodyStr);
let modified = false;
if (Array.isArray(body.system)) {
body.system = body.system.filter(block => {
if (block.type === 'text' && block.text && block.text.startsWith('x-anthropic-billing-header:')) {
modified = true;
return false;
}
return true;
});
}
if (Array.isArray(body.messages)) {
for (const msg of body.messages) {
if (Array.isArray(msg.content)) {
const before = msg.content.length;
msg.content = msg.content.filter(block => {
if (block.type === 'text' && block.text && block.text.startsWith('x-anthropic-billing-header:')) {
return false;
}
return true;
});
if (msg.content.length !== before) modified = true;
}
}
}
return modified ? JSON.stringify(body) : bodyStr;
} catch (e) {
return bodyStr;
}
}
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url.match(/^\/v1\/models/)) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ object: 'list', data: loadModels() }));
return;
}
let bodyChunks = [];
req.on('data', chunk => bodyChunks.push(chunk));
req.on('end', () => {
const cleaned = stripAttributionHeader(Buffer.concat(bodyChunks).toString('utf-8'));
const buf = Buffer.from(cleaned);
const headers = { ...req.headers, 'content-length': buf.length };
delete headers['transfer-encoding'];
const ccrReq = http.request({
hostname: '127.0.0.1', port: CCR_PORT, path: req.url, method: req.method, headers
}, (ccrRes) => {
res.writeHead(ccrRes.statusCode, ccrRes.headers);
ccrRes.pipe(res);
});
ccrReq.on('error', (err) => {
if (!res.headersSent) res.writeHead(502, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: { message: 'CCR upstream error: ' + err.message } }));
});
res.on('close', () => ccrReq.destroy());
ccrReq.write(buf);
ccrReq.end();
});
});
server.listen(PROXY_PORT, '127.0.0.1', () => {
console.log(`CCR compat proxy listening on http://127.0.0.1:${PROXY_PORT}`);
console.log(`Stripping x-anthropic-billing-header from POST bodies`);
});
6. 配置开机自启动 (launchd)
6.1 ccr 服务
创建 ~/Library/LaunchAgents/com.user.ccr.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.ccr</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/node</string>
<string>/Users/mac/.npm-global/lib/node_modules/@musistudio/claude-code-router/dist/cli.js</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/mac/.claude-code-router</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/mac/.claude-code-router/logs/ccr-stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/mac/.claude-code-router/logs/ccr-stderr.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>HOME</key>
<string>/Users/mac</string>
</dict>
</dict>
</plist>
6.2 ccr-compat-proxy 服务
创建 ~/Library/LaunchAgents/com.user.ccr-proxy.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.ccr-proxy</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/node</string>
<string>/Users/mac/.claude-code-router/ccr-compat-proxy.js</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/mac/.claude-code-router</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/mac/.claude-code-router/logs/proxy-stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/mac/.claude-code-router/logs/proxy-stderr.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/Users/mac/.npm-global/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>HOME</key>
<string>/Users/mac</string>
</dict>
</dict>
</plist>
6.3 启动服务
# 创建日志目录
mkdir -p ~/.claude-code-router/logs
# 加载服务
launchctl load ~/Library/LaunchAgents/com.user.ccr.plist
launchctl load ~/Library/LaunchAgents/com.user.ccr-proxy.plist
# 验证
lsof -i :3456 # ccr 应该在监听
lsof -i :3457 # proxy 应该在监听
7. 配置 Claude Desktop App
编辑 ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"preferences": {
"coworkWebSearchEnabled": true
}
}
在 Claude Desktop App 中:
- 打开设置 (Settings)
- 找到 API Provider 或 Custom API 部分
- 设置 API Base URL 为
http://127.0.0.1:3457 - 输入 LM Studio 的 API Key(如果有的话)
注意:Claude Desktop App 要求 API 地址必须使用
https或http://127.0.0.1,不能使用其他 IP 地址。这就是为什么需要 ccr-compat-proxy 在127.0.0.1:3457监听 — LM Studio 如果运行在远程 IP(如192.168.31.127),不能直接被 Claude Desktop App 引用。
8. 环境变量配置
在 ~/.claude/settings.json 中配置(对 Claude Desktop App 和 CLI 都生效):
{
"permissions": {
"dangerouslySkipPermissions": true
},
"env": {
"CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
"CLAUDE_CODE_ENABLE_TELEMETRY": "0",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
}
}
| 环境变量 | 作用 |
|---|---|
CLAUDE_CODE_ATTRIBUTION_HEADER | 设为 0 尝试关闭 HTTP 层的 attribution header(body 中的需要 proxy 剥离) |
CLAUDE_CODE_ENABLE_TELEMETRY | 设为 0 禁用遥测数据上报 |
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC | 设为 1 禁用非必要的网络请求 |
9. Patch:延长上游请求超时
ccr 使用 Node.js 内置的 undici 作为 HTTP 客户端,bodyTimeout 硬编码为 3e5(5 分钟)。如果本地模型推理较慢,需要 patch 延长超时。
9.1 手动 patch
编辑 ~/.npm-global/lib/node_modules/@musistudio/claude-code-router/dist/cli.js,搜索并替换:
# 将 bodyTimeout 从 5 分钟改为 10 分钟
qe??3e5 → qe??6e5
9.2 自动 patch 脚本
已提供脚本 ~/.claude-code-router/patch-body-timeout.sh:
# 默认 patch 到 600s (10 分钟)
bash ~/.claude-code-router/patch-body-timeout.sh
# 自定义超时
bash ~/.claude-code-router/patch-body-timeout.sh 1200
脚本会自动备份原文件、执行替换、重启 ccr。
注意:ccr 版本更新(
npm update)后此 patch 会丢失,需要重新运行脚本。
10. 目录结构总览
~/.claude-code-router/
├── config.json # ccr 配置(模型、API 地址等)
├── ccr-compat-proxy.js # Claude Desktop 兼容代理
├── patch-body-timeout.sh # bodyTimeout 自动 patch 脚本
├── node_modules/
│ └── http-proxy/ # proxy 依赖
└── logs/
├── ccr-stdout.log # ccr 标准输出
├── ccr-stderr.log # ccr 错误输出
├── ccr-*.log # ccr 运行日志(含请求详情)
├── proxy-stdout.log # proxy 标准输出
└── proxy-stderr.log # proxy 错误输出
~/.npm-global/lib/node_modules/@musistudio/claude-code-router/
└── dist/cli.js # ccr 主程序(已 patch bodyTimeout)
~/.local/bin/
└── ccr # ccr 启动脚本
~/Library/LaunchAgents/
├── com.user.ccr.plist # ccr 开机自启动配置
└── com.user.ccr-proxy.plist # proxy 开机自启动配置
~/.claude/
├── settings.json # Claude Code 通用设置(含环境变量)
└── claude.json # Claude Code 本地缓存(feature flags 等)
11. 常见问题排查
请求发不出去
# 检查服务是否运行
lsof -i :3456 # ccr
lsof -i :3457 # proxy
# 查看日志
tail -50 ~/.claude-code-router/logs/ccr-*.log
tail -50 ~/.claude-code-router/logs/proxy-stderr.log
ccr 日志显示 ERR_STREAM_PREMATURE_CLOSE
proxy 的 stream 处理有问题,确保 ccr-compat-proxy.js 中删除了 transfer-encoding header 并使用 content-length。
切换模型后请求的模型名没变
ccr 缓存了旧配置,需要重启:
launchctl unload ~/Library/LaunchAgents/com.user.ccr.plist
launchctl load ~/Library/LaunchAgents/com.user.ccr.plist
5 分钟超时
undici 的 bodyTimeout 硬编码为 5 分钟,需要 patch cli.js(参见第 9 节)。
x-anthropic-billing-header 仍然出现在请求中
- 确认 ccr-compat-proxy 正在运行(
lsof -i :3457) - 确认 Claude Desktop 连接的是
127.0.0.1:3457(proxy)而不是3456(ccr) - 只有经过 proxy 的请求才会剥离 attribution header
LM Studio 报错 connection refused
确认 LM Studio 的 Server 模式已开启,且 API 地址和端口配置正确:
curl http://192.168.31.127:1234/v1/models
