mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-15 15:41:05 +00:00
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:
@@ -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
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
|
@@ -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 });
|
||||
|
||||
|
@@ -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');
|
||||
}
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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);
|
||||
};
|
@@ -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;
|
||||
};
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user