update mcp server (#5076)

* update mcp server

* fix: action

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile
This commit is contained in:
Archer
2025-06-22 14:36:56 +08:00
committed by GitHub
parent 3ed3f2ad01
commit 02dfbda1f8
11 changed files with 243 additions and 235 deletions

View File

@@ -6,6 +6,7 @@ RUN npm install -g pnpm@9.4.0
# 复制package.json
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/global ./packages/global
COPY projects/mcp_server/package.json ./projects/mcp_server/package.json
RUN apk add --no-cache\
@@ -14,18 +15,30 @@ RUN apk add --no-cache\
# 安装依赖
RUN [ -f pnpm-lock.yaml ] || (echo "Lockfile not found." && exit 1)
RUN pnpm i
# if proxy exists, set proxy
RUN if [ -z "$proxy" ]; then \
pnpm i; \
else \
pnpm i --registry=https://registry.npmmirror.com; \
fi
# --------- builder -----------
FROM node:20.14.0-alpine AS builder
WORKDIR /app
COPY package.json pnpm-workspace.yaml /app/
COPY package.json pnpm-workspace.yaml .npmrc tsconfig.json ./
COPY ./projects/mcp_server /app/projects/mcp_server
COPY --from=install /app/packages /app/packages
COPY --from=install /app/node_modules /app/node_modules
COPY --from=install /app/projects/mcp_server/node_modules /app/projects/mcp_server/node_modules
RUN npm install -g pnpm@9.4.0
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
RUN apk add --no-cache libc6-compat curl bash && npm install -g pnpm@9.4.0
# Install curl and bash, then install bun
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:$PATH"
RUN pnpm --filter=mcp_server build
# runner

View File

@@ -8,23 +8,20 @@
],
"type": "module",
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"dev": "nodemon --watch src --ext ts,json --exec \"npm run dev:run\"",
"dev:run": "tsc && node dist/index.js",
"build": "bun build src/index.ts --outdir=dist --target=node && chmod +x dist/index.js",
"dev": "bun --watch src/index.ts",
"start": "bun src/index.ts",
"mcp_test": "npx @modelcontextprotocol/inspector"
},
"dependencies": {
"@fastgpt/global": "workspace:*",
"@modelcontextprotocol/sdk": "^1.12.1",
"axios": "^1.8.2",
"chalk": "^5.3.0",
"dayjs": "^1.11.7",
"dotenv": "^16.5.0",
"express": "^4.21.2"
},
"devDependencies": {
"@types/express": "^5.0.1",
"nodemon": "^3.1.9",
"shx": "^0.3.4",
"typescript": "^5.6.2"
"@types/express": "^5.0.1"
}
}

View File

@@ -1,5 +1,5 @@
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
import { GET, POST } from './request.js';
import { GET, POST } from './request';
export const getTools = (key: string) => GET<Tool[]>('/support/mcp/server/toolList', { key });

View File

@@ -1,29 +1,16 @@
import type { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import axios from 'axios';
import { addLog } from '../utils/log';
type ConfigType = {
headers?: Record<string, string>;
timeout?: number;
};
type ConfigType = {};
type ResponseDataType = {
code: number;
message: string;
data: any;
};
/**
* 请求开始
*/
function startInterceptors(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig {
if (config.headers) {
}
return config;
}
/**
* 请求成功,检查请求头
*/
function responseSuccess(response: AxiosResponse<ResponseDataType>) {
return response;
}
/**
* 响应数据检查
*/
@@ -38,10 +25,10 @@ function checkRes(data: ResponseDataType) {
}
/**
* 响应错误
* 响应错误处理
*/
function responseError(err: any) {
console.log('error->', '请求错误', err);
addLog.error(`Fetch request error`, err);
const data = err?.response?.data || err;
if (!err) {
@@ -51,62 +38,175 @@ function responseError(err: any) {
return Promise.reject({ message: err });
}
if (typeof data === 'string') {
return Promise.reject(data);
}
}
/* 创建请求实例 */
const instance = axios.create({
baseURL: `${process.env.FASTGPT_ENDPOINT}/api`,
timeout: 600000, // 超时时间
headers: {
'content-type': 'application/json'
}
});
/* 请求拦截 */
instance.interceptors.request.use(startInterceptors, (err) => Promise.reject(err));
/* 响应拦截 */
instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err));
function request(url: string, data: any, config: ConfigType, method: Method): any {
/* 去空 */
for (const key in data) {
if (data[key] === undefined) {
delete data[key];
}
return Promise.reject({ message: data });
}
return instance
.request({
url,
method,
data: ['POST', 'PUT'].includes(method) ? data : undefined,
params: !['POST', 'PUT'].includes(method) ? data : undefined
})
.then((res) => checkRes(res.data))
.catch((err) => responseError(err));
// Handle fetch-specific errors
if (err.name === 'AbortError') {
return Promise.reject({ message: '请求超时' });
}
if (err.name === 'TypeError' && err.message.includes('fetch')) {
return Promise.reject({ message: '网络连接失败' });
}
return Promise.reject(data || err);
}
/**
* api请求方式
* @param {String} url
* @param {Any} params
* @param {Object} config
* @returns
* 构建查询参数
*/
function buildQueryString(params: Record<string, any>): string {
const searchParams = new URLSearchParams();
Object.keys(params).forEach((key) => {
const value = params[key];
if (value !== undefined && value !== null) {
if (Array.isArray(value)) {
value.forEach((item) => searchParams.append(key, String(item)));
} else {
searchParams.append(key, String(value));
}
}
});
return searchParams.toString();
}
/**
* 发送请求
*/
async function request(url: string, data: any, config: ConfigType, method: string): Promise<any> {
// Remove undefined values
const cleanData = { ...data };
for (const key in cleanData) {
if (cleanData[key] === undefined) {
delete cleanData[key];
}
}
const baseURL = `${process.env.FASTGPT_ENDPOINT}/api`;
let fullUrl = `${baseURL}${url}`;
// Default timeout from config or 600 seconds
const timeout = config.timeout || 600000;
const options: RequestInit = {
method,
headers: {
'content-type': 'application/json',
...config.headers
},
signal: AbortSignal.timeout(timeout)
};
// Handle request body and query parameters
if (['POST', 'PUT', 'PATCH'].includes(method)) {
if (Object.keys(cleanData).length > 0) {
options.body = JSON.stringify(cleanData);
}
} else if (Object.keys(cleanData).length > 0) {
const queryString = buildQueryString(cleanData);
if (queryString) {
fullUrl += `?${queryString}`;
}
}
try {
const response = await fetch(fullUrl, options);
// Handle non-2xx responses
if (!response.ok) {
let errorData;
try {
errorData = await response.json();
} catch {
errorData = {
code: response.status,
message: response.statusText || `HTTP ${response.status}`,
data: null
};
}
throw errorData;
}
// Parse response
let result;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
result = await response.json();
} else {
// Handle non-JSON responses
const text = await response.text();
try {
result = JSON.parse(text);
} catch {
// If it's not JSON, wrap it in the expected format
result = {
code: response.status,
message: 'success',
data: text
};
}
}
return checkRes(result);
} catch (err) {
return responseError(err);
}
}
/**
* GET 请求
* @param {String} url - 请求路径
* @param {Object} params - 查询参数
* @param {Object} config - 请求配置
* @returns {Promise<T>}
*/
export function GET<T = undefined>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
return request(url, params, config, 'GET');
}
/**
* POST 请求
* @param {String} url - 请求路径
* @param {Object} data - 请求体数据
* @param {Object} config - 请求配置
* @returns {Promise<T>}
*/
export function POST<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'POST');
}
/**
* PUT 请求
* @param {String} url - 请求路径
* @param {Object} data - 请求体数据
* @param {Object} config - 请求配置
* @returns {Promise<T>}
*/
export function PUT<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'PUT');
}
/**
* DELETE 请求
* @param {String} url - 请求路径
* @param {Object} data - 请求体数据
* @param {Object} config - 请求配置
* @returns {Promise<T>}
*/
export function DELETE<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'DELETE');
}
/**
* PATCH 请求
* @param {String} url - 请求路径
* @param {Object} data - 请求体数据
* @param {Object} config - 请求配置
* @returns {Promise<T>}
*/
export function PATCH<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'PATCH');
}

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env node
import './init.js';
import './init';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
import { callTool, getTools } from './api/fastgpt.js';
import { addLog } from './utils/log.js';
import { getErrText } from './utils/error.js';
import { callTool, getTools } from './api/fastgpt';
import { addLog } from './utils/log';
import { getErrText } from '@fastgpt/global/common/error/utils';
const app = express();

View File

@@ -1,10 +0,0 @@
import { replaceSensitiveText } from './string.js';
export const getErrText = (err: any, def = ''): any => {
const msg: string =
typeof err === 'string'
? err
: err?.response?.data?.message || err?.response?.message || err?.message || def;
// msg && console.log('error =>', msg);
return replaceSensitiveText(msg);
};

View File

@@ -1,8 +0,0 @@
export const replaceSensitiveText = (text: string) => {
// 1. http link
text = text.replace(/(?<=https?:\/\/)[^\s]+/g, 'xxx');
// 2. nx-xxx 全部替换成xxx
text = text.replace(/ns-[\w-]+/g, 'xxx');
return text;
};

View File

@@ -1,9 +1,8 @@
{
"include": ["src"],
"exclude": ["node_modules"],
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2022",
"module": "esnext",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,