Compare commits
13 Commits
db7b8273db
...
68db65c61b
Author | SHA1 | Date | |
---|---|---|---|
68db65c61b | |||
![]() |
167c791452 | ||
![]() |
547d07c8fa | ||
![]() |
740293ab74 | ||
![]() |
fe2a7c68cb | ||
![]() |
46115bcfba | ||
![]() |
ae7eb45403 | ||
![]() |
dd4331cdaa | ||
![]() |
054017bfe5 | ||
![]() |
c50c75821c | ||
![]() |
df2716fa7a | ||
![]() |
b1f0675b5e | ||
![]() |
fc68a12b30 |
3514
package-lock.json
generated
3514
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -214,8 +214,7 @@
|
|||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"service",
|
"service",
|
||||||
"renderer",
|
"renderer"
|
||||||
"software"
|
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"setup": "npm i && npm run prepare:ocr",
|
"setup": "npm i && npm run prepare:ocr",
|
||||||
@ -237,6 +236,7 @@
|
|||||||
"@seald-io/nedb": "^4.1.1",
|
"@seald-io/nedb": "^4.1.1",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"bson": "^6.8.0",
|
"bson": "^6.8.0",
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
"openai": "^5.0.1",
|
"openai": "^5.0.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"tesseract.js": "^6.0.1",
|
"tesseract.js": "^6.0.1",
|
||||||
|
@ -132,6 +132,7 @@ import ConnectInterfaceOpenai from './connect-interface-openai.vue';
|
|||||||
import ConnectTest from './connect-test.vue';
|
import ConnectTest from './connect-test.vue';
|
||||||
import { llmSettingRef, makeSimpleTalk, simpleTestResult } from './api';
|
import { llmSettingRef, makeSimpleTalk, simpleTestResult } from './api';
|
||||||
import { useMessageBridge } from '@/api/message-bridge';
|
import { useMessageBridge } from '@/api/message-bridge';
|
||||||
|
import { mcpSetting } from '@/hook/mcp';
|
||||||
|
|
||||||
defineComponent({ name: 'api' });
|
defineComponent({ name: 'api' });
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@ -233,11 +234,13 @@ async function updateModels() {
|
|||||||
const llm = llms[llmManager.currentModelIndex];
|
const llm = llms[llmManager.currentModelIndex];
|
||||||
const apiKey = llm.userToken;
|
const apiKey = llm.userToken;
|
||||||
const baseURL = llm.baseUrl;
|
const baseURL = llm.baseUrl;
|
||||||
|
const proxyServer = mcpSetting.proxyServer;
|
||||||
|
|
||||||
const bridge = useMessageBridge();
|
const bridge = useMessageBridge();
|
||||||
const { code, msg } = await bridge.commandRequest('llm/models', {
|
const { code, msg } = await bridge.commandRequest('llm/models', {
|
||||||
apiKey,
|
apiKey,
|
||||||
baseURL
|
baseURL,
|
||||||
|
proxyServer
|
||||||
});
|
});
|
||||||
|
|
||||||
const isGemini = baseURL.includes('googleapis');
|
const isGemini = baseURL.includes('googleapis');
|
||||||
|
@ -28,7 +28,7 @@ const { t } = useI18n();
|
|||||||
|
|
||||||
const isGoogle = computed(() => {
|
const isGoogle = computed(() => {
|
||||||
const model = llms[llmManager.currentModelIndex];
|
const model = llms[llmManager.currentModelIndex];
|
||||||
return model.userModel.startsWith('gemini') || model.baseUrl.includes('googleapis');
|
return model.userModel?.startsWith('gemini') || model.baseUrl.includes('googleapis');
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(llms[llmManager.currentModelIndex]);
|
console.log(llms[llmManager.currentModelIndex]);
|
||||||
|
@ -4,16 +4,15 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"type": "commonjs",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "ts-node-dev --respawn --transpile-only src/main.ts",
|
"serve": "nodemon --watch src --exec tsx src/main.ts",
|
||||||
"serve": "ts-node-dev --respawn --transpile-only src/main.ts",
|
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"build:watch": "tsc --watch",
|
"build:watch": "tsc --watch",
|
||||||
"postbuild": "node ./scripts/post-build.mjs",
|
"postbuild": "node ./scripts/post-build.mjs",
|
||||||
"start": "node dist/main.js",
|
"start": "node --experimental-specifier-resolution=node dist/main.js",
|
||||||
"start:prod": "NODE_ENV=production node dist/main.js",
|
"start:prod": "NODE_ENV=production node --experimental-specifier-resolution=node dist/main.js",
|
||||||
"debug": "node --inspect -r ts-node/register src/main.ts",
|
"debug": "node --inspect --no-warnings=ExperimentalWarning -r tsx/esm src/main.ts",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"lint": "eslint src --ext .ts,.tsx",
|
"lint": "eslint src --ext .ts,.tsx",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
@ -29,9 +28,10 @@
|
|||||||
"@types/pako": "^2.0.3",
|
"@types/pako": "^2.0.3",
|
||||||
"@types/ws": "^8.18.0",
|
"@types/ws": "^8.18.0",
|
||||||
"esbuild": "^0.25.3",
|
"esbuild": "^0.25.3",
|
||||||
"ts-node": "^10.9.2",
|
"nodemon": "^3.1.10",
|
||||||
"ts-node-dev": "^2.0.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.6.3",
|
"tsx": "^4.19.4",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
"webpack-cli": "^6.0.1",
|
"webpack-cli": "^6.0.1",
|
||||||
"webpack-node-externals": "^3.0.0"
|
"webpack-node-externals": "^3.0.0"
|
||||||
},
|
},
|
||||||
@ -39,6 +39,8 @@
|
|||||||
"@modelcontextprotocol/sdk": "^1.12.1",
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
"@seald-io/nedb": "^4.1.1",
|
"@seald-io/nedb": "^4.1.1",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
|
"open": "^10.1.2",
|
||||||
"openai": "^5.0.1",
|
"openai": "^5.0.1",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter.js";
|
||||||
import { McpClient } from "../mcp/client.service";
|
import { McpClient } from "../mcp/client.service.js";
|
||||||
|
|
||||||
export type RequestClientType = McpClient | undefined;
|
export type RequestClientType = McpClient | undefined;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { MapperDescriptor, RequestHandler, RequestClientType, RestfulResponse, ControllerOption, RequestHandlerStore } from "./index.dto";
|
import { MapperDescriptor, RestfulResponse, ControllerOption, RequestHandlerStore } from "./index.dto.js";
|
||||||
export { MapperDescriptor, RequestHandler, RequestClientType };
|
|
||||||
|
|
||||||
export const requestHandlerStorage = new Map<string, RequestHandlerStore<any, RestfulResponse>>();
|
export const requestHandlerStorage = new Map<string, RequestHandlerStore<any, RestfulResponse>>();
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { requestHandlerStorage } from ".";
|
import { requestHandlerStorage } from "./index.js";
|
||||||
import type { PostMessageble } from "../hook/adapter";
|
import type { PostMessageble } from "../hook/adapter.js";
|
||||||
import { LlmController } from "../llm/llm.controller";
|
import { LlmController } from "../llm/llm.controller.js";
|
||||||
import { ClientController } from "../mcp/client.controller";
|
import { ClientController } from "../mcp/client.controller.js";
|
||||||
import { ConnectController } from "../mcp/connect.controller";
|
import { ConnectController } from "../mcp/connect.controller.js";
|
||||||
import { OcrController } from "../mcp/ocr.controller";
|
import { OcrController } from "../mcp/ocr.controller.js";
|
||||||
import { PanelController } from "../panel/panel.controller";
|
import { PanelController } from "../panel/panel.controller.js";
|
||||||
import { SettingController } from "../setting/setting.controller";
|
import { SettingController } from "../setting/setting.controller.js";
|
||||||
|
|
||||||
export const ModuleControllers = [
|
export const ModuleControllers = [
|
||||||
ConnectController,
|
ConnectController,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { WebSocket } from 'ws';
|
import { WebSocket } from 'ws';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { routeMessage } from '../common/router';
|
import { routeMessage } from '../common/router.js';
|
||||||
import { McpOptions } from '../mcp/client.dto';
|
import { McpOptions } from '../mcp/client.dto.js';
|
||||||
import { clientMap, connectService } from '../mcp/connect.service';
|
import { clientMap, connectService } from '../mcp/connect.service.js';
|
||||||
|
|
||||||
// WebSocket 消息格式
|
// WebSocket 消息格式
|
||||||
export interface WebSocketMessage {
|
export interface WebSocketMessage {
|
||||||
@ -136,7 +136,7 @@ export class TaskLoopAdapter {
|
|||||||
for (const client of clientMap.values()) {
|
for (const client of clientMap.values()) {
|
||||||
const clientTools = await client?.listTools();
|
const clientTools = await client?.listTools();
|
||||||
if (clientTools?.tools) {
|
if (clientTools?.tools) {
|
||||||
const enabledTools = clientTools.tools.map((tool) => {
|
const enabledTools = clientTools.tools.map((tool: any) => {
|
||||||
const enabledTools = {...tool, enabled: true };
|
const enabledTools = {...tool, enabled: true };
|
||||||
return enabledTools;
|
return enabledTools;
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ interface Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dbConnections: Record<string, any> = {};
|
const dbConnections: Record<string, any> = {};
|
||||||
|
const DatastoreCtor = Datastore as unknown as { new(options: any): any };
|
||||||
|
|
||||||
export class LocalDB<T extends Entity> {
|
export class LocalDB<T extends Entity> {
|
||||||
private db: any;
|
private db: any;
|
||||||
@ -28,7 +29,7 @@ export class LocalDB<T extends Entity> {
|
|||||||
const filename = path.join(dbPath, `${this.tableName}.db`);
|
const filename = path.join(dbPath, `${this.tableName}.db`);
|
||||||
|
|
||||||
if (!dbConnections[filename]) {
|
if (!dbConnections[filename]) {
|
||||||
dbConnections[filename] = new Datastore({
|
dbConnections[filename] = new DatastoreCtor({
|
||||||
filename,
|
filename,
|
||||||
autoload: true,
|
autoload: true,
|
||||||
timestampData: true
|
timestampData: true
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export { routeMessage } from './common/router';
|
export { routeMessage } from './common/router.js';
|
||||||
export { VSCodeWebViewLike, TaskLoopAdapter } from './hook/adapter';
|
export { VSCodeWebViewLike, TaskLoopAdapter } from './hook/adapter.js';
|
||||||
export { setVscodeWorkspace, setRunningCWD } from './hook/setting';
|
export { setVscodeWorkspace, setRunningCWD } from './hook/setting.js';
|
||||||
// TODO: 更加规范
|
// TODO: 更加规范
|
||||||
export { clientMap } from './mcp/connect.service';
|
export { clientMap } from './mcp/connect.service.js';
|
@ -1,9 +1,11 @@
|
|||||||
|
import { RequestClientType } from "../common/index.dto.js";
|
||||||
|
import { Controller } from "../common/index.js";
|
||||||
|
import { RequestData } from "../common/index.dto.js";
|
||||||
|
import { PostMessageble } from "../hook/adapter.js";
|
||||||
|
import { getClient } from "../mcp/connect.service.js";
|
||||||
|
import { abortMessageService, streamingChatCompletion } from "./llm.service.js";
|
||||||
import { OpenAI } from "openai";
|
import { OpenAI } from "openai";
|
||||||
import { Controller } from "../common";
|
import { axiosFetch } from "src/hook/axios-fetch.js";
|
||||||
import { RequestData } from "../common/index.dto";
|
|
||||||
import { PostMessageble } from "../hook/adapter";
|
|
||||||
import { abortMessageService, streamingChatCompletion } from "./llm.service";
|
|
||||||
|
|
||||||
export class LlmController {
|
export class LlmController {
|
||||||
|
|
||||||
@Controller('llm/chat/completions')
|
@Controller('llm/chat/completions')
|
||||||
@ -40,9 +42,14 @@ export class LlmController {
|
|||||||
const {
|
const {
|
||||||
baseURL,
|
baseURL,
|
||||||
apiKey,
|
apiKey,
|
||||||
} = data;
|
proxyServer
|
||||||
|
} = data;
|
||||||
|
|
||||||
const client = new OpenAI({ apiKey, baseURL });
|
|
||||||
|
const client = new OpenAI({
|
||||||
|
apiKey,
|
||||||
|
baseURL,
|
||||||
|
});
|
||||||
const models = await client.models.list();
|
const models = await client.models.list();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter.js";
|
||||||
import { OpenAI } from "openai";
|
import { OpenAI } from "openai";
|
||||||
import { MyMessageType, MyToolMessageType } from "./llm.dto";
|
import { MyMessageType, MyToolMessageType } from "./llm.dto.js";
|
||||||
import { RestfulResponse } from "../common/index.dto";
|
import { RestfulResponse } from "../common/index.dto.js";
|
||||||
import { ocrDB } from "../hook/db";
|
import { ocrDB } from "../hook/db.js";
|
||||||
import type { ToolCallContent } from "../mcp/client.dto";
|
import type { ToolCallContent } from "../mcp/client.dto.js";
|
||||||
import { ocrWorkerStorage } from "../mcp/ocr.service";
|
import { ocrWorkerStorage } from "../mcp/ocr.service.js";
|
||||||
import { axiosFetch } from "../hook/axios-fetch";
|
import { axiosFetch } from "../hook/axios-fetch.js";
|
||||||
|
|
||||||
export let currentStream: AsyncIterable<any> | null = null;
|
export let currentStream: AsyncIterable<any> | null = null;
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { WebSocketServer } from 'ws';
|
import { WebSocketServer } from 'ws';
|
||||||
import pino from 'pino';
|
import {pino} from 'pino';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import { routeMessage } from './common/router';
|
import { dirname } from 'path';
|
||||||
import { VSCodeWebViewLike } from './hook/adapter';
|
import { routeMessage } from './common/router.js';
|
||||||
|
import { VSCodeWebViewLike } from './hook/adapter.js';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import { setRunningCWD } from './hook/setting';
|
import { setRunningCWD } from './hook/setting.js';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export interface VSCodeMessage {
|
export interface VSCodeMessage {
|
||||||
@ -85,7 +86,7 @@ function updateConnectionOption(data: any) {
|
|||||||
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
|
fs.writeFileSync(envPath, JSON.stringify(connection, null, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const devHome = path.join(__dirname, '..', '..');
|
const devHome = path.join(__dirname, '..', '..');
|
||||||
setRunningCWD(devHome);
|
setRunningCWD(devHome);
|
||||||
|
|
||||||
|
186
service/src/mcp/auth.service.ts
Normal file
186
service/src/mcp/auth.service.ts
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import { createServer } from 'node:http';
|
||||||
|
import { URL } from 'node:url';
|
||||||
|
import { OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
||||||
|
import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
|
||||||
|
import open from 'open';
|
||||||
|
|
||||||
|
// const CALLBACK_PORT = 16203; // Use different port than auth server (3001)
|
||||||
|
// const CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 内存中的OAuth客户端提供者
|
||||||
|
*/
|
||||||
|
class InMemoryOAuthClientProvider implements OAuthClientProvider {
|
||||||
|
private _clientInformation?: OAuthClientInformationFull;
|
||||||
|
private _tokens?: OAuthTokens;
|
||||||
|
private _codeVerifier?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _redirectUrl: string | URL,
|
||||||
|
private readonly _clientMetadata: OAuthClientMetadata,
|
||||||
|
onRedirect?: (url: URL) => void
|
||||||
|
) {
|
||||||
|
this._onRedirect = onRedirect || ((url) => {
|
||||||
|
console.log(`Redirect to: ${url.toString()}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onRedirect: (url: URL) => void;
|
||||||
|
|
||||||
|
get redirectUrl(): string | URL {
|
||||||
|
return this._redirectUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
get clientMetadata(): OAuthClientMetadata {
|
||||||
|
return this._clientMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientInformation(): OAuthClientInformation | undefined {
|
||||||
|
return this._clientInformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveClientInformation(clientInformation: OAuthClientInformationFull): void {
|
||||||
|
this._clientInformation = clientInformation;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens(): OAuthTokens | undefined {
|
||||||
|
return this._tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveTokens(tokens: OAuthTokens): void {
|
||||||
|
this._tokens = tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectToAuthorization(authorizationUrl: URL): void {
|
||||||
|
this._onRedirect(authorizationUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCodeVerifier(codeVerifier: string): void {
|
||||||
|
this._codeVerifier = codeVerifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
codeVerifier(): string {
|
||||||
|
if (!this._codeVerifier) {
|
||||||
|
throw new Error('No code verifier saved');
|
||||||
|
}
|
||||||
|
return this._codeVerifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class OAuthClient {
|
||||||
|
port: number;
|
||||||
|
callbackUrl: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
console.log('🔐 Initializing OAuth client...');
|
||||||
|
// 初始化OAuth客户端
|
||||||
|
this.port = Math.floor(Math.random() * (50000 - 40000 + 1)) + 40000;
|
||||||
|
//TODO: 如果端口被占用,重新生成一个端口
|
||||||
|
this.callbackUrl = `http://localhost:${this.port}/callback`;
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description 开启本地服务器上,并监听OAuth回调请求,并解析授权码或错误信息
|
||||||
|
* @returns {Promise<string>} 返回授权码
|
||||||
|
* @throws {Error} 如果没有收到授权码或发生错误
|
||||||
|
*/
|
||||||
|
|
||||||
|
public async waitForOAuthCallback(): Promise<string> {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const server = createServer((req, res) => {
|
||||||
|
// Ignore favicon requests
|
||||||
|
if (req.url === '/favicon.ico') {
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📥 Received callback: ${req.url}`);
|
||||||
|
const parsedUrl = new URL(req.url || '', 'http://localhost');
|
||||||
|
const code = parsedUrl.searchParams.get('code');
|
||||||
|
const error = parsedUrl.searchParams.get('error');
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
console.log(`✅ Authorization code received: ${code?.substring(0, 10)}...`);
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||||
|
res.end(`
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Authorization Successful!</h1>
|
||||||
|
<p>You can close this window and return to the terminal.</p>
|
||||||
|
<script>setTimeout(() => window.close(), 2000);</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
|
||||||
|
resolve(code);
|
||||||
|
setTimeout(() => server.close(), 3000);
|
||||||
|
} else if (error) {
|
||||||
|
console.log(`❌ Authorization error: ${error}`);
|
||||||
|
res.writeHead(400, { 'Content-Type': 'text/html' });
|
||||||
|
res.end(`
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Authorization Failed</h1>
|
||||||
|
<p>Error: ${error}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
reject(new Error(`OAuth authorization failed: ${error}`));
|
||||||
|
} else {
|
||||||
|
console.log(`❌ No authorization code or error in callback`);
|
||||||
|
res.writeHead(400);
|
||||||
|
res.end('Bad request');
|
||||||
|
reject(new Error('No authorization code provided'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(this.port, () => {
|
||||||
|
console.log(`OAuth callback server started on http://localhost:${this.port}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取Oauth认证provider
|
||||||
|
* @return {Promise<OAuthClientProvider>} 返回一个OAuthClientProvider实例
|
||||||
|
*/
|
||||||
|
public async getOAuthProvider(): Promise<OAuthClientProvider> {
|
||||||
|
|
||||||
|
const clientMetadata: OAuthClientMetadata = {
|
||||||
|
client_name: 'Simple OAuth MCP Client',
|
||||||
|
redirect_uris: [this.callbackUrl],
|
||||||
|
grant_types: ['authorization_code', 'refresh_token'],
|
||||||
|
response_types: ['code'],
|
||||||
|
token_endpoint_auth_method: 'none',
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('🔐 Creating OAuth provider...');
|
||||||
|
const oauthProvider = new InMemoryOAuthClientProvider(
|
||||||
|
this.callbackUrl,
|
||||||
|
clientMetadata,
|
||||||
|
(redirectUrl: URL) => {
|
||||||
|
console.log(`📌 OAuth redirect handler called - opening browser`);
|
||||||
|
console.log(`Opening browser to: ${redirectUrl.toString()}`);
|
||||||
|
this.openBrowser(redirectUrl.toString());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log('🔐 OAuth provider created');
|
||||||
|
return oauthProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 打开浏览器
|
||||||
|
* @param url 授权URL
|
||||||
|
*/
|
||||||
|
|
||||||
|
public async openBrowser(url: string): Promise<void> {
|
||||||
|
console.log(`🌐 Opening browser for authorization: ${url}`);
|
||||||
|
await open(url); // 自动适配不同操作系统
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
import { Controller } from "../common";
|
import { Controller } from "../common/index.js";
|
||||||
import { RequestData } from "../common/index.dto";
|
import { RequestData } from "../common/index.dto.js";
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter.js";
|
||||||
import { postProcessMcpToolcallResponse } from "./client.service";
|
import { postProcessMcpToolcallResponse } from "./client.service.js";
|
||||||
import { getClient } from "./connect.service";
|
import { getClient } from "./connect.service.js";
|
||||||
|
|
||||||
export class ClientController {
|
export class ClientController {
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||||
import { Implementation } from "@modelcontextprotocol/sdk/types";
|
import { Implementation } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
export interface GetPromptOption {
|
export interface GetPromptOption {
|
||||||
promptId: string;
|
promptId: string;
|
||||||
|
@ -3,16 +3,20 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|||||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||||
import type { McpOptions, McpTransport, IServerVersion, ToolCallResponse, ToolCallContent } from './client.dto';
|
import type { McpOptions, McpTransport, IServerVersion, ToolCallResponse, ToolCallContent } from './client.dto.js';
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter.js";
|
||||||
import { createOcrWorker, saveBase64ImageData } from "./ocr.service";
|
import { createOcrWorker, saveBase64ImageData } from "./ocr.service.js";
|
||||||
|
import { OAuthClient } from "./auth.service.js";
|
||||||
|
import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js';
|
||||||
|
import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
|
||||||
// 增强的客户端类
|
// 增强的客户端类
|
||||||
export class McpClient {
|
export class McpClient {
|
||||||
private client: Client;
|
private client: Client;
|
||||||
private transport?: McpTransport;
|
private transport?: McpTransport;
|
||||||
private options: McpOptions;
|
private options: McpOptions;
|
||||||
private serverVersion: IServerVersion;
|
private serverVersion: IServerVersion;
|
||||||
|
private oAuthClient: OAuthClient;
|
||||||
|
private oauthPovider?: OAuthClientProvider;
|
||||||
|
|
||||||
constructor(options: McpOptions) {
|
constructor(options: McpOptions) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
@ -31,14 +35,18 @@ export class McpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.oAuthClient = new OAuthClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 连接方法
|
// 连接方法
|
||||||
public async connect(): Promise<void> {
|
public async connect(): Promise<void> {
|
||||||
|
if (!this.oauthPovider){
|
||||||
|
this.oauthPovider = await this.oAuthClient.getOAuthProvider();
|
||||||
|
}
|
||||||
// 根据连接类型创建传输层
|
// 根据连接类型创建传输层
|
||||||
switch (this.options.connectionType) {
|
switch (this.options.connectionType) {
|
||||||
case 'STDIO':
|
case 'STDIO':
|
||||||
this.transport = new StdioClientTransport({
|
this.transport = new StdioClientTransport({
|
||||||
command: this.options.command || '',
|
command: this.options.command || '',
|
||||||
args: this.options.args || [],
|
args: this.options.args || [],
|
||||||
@ -55,20 +63,22 @@ export class McpClient {
|
|||||||
this.transport = new SSEClientTransport(
|
this.transport = new SSEClientTransport(
|
||||||
new URL(this.options.url),
|
new URL(this.options.url),
|
||||||
{
|
{
|
||||||
// authProvider:
|
authProvider: this.oauthPovider
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'STREAMABLE_HTTP':
|
case 'STREAMABLE_HTTP':
|
||||||
if (!this.options.url) {
|
if (!this.options.url) {
|
||||||
throw new Error('URL is required for STREAMABLE_HTTP connection');
|
throw new Error('URL is required for STREAMABLE_HTTP connection');
|
||||||
}
|
}
|
||||||
this.transport = new StreamableHTTPClientTransport(
|
this.transport = new StreamableHTTPClientTransport(
|
||||||
new URL(this.options.url)
|
new URL(this.options.url),
|
||||||
|
{
|
||||||
|
authProvider:this.oauthPovider
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported connection type: ${this.options.connectionType}`);
|
throw new Error(`Unsupported connection type: ${this.options.connectionType}`);
|
||||||
@ -76,11 +86,32 @@ export class McpClient {
|
|||||||
|
|
||||||
// 建立连接
|
// 建立连接
|
||||||
if (this.transport) {
|
if (this.transport) {
|
||||||
await this.client.connect(this.transport);
|
try {
|
||||||
console.log(`Connected to MCP server via ${this.options.connectionType}`);
|
console.log(`🔌 Connecting to MCP server via ${this.options.connectionType}...`);
|
||||||
|
await this.client.connect(this.transport);
|
||||||
|
console.log(`Connected to MCP server via ${this.options.connectionType}`);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof UnauthorizedError) {
|
||||||
|
if (!(this.transport instanceof StreamableHTTPClientTransport) && !(this.transport instanceof SSEClientTransport)) {
|
||||||
|
console.error('❌ OAuth is only supported for StreamableHTTP and SSE transports. Please use one of these transports for OAuth authentication.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('🔐 OAuth required - waiting for authorization...');
|
||||||
|
const callbackPromise = this.oAuthClient.waitForOAuthCallback();
|
||||||
|
const authCode = await callbackPromise;
|
||||||
|
await this.transport.finishAuth(authCode);
|
||||||
|
console.log('🔐 Authorization code received:', authCode);
|
||||||
|
console.log('🔌 Reconnecting with authenticated transport...');
|
||||||
|
await this.connect(); // 递归重试
|
||||||
|
} else {
|
||||||
|
console.error('❌ Connection failed with non-auth error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public getServerVersion() {
|
public getServerVersion() {
|
||||||
if (this.serverVersion) {
|
if (this.serverVersion) {
|
||||||
return this.serverVersion;
|
return this.serverVersion;
|
||||||
@ -94,7 +125,7 @@ export class McpClient {
|
|||||||
// 断开连接
|
// 断开连接
|
||||||
public async disconnect(): Promise<void> {
|
public async disconnect(): Promise<void> {
|
||||||
await this.client.close();
|
await this.client.close();
|
||||||
|
|
||||||
console.log('Disconnected from MCP server');
|
console.log('Disconnected from MCP server');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +135,7 @@ export class McpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取提示
|
// 获取提示
|
||||||
public async getPrompt(name: string, args: Record<string, any> = {}) {
|
public async getPrompt(name: string, args: Record<string, any> = {}) {
|
||||||
return await this.client.getPrompt({
|
return await this.client.getPrompt({
|
||||||
name, arguments: args
|
name, arguments: args
|
||||||
});
|
});
|
||||||
@ -136,7 +167,6 @@ export class McpClient {
|
|||||||
public async callTool(options: { name: string; arguments: Record<string, any>, callToolOption?: any }) {
|
public async callTool(options: { name: string; arguments: Record<string, any>, callToolOption?: any }) {
|
||||||
const { callToolOption, ...methodArgs } = options;
|
const { callToolOption, ...methodArgs } = options;
|
||||||
const res = await this.client.callTool(methodArgs, undefined, callToolOption);
|
const res = await this.client.callTool(methodArgs, undefined, callToolOption);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,10 +185,10 @@ async function handleImage(
|
|||||||
if (content.data && content.mimeType) {
|
if (content.data && content.mimeType) {
|
||||||
const filename = saveBase64ImageData(content.data, content.mimeType);
|
const filename = saveBase64ImageData(content.data, content.mimeType);
|
||||||
content.data = filename;
|
content.data = filename;
|
||||||
|
|
||||||
// 加入工作线程
|
// 加入工作线程
|
||||||
const worker = createOcrWorker(filename, webview);
|
const worker = createOcrWorker(filename, webview);
|
||||||
|
|
||||||
content._meta = {
|
content._meta = {
|
||||||
ocr: true,
|
ocr: true,
|
||||||
workerId: worker.id
|
workerId: worker.id
|
||||||
@ -189,7 +219,7 @@ export function postProcessMcpToolcallResponse(
|
|||||||
case 'image':
|
case 'image':
|
||||||
handleImage(content, webview);
|
handleImage(content, webview);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Controller } from '../common';
|
import { Controller } from '../common/index.js';
|
||||||
import { PostMessageble } from '../hook/adapter';
|
import { PostMessageble } from '../hook/adapter.js';
|
||||||
import { RequestData } from '../common/index.dto';
|
import { RequestData } from '../common/index.dto.js';
|
||||||
import { connectService, getClient } from './connect.service';
|
import { connectService, getClient } from './connect.service.js';
|
||||||
|
|
||||||
export class ConnectController {
|
export class ConnectController {
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { exec, execSync, spawnSync } from 'node:child_process';
|
import { exec, execSync, spawnSync } from 'node:child_process';
|
||||||
import { RequestClientType } from '../common';
|
import { RequestClientType } from '../common/index.dto.js';
|
||||||
import { connect } from './client.service';
|
import { connect } from './client.service.js';
|
||||||
import { RestfulResponse } from '../common/index.dto';
|
import { RestfulResponse } from '../common/index.dto.js';
|
||||||
import { McpOptions } from './client.dto';
|
import { McpOptions } from './client.dto.js';
|
||||||
import * as crypto from 'node:crypto';
|
import * as crypto from 'node:crypto';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { PostMessageble } from '../hook/adapter';
|
import { PostMessageble } from '../hook/adapter.js';
|
||||||
|
|
||||||
export const clientMap: Map<string, RequestClientType> = new Map();
|
export const clientMap: Map<string, RequestClientType> = new Map();
|
||||||
export function getClient(clientId?: string): RequestClientType | undefined {
|
export function getClient(clientId?: string): RequestClientType | undefined {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Controller, RequestClientType } from "../common";
|
import { RequestClientType } from "../common/index.dto.js";
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { Controller } from "../common/index.js";
|
||||||
import { diskStorage } from "../hook/db";
|
import { PostMessageble } from "../hook/adapter.js";
|
||||||
import { createOcrWorker, saveBase64ImageData } from "./ocr.service";
|
import { diskStorage } from "../hook/db.js";
|
||||||
|
import { createOcrWorker, saveBase64ImageData } from "./ocr.service.js";
|
||||||
|
|
||||||
export class OcrController {
|
export class OcrController {
|
||||||
@Controller('ocr/get-ocr-image')
|
@Controller('ocr/get-ocr-image')
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import Tesseract from 'tesseract.js';
|
import Tesseract from 'tesseract.js';
|
||||||
import { PostMessageble } from '../hook/adapter';
|
import { PostMessageble } from '../hook/adapter.js';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { OcrWorker } from './ocr.dto';
|
import { OcrWorker } from './ocr.dto.js';
|
||||||
import { diskStorage, ocrDB } from '../hook/db';
|
import { diskStorage, ocrDB } from '../hook/db.js';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { RUNNING_CWD } from '../hook/setting';
|
import { RUNNING_CWD } from '../hook/setting.js';
|
||||||
|
|
||||||
export const ocrWorkerStorage = new Map<string, OcrWorker>();
|
export const ocrWorkerStorage = new Map<string, OcrWorker>();
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Controller } from "../common";
|
import { Controller } from "../common/index.js";
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter.js";
|
||||||
import { RequestData } from "../common/index.dto";
|
import { RequestData } from "../common/index.dto.js";
|
||||||
import { getClient } from "../mcp/connect.service";
|
import { getClient } from "../mcp/connect.service.js";
|
||||||
import { systemPromptDB } from "../hook/db";
|
import { systemPromptDB } from "../hook/db.js";
|
||||||
import { loadTabSaveConfig, saveTabSaveConfig } from "./panel.service";
|
import { loadTabSaveConfig, saveTabSaveConfig } from "./panel.service.js";
|
||||||
|
|
||||||
export class PanelController {
|
export class PanelController {
|
||||||
@Controller('panel/save')
|
@Controller('panel/save')
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { VSCODE_WORKSPACE } from '../hook/setting';
|
import { VSCODE_WORKSPACE } from '../hook/setting.js';
|
||||||
import { IServerVersion } from '../mcp/client.dto';
|
import { IServerVersion } from '../mcp/client.dto.js';
|
||||||
import { SaveTab } from './panel.dto';
|
import { SaveTab } from './panel.dto.js';
|
||||||
import { IConfig } from '../setting/setting.dto';
|
import { IConfig } from '../setting/setting.dto.js';
|
||||||
|
|
||||||
const DEFAULT_TABS: SaveTab = {
|
const DEFAULT_TABS: SaveTab = {
|
||||||
tabs: [],
|
tabs: [],
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { WebSocketServer } from 'ws';
|
import { WebSocketServer } from 'ws';
|
||||||
import pino from 'pino';
|
import pino from 'pino';
|
||||||
|
|
||||||
import { routeMessage } from './common/router';
|
import { routeMessage } from './common/router.js';
|
||||||
import { VSCodeWebViewLike } from './hook/adapter';
|
import { VSCodeWebViewLike } from './hook/adapter.js';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import { setRunningCWD } from './hook/setting';
|
import { setRunningCWD } from './hook/setting.js';
|
||||||
import { exit } from 'node:process';
|
import { exit } from 'node:process';
|
||||||
|
|
||||||
export interface VSCodeMessage {
|
export interface VSCodeMessage {
|
||||||
@ -14,7 +14,7 @@ export interface VSCodeMessage {
|
|||||||
callbackId?: string;
|
callbackId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = pino({
|
const logger = pino.default({
|
||||||
transport: {
|
transport: {
|
||||||
target: 'pino-pretty', // 启用 pino-pretty
|
target: 'pino-pretty', // 启用 pino-pretty
|
||||||
options: {
|
options: {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Controller } from "../common";
|
import { Controller } from "../common/index.js";
|
||||||
import { PostMessageble } from "../hook/adapter";
|
import { PostMessageble } from "../hook/adapter.js";
|
||||||
import { RequestData } from "../common/index.dto";
|
import { RequestData } from "../common/index.dto.js";
|
||||||
import { getClient } from "../mcp/connect.service";
|
import { getClient } from "../mcp/connect.service.js";
|
||||||
import { getTour, loadSetting, saveSetting, setTour } from "./setting.service";
|
import { getTour, loadSetting, saveSetting, setTour } from "./setting.service.js";
|
||||||
|
|
||||||
export class SettingController {
|
export class SettingController {
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { VSCODE_WORKSPACE } from '../hook/setting';
|
import { VSCODE_WORKSPACE } from '../hook/setting.js';
|
||||||
import { IConfig } from './setting.dto';
|
import { IConfig } from './setting.dto.js';
|
||||||
import { llms } from '../hook/llm';
|
import { llms } from '../hook/llm.js';
|
||||||
|
|
||||||
function getConfigurationPath() {
|
function getConfigurationPath() {
|
||||||
const homeDir = os.homedir();
|
const homeDir = os.homedir();
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"module": "commonjs",
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
@ -10,13 +12,16 @@
|
|||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"moduleResolution": "node"
|
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
"src/*"
|
"src/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": [
|
||||||
"exclude": ["node_modules", "src/main.ts"] // 排除 main.ts
|
"src/**/*"
|
||||||
}
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
]
|
||||||
|
}
|
@ -16,25 +16,37 @@ export class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<Conn
|
|||||||
getTreeItem(element: ConnectionViewItem): vscode.TreeItem {
|
getTreeItem(element: ConnectionViewItem): vscode.TreeItem {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
|
getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
|
||||||
// TODO: 读取 configDir 下的所有文件,作为子节点
|
// TODO: 读取 configDir 下的所有文件,作为子节点
|
||||||
const connection = getWorkspaceConnectionConfig();
|
const connection = getWorkspaceConnectionConfig();
|
||||||
const sidebarItems = connection.items.map((item, index) => {
|
|
||||||
// 连接的名字
|
// 校验 connection 和 connection.items
|
||||||
const nItem = Array.isArray(item) ? item[0] : item;
|
if (!connection || !Array.isArray(connection.items)) {
|
||||||
const itemName = `${nItem.name} (${nItem.type || nItem.connectionType})`
|
return Promise.resolve([]);
|
||||||
return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server');
|
}
|
||||||
})
|
|
||||||
|
const sidebarItems = connection.items
|
||||||
|
.filter(item => item !== null && item !== undefined)
|
||||||
|
.map((item, index) => {
|
||||||
|
// 连接的名字
|
||||||
|
const nItem = Array.isArray(item) ? item[0] : item;
|
||||||
|
if (!nItem || typeof nItem !== 'object') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const name = nItem.name || '未命名';
|
||||||
|
const type = nItem.type || nItem.connectionType || '未知类型';
|
||||||
|
const itemName = `${name} (${type})`;
|
||||||
|
return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server');
|
||||||
|
})
|
||||||
|
.filter(Boolean) as ConnectionViewItem[]; // 过滤掉为 null 的项
|
||||||
|
|
||||||
// 返回子节点
|
// 返回子节点
|
||||||
return Promise.resolve(sidebarItems);
|
return Promise.resolve(sidebarItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RegisterCommand('revealWebviewPanel')
|
@RegisterCommand('revealWebviewPanel')
|
||||||
public revealWebviewPanel(context: vscode.ExtensionContext, view: ConnectionViewItem) {
|
public revealWebviewPanel(context: vscode.ExtensionContext, view: ConnectionViewItem) {
|
||||||
const item = view.item;
|
const item = view.item;
|
||||||
const masterNode = Array.isArray(item)? item[0] : item;
|
const masterNode = Array.isArray(item) ? item[0] : item;
|
||||||
const name = masterNode.filePath || masterNode.name || '';
|
const name = masterNode.filePath || masterNode.name || '';
|
||||||
revealOpenMcpWebviewPanel(context, 'workspace', name, item);
|
revealOpenMcpWebviewPanel(context, 'workspace', name, item);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user