Commit 02dfbda1 by Archer Committed by GitHub

update mcp server (#5076)

* update mcp server

* fix: action

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile

* fix: dockerfile
parent 3ed3f2ad
...@@ -7,6 +7,16 @@ toc: true ...@@ -7,6 +7,16 @@ toc: true
weight: 787 weight: 787
--- ---
## 更新指南
### 1. 更新镜像:
- 更新 FastGPT 镜像 tag: v4.9.13
- 更新 FastGPT 商业版镜像 tag: v4.9.13
- mcp_server 无需更新
- Sandbox 无需更新
- AIProxy 无需更新
## 🚀 新增内容 ## 🚀 新增内容
......
...@@ -4,7 +4,7 @@ description: 'FastGPT V4.9.14 更新说明' ...@@ -4,7 +4,7 @@ description: 'FastGPT V4.9.14 更新说明'
icon: 'upgrade' icon: 'upgrade'
draft: false draft: false
toc: true toc: true
weight: 787 weight: 786
--- ---
......
...@@ -6,6 +6,7 @@ RUN npm install -g pnpm@9.4.0 ...@@ -6,6 +6,7 @@ RUN npm install -g pnpm@9.4.0
# 复制package.json # 复制package.json
COPY pnpm-lock.yaml pnpm-workspace.yaml ./ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/global ./packages/global
COPY projects/mcp_server/package.json ./projects/mcp_server/package.json COPY projects/mcp_server/package.json ./projects/mcp_server/package.json
RUN apk add --no-cache\ RUN apk add --no-cache\
...@@ -14,18 +15,30 @@ 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 [ -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 ----------- # --------- builder -----------
FROM node:20.14.0-alpine AS builder FROM node:20.14.0-alpine AS builder
WORKDIR /app 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 ./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/node_modules /app/node_modules
COPY --from=install /app/projects/mcp_server/node_modules /app/projects/mcp_server/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 RUN pnpm --filter=mcp_server build
# runner # runner
......
...@@ -8,23 +8,20 @@ ...@@ -8,23 +8,20 @@
], ],
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "tsc && shx chmod +x dist/*.js", "build": "bun build src/index.ts --outdir=dist --target=node && chmod +x dist/index.js",
"dev": "nodemon --watch src --ext ts,json --exec \"npm run dev:run\"", "dev": "bun --watch src/index.ts",
"dev:run": "tsc && node dist/index.js", "start": "bun src/index.ts",
"mcp_test": "npx @modelcontextprotocol/inspector" "mcp_test": "npx @modelcontextprotocol/inspector"
}, },
"dependencies": { "dependencies": {
"@fastgpt/global": "workspace:*",
"@modelcontextprotocol/sdk": "^1.12.1", "@modelcontextprotocol/sdk": "^1.12.1",
"axios": "^1.8.2",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"dotenv": "^16.5.0", "dotenv": "^16.5.0",
"express": "^4.21.2" "express": "^4.21.2"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^5.0.1", "@types/express": "^5.0.1"
"nodemon": "^3.1.9",
"shx": "^0.3.4",
"typescript": "^5.6.2"
} }
} }
import type { Tool } from '@modelcontextprotocol/sdk/types.js'; 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 }); export const getTools = (key: string) => GET<Tool[]>('/support/mcp/server/toolList', { key });
......
import type { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import { addLog } from '../utils/log';
import axios from 'axios';
type ConfigType = {
headers?: Record<string, string>;
timeout?: number;
};
type ConfigType = {};
type ResponseDataType = { type ResponseDataType = {
code: number; code: number;
message: string; message: string;
...@@ -9,22 +12,6 @@ type ResponseDataType = { ...@@ -9,22 +12,6 @@ type ResponseDataType = {
}; };
/** /**
* 请求开始
*/
function startInterceptors(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig {
if (config.headers) {
}
return config;
}
/**
* 请求成功,检查请求头
*/
function responseSuccess(response: AxiosResponse<ResponseDataType>) {
return response;
}
/**
* 响应数据检查 * 响应数据检查
*/ */
function checkRes(data: ResponseDataType) { function checkRes(data: ResponseDataType) {
...@@ -38,10 +25,10 @@ function checkRes(data: ResponseDataType) { ...@@ -38,10 +25,10 @@ function checkRes(data: ResponseDataType) {
} }
/** /**
* 响应错误 * 响应错误处理
*/ */
function responseError(err: any) { function responseError(err: any) {
console.log('error->', '请求错误', err); addLog.error(`Fetch request error`, err);
const data = err?.response?.data || err; const data = err?.response?.data || err;
if (!err) { if (!err) {
...@@ -51,62 +38,175 @@ function responseError(err: any) { ...@@ -51,62 +38,175 @@ function responseError(err: any) {
return Promise.reject({ message: err }); return Promise.reject({ message: err });
} }
if (typeof data === 'string') { if (typeof data === 'string') {
return Promise.reject(data); return Promise.reject({ message: data });
}
// 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);
} }
/* 创建请求实例 */ /**
const instance = axios.create({ * 构建查询参数
baseURL: `${process.env.FASTGPT_ENDPOINT}/api`, */
timeout: 600000, // 超时时间 function buildQueryString(params: Record<string, any>): string {
headers: { const searchParams = new URLSearchParams();
'content-type': 'application/json'
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();
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) { async function request(url: string, data: any, config: ConfigType, method: string): Promise<any> {
delete data[key]; // Remove undefined values
const cleanData = { ...data };
for (const key in cleanData) {
if (cleanData[key] === undefined) {
delete cleanData[key];
} }
} }
return instance const baseURL = `${process.env.FASTGPT_ENDPOINT}/api`;
.request({ let fullUrl = `${baseURL}${url}`;
url,
// Default timeout from config or 600 seconds
const timeout = config.timeout || 600000;
const options: RequestInit = {
method, method,
data: ['POST', 'PUT'].includes(method) ? data : undefined, headers: {
params: !['POST', 'PUT'].includes(method) ? data : undefined 'content-type': 'application/json',
}) ...config.headers
.then((res) => checkRes(res.data)) },
.catch((err) => responseError(err)); 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);
}
} }
/** /**
* api请求方式 * GET 请求
* @param {String} url * @param {String} url - 请求路径
* @param {Any} params * @param {Object} params - 查询参数
* @param {Object} config * @param {Object} config - 请求配置
* @returns * @returns {Promise<T>}
*/ */
export function GET<T = undefined>(url: string, params = {}, config: ConfigType = {}): Promise<T> { export function GET<T = undefined>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
return request(url, params, config, 'GET'); 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> { export function POST<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'POST'); 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> { export function PUT<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'PUT'); 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> { export function DELETE<T = undefined>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'DELETE'); 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');
}
#!/usr/bin/env node #!/usr/bin/env node
import './init.js'; import './init';
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express'; import express from 'express';
import { callTool, getTools } from './api/fastgpt.js'; import { callTool, getTools } from './api/fastgpt';
import { addLog } from './utils/log.js'; import { addLog } from './utils/log';
import { getErrText } from './utils/error.js'; import { getErrText } from '@fastgpt/global/common/error/utils';
const app = express(); const app = express();
......
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);
};
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;
};
{ {
"include": ["src"], "extends": "../../tsconfig.json",
"exclude": ["node_modules"],
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"module": "esnext", "module": "ESNext",
"moduleResolution": "node", "moduleResolution": "node",
"strict": true, "strict": true,
"esModuleInterop": true, "esModuleInterop": true,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment