server.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. #!/usr/bin/env node
  2. /**
  3. * DRG医保控费预警系统 - MCP服务器主文件
  4. * 连接内网IRIS数据库(172.16.2.15:52773)的完整实现
  5. */
  6. const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
  7. const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
  8. const {
  9. CallToolRequestSchema,
  10. ListToolsRequestSchema,
  11. ListResourcesRequestSchema,
  12. ReadResourceRequestSchema
  13. } = require('@modelcontextprotocol/sdk/types.js');
  14. const axios = require('axios');
  15. // 从环境变量获取配置
  16. const IRIS_HOST = process.env.IRIS_HOST || 'localhost';
  17. const IRIS_PORT = process.env.IRIS_PORT || '52773';
  18. const IRIS_NAMESPACE = process.env.IRIS_NAMESPACE || 'DRG';
  19. const IRIS_USERNAME = process.env.IRIS_USERNAME || '_SYSTEM';
  20. const IRIS_PASSWORD = process.env.IRIS_PASSWORD || 'pryk@2020';
  21. const MCP_API_KEY = process.env.MCP_API_KEY || 'drg-mcp-access-key-2026';
  22. // IRIS服务器基础URL
  23. const IRIS_BASE_URL = `http://${IRIS_HOST}:${IRIS_PORT}`;
  24. const IRIS_ENDPOINT = `${IRIS_BASE_URL}/csp/${IRIS_NAMESPACE.toLowerCase()}/sysInternalMutiple`;
  25. console.error(`[DRG MCP Server] 启动连接到IRIS服务器: ${IRIS_BASE_URL}`);
  26. console.error(`[DRG MCP Server] 命名空间: ${IRIS_NAMESPACE}`);
  27. console.error(`[DRG MCP Server] 用户名: ${IRIS_USERNAME}`);
  28. // 默认Session配置
  29. const defaultSession = {
  30. userID: "158",
  31. userCode: "admin",
  32. userName: "admin",
  33. locID: "2300",
  34. locDesc: "信息中心",
  35. groupID: "3",
  36. groupDesc: "医院维护员",
  37. hospID: "25",
  38. hospCode: "H03",
  39. hospDesc: "合肥普瑞眼科医院",
  40. langID: 1,
  41. langDesc: "简体中文",
  42. changeFlag: "N",
  43. changeDesc: "",
  44. lastLoginDate: "2026-01-01",
  45. lastLoginTime: "00:00:00",
  46. directorAuth: "N",
  47. defaultMenuType: "2",
  48. titleDesc: "",
  49. userYBCode: "",
  50. hospYBCode: "H34010400768",
  51. path: "",
  52. sessionID: "P9BXebxmDI",
  53. errorMessageTime: "",
  54. language: "CN",
  55. messageTime: 1
  56. };
  57. // 创建axios实例
  58. const irisClient = axios.create({
  59. baseURL: IRIS_BASE_URL,
  60. timeout: 30000,
  61. auth: {
  62. username: IRIS_USERNAME,
  63. password: IRIS_PASSWORD
  64. },
  65. headers: {
  66. 'Content-Type': 'application/json',
  67. 'Accept': 'application/json'
  68. }
  69. });
  70. /**
  71. * 调用IRIS接口
  72. */
  73. async function invokeIRISInterface(interfaceCode, params = [], sessionData = null) {
  74. try {
  75. const requestData = {
  76. code: interfaceCode,
  77. params: params,
  78. session: [sessionData || defaultSession]
  79. };
  80. console.error(`[DRG MCP Server] 调用IRIS接口: ${interfaceCode}`);
  81. const response = await irisClient.post(
  82. `/csp/${IRIS_NAMESPACE.toLowerCase()}/sysInternalMutiple`,
  83. requestData
  84. );
  85. const data = response.data;
  86. if (data.errorCode === "0") {
  87. return {
  88. success: true,
  89. errorCode: "0",
  90. errorMessage: "",
  91. data: data.result || {}
  92. };
  93. } else {
  94. console.error(`[DRG MCP Server] 接口调用失败: ${data.errorMessage}`);
  95. return {
  96. success: false,
  97. errorCode: data.errorCode || "-1",
  98. errorMessage: data.errorMessage || "接口调用失败",
  99. data: null
  100. };
  101. }
  102. } catch (error) {
  103. console.error(`[DRG MCP Server] 接口调用异常:`, error.message);
  104. return {
  105. success: false,
  106. errorCode: "-99",
  107. errorMessage: `接口调用异常: ${error.message}`,
  108. data: null
  109. };
  110. }
  111. }
  112. // 创建MCP服务器
  113. const server = new Server(
  114. {
  115. name: "drg-mcp-server",
  116. version: "1.0.0",
  117. description: "DRG医保控费预警系统MCP服务器 - 连接内网IRIS数据库(172.16.2.15)"
  118. },
  119. {
  120. capabilities: {
  121. tools: {},
  122. resources: {}
  123. }
  124. }
  125. );
  126. // 注册工具
  127. server.setRequestHandler(ListToolsRequestSchema, async () => {
  128. return {
  129. tools: [
  130. {
  131. name: "invoke_drg_interface",
  132. description: "调用DRG系统IRIS接口",
  133. inputSchema: {
  134. type: "object",
  135. properties: {
  136. interface_code: {
  137. type: "string",
  138. description: "接口代码,如01050103",
  139. required: true
  140. },
  141. params: {
  142. type: "array",
  143. description: "接口参数数组",
  144. required: false
  145. },
  146. session_data: {
  147. type: "object",
  148. description: "会话信息(可选,默认使用内置session)",
  149. required: false
  150. }
  151. },
  152. required: ["interface_code"]
  153. }
  154. },
  155. {
  156. name: "query_hospitals",
  157. description: "查询医院信息表记录",
  158. inputSchema: {
  159. type: "object",
  160. properties: {
  161. hospital_name: {
  162. type: "string",
  163. description: "医院名称(模糊查询)",
  164. required: false
  165. },
  166. organization_code: {
  167. type: "string",
  168. description: "机构代码",
  169. required: false
  170. },
  171. active: {
  172. type: "string",
  173. description: "是否有效(Y/N)",
  174. required: false
  175. },
  176. page: {
  177. type: "number",
  178. description: "页码",
  179. required: false,
  180. default: 1
  181. },
  182. limit: {
  183. type: "number",
  184. description: "每页数量",
  185. required: false,
  186. default: 20
  187. }
  188. }
  189. }
  190. },
  191. {
  192. name: "query_hospital_count",
  193. description: "查询医院信息表记录数",
  194. inputSchema: {
  195. type: "object",
  196. properties: {
  197. hospital_name: {
  198. type: "string",
  199. description: "医院名称(模糊查询)",
  200. required: false
  201. },
  202. active: {
  203. type: "string",
  204. description: "是否有效(Y/N)",
  205. required: false
  206. }
  207. }
  208. }
  209. },
  210. {
  211. name: "query_basic_data",
  212. description: "查询基础数据",
  213. inputSchema: {
  214. type: "object",
  215. properties: {
  216. data_type: {
  217. type: "string",
  218. description: "数据类型:province(省份)、city(城市)、area(区域)、policy(政策类型)",
  219. required: true
  220. },
  221. parent_code: {
  222. type: "string",
  223. description: "父级代码(用于城市、区域查询)",
  224. required: false
  225. }
  226. },
  227. required: ["data_type"]
  228. }
  229. },
  230. {
  231. name: "test_connection",
  232. description: "测试IRIS数据库连接",
  233. inputSchema: {
  234. type: "object",
  235. properties: {}
  236. }
  237. }
  238. ]
  239. };
  240. });
  241. // 注册资源
  242. server.setRequestHandler(ListResourcesRequestSchema, async () => {
  243. return {
  244. resources: [
  245. {
  246. uri: "iris://server-info",
  247. name: "IRIS服务器信息",
  248. description: "内网IRIS数据库服务器连接信息(172.16.2.15)",
  249. mimeType: "application/json"
  250. },
  251. {
  252. uri: "drg://interface-docs",
  253. name: "DRG接口文档",
  254. description: "DRG系统接口服务文档",
  255. mimeType: "text/markdown"
  256. },
  257. {
  258. uri: "drg://hospital-data",
  259. name: "医院数据模型",
  260. description: "医院信息表结构定义",
  261. mimeType: "application/json"
  262. }
  263. ]
  264. };
  265. });
  266. // 处理资源读取请求
  267. server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  268. const { uri } = request.params;
  269. console.error(`[DRG MCP Server] 读取资源: ${uri}`);
  270. if (uri === "iris://server-info") {
  271. return {
  272. contents: [
  273. {
  274. uri: uri,
  275. mimeType: "application/json",
  276. text: JSON.stringify({
  277. server_type: "InterSystems IRIS",
  278. host: IRIS_HOST,
  279. port: IRIS_PORT,
  280. namespace: IRIS_NAMESPACE,
  281. username: IRIS_USERNAME,
  282. base_url: IRIS_BASE_URL,
  283. endpoint: IRIS_ENDPOINT,
  284. connection_status: "active",
  285. timestamp: new Date().toISOString()
  286. }, null, 2)
  287. }
  288. ]
  289. };
  290. } else if (uri === "drg://interface-docs") {
  291. return {
  292. contents: [
  293. {
  294. uri: uri,
  295. mimeType: "text/markdown",
  296. text: `# DRG医保控费预警系统 - 接口文档
  297. ## 服务器信息
  298. - **地址**: ${IRIS_HOST}:${IRIS_PORT}
  299. - **命名空间**: ${IRIS_NAMESPACE}
  300. - **用户名**: ${IRIS_USERNAME}
  301. ## 医院管理接口
  302. - **01050103**: 查询医疗机构分页
  303. - **01050101**: 新增医疗机构
  304. - **01050102**: 修改医疗机构
  305. - **01050104**: 删除医疗机构
  306. ## 请求格式
  307. \`\`\`json
  308. {
  309. "code": "接口代码",
  310. "params": [{"参数名": "参数值"}],
  311. "session": [{"userID": "158", "userCode": "admin"}]
  312. }
  313. \`\`\`
  314. ## 响应格式
  315. \`\`\`json
  316. {
  317. "errorCode": "0",
  318. "errorMessage": "",
  319. "result": {}
  320. }
  321. \`\`\`
  322. `
  323. }
  324. ]
  325. };
  326. } else if (uri === "drg://hospital-data") {
  327. return {
  328. contents: [
  329. {
  330. uri: uri,
  331. mimeType: "application/json",
  332. text: JSON.stringify({
  333. table_name: "CB_Hospital",
  334. description: "医院信息表",
  335. fields: [
  336. { name: "ID", type: "string", description: "主键ID" },
  337. { name: "Code", type: "string", description: "医院代码" },
  338. { name: "Descripts", type: "string", description: "医院名称" },
  339. { name: "HospGrade_Dr", type: "number", description: "医院等级ID" },
  340. { name: "HospType", type: "string", description: "医院类型" },
  341. { name: "HospNature", type: "string", description: "医院性质" },
  342. { name: "ProvID_Dr", type: "number", description: "省份ID" },
  343. { name: "CityID_Dr", type: "number", description: "城市ID" },
  344. { name: "AreaID_Dr", type: "number", description: "区域ID" },
  345. { name: "Active", type: "string", description: "是否有效 (Y/N)" },
  346. { name: "OrganizationCode", type: "string", description: "组织机构代码" },
  347. { name: "Businesslicense", type: "string", description: "营业执照" }
  348. ],
  349. interface_code: "01050103",
  350. example_request: {
  351. code: "01050103",
  352. params: [{
  353. desc: "医院名称",
  354. active: "Y"
  355. }],
  356. session: [{
  357. userID: "158",
  358. userCode: "admin"
  359. }]
  360. }
  361. }, null, 2)
  362. }
  363. ]
  364. };
  365. }
  366. throw new Error(`Resource not found: ${uri}`);
  367. });
  368. // 处理工具调用请求
  369. server.setRequestHandler(CallToolRequestSchema, async (request) => {
  370. const { name, arguments: args = {} } = request.params;
  371. console.error(`[DRG MCP Server] 调用工具: ${name}`, args);
  372. try {
  373. switch (name) {
  374. case "invoke_drg_interface": {
  375. const { interface_code, params = [], session_data } = args;
  376. if (!interface_code) {
  377. throw new Error("interface_code参数不能为空");
  378. }
  379. const result = await invokeIRISInterface(
  380. interface_code,
  381. params,
  382. session_data
  383. );
  384. if (result.success) {
  385. return {
  386. content: [
  387. {
  388. type: "text",
  389. text: `✅ 接口调用成功 (${interface_code})\n\n` +
  390. `响应数据: ${JSON.stringify(result.data, null, 2)}`
  391. }
  392. ]
  393. };
  394. } else {
  395. return {
  396. content: [
  397. {
  398. type: "text",
  399. text: `❌ 接口调用失败 (${interface_code})\n\n` +
  400. `错误代码: ${result.errorCode}\n` +
  401. `错误信息: ${result.errorMessage}`
  402. }
  403. ]
  404. };
  405. }
  406. }
  407. case "query_hospitals": {
  408. const { hospital_name, organization_code, active, page = 1, limit = 20 } = args;
  409. const params = [];
  410. if (hospital_name || organization_code || active) {
  411. const filterParam = {};
  412. if (hospital_name) filterParam.desc = hospital_name;
  413. if (organization_code) filterParam.organizationCode = organization_code;
  414. if (active) filterParam.active = active;
  415. params.push(filterParam);
  416. }
  417. const result = await invokeIRISInterface("01050103", params);
  418. if (result.success) {
  419. const data = result.data;
  420. const total = data.total || 0;
  421. const rows = data.rows || [];
  422. // 分页处理
  423. const startIndex = (page - 1) * limit;
  424. const endIndex = Math.min(startIndex + limit, rows.length);
  425. const pageRows = rows.slice(startIndex, endIndex);
  426. let responseText = `✅ 查询到 ${total} 家医院 (第 ${page} 页,每页 ${limit} 条)\n\n`;
  427. if (pageRows.length > 0) {
  428. responseText += "**医院列表:**\n";
  429. pageRows.forEach((hospital, index) => {
  430. responseText += `${startIndex + index + 1}. ${hospital.code || ''} - ` +
  431. `${hospital.descripts || '未知医院'} - ` +
  432. `${hospital.gradeDesc || ''} - ` +
  433. `${hospital.proDesc || ''}${hospital.cityDesc || ''}\n`;
  434. });
  435. } else {
  436. responseText += "**未找到匹配的医院**";
  437. }
  438. return {
  439. content: [
  440. {
  441. type: "text",
  442. text: responseText
  443. }
  444. ]
  445. };
  446. } else {
  447. return {
  448. content: [
  449. {
  450. type: "text",
  451. text: `❌ 查询医院信息失败\n\n` +
  452. `错误代码: ${result.errorCode}\n` +
  453. `错误信息: ${result.errorMessage}`
  454. }
  455. ]
  456. };
  457. }
  458. }
  459. case "query_hospital_count": {
  460. const { hospital_name, active } = args;
  461. const params = [];
  462. if (hospital_name || active) {
  463. const filterParam = {};
  464. if (hospital_name) filterParam.desc = hospital_name;
  465. if (active) filterParam.active = active;
  466. params.push(filterParam);
  467. }
  468. const result = await invokeIRISInterface("01050103", params);
  469. if (result.success) {
  470. const data = result.data;
  471. const total = data.total || 0;
  472. const rows = data.rows || [];
  473. let responseText = `✅ 医院信息表统计结果:\n\n`;
  474. responseText += `总记录数: ${total}\n`;
  475. responseText += `当前查询匹配数: ${rows.length}\n`;
  476. // 状态统计
  477. const activeCount = rows.filter(h => h.active === 'Y').length;
  478. const inactiveCount = rows.filter(h => h.active === 'N').length;
  479. responseText += `有效医院 (Active=Y): ${activeCount}\n`;
  480. responseText += `无效医院 (Active=N): ${inactiveCount}\n`;
  481. if (hospital_name) {
  482. responseText += `\n搜索关键词: "${hospital_name}"\n`;
  483. }
  484. return {
  485. content: [
  486. {
  487. type: "text",
  488. text: responseText
  489. }
  490. ]
  491. };
  492. } else {
  493. return {
  494. content: [
  495. {
  496. type: "text",
  497. text: `❌ 查询医院记录数失败\n\n` +
  498. `错误代码: ${result.errorCode}\n` +
  499. `错误信息: ${result.errorMessage}`
  500. }
  501. ]
  502. };
  503. }
  504. }
  505. case "query_basic_data": {
  506. const { data_type, parent_code } = args;
  507. if (!data_type) {
  508. throw new Error("data_type参数不能为空");
  509. }
  510. // 根据数据类型调用不同的接口
  511. let interfaceCode, params = [];
  512. let dataTypeName = "";
  513. switch (data_type) {
  514. case "province":
  515. interfaceCode = "03020101"; // 查询省份
  516. dataTypeName = "省份";
  517. break;
  518. case "city":
  519. interfaceCode = "03020102"; // 查询城市
  520. dataTypeName = "城市";
  521. if (parent_code) params.push({ provIDID: parent_code });
  522. break;
  523. case "area":
  524. interfaceCode = "03020103"; // 查询区域
  525. dataTypeName = "区域";
  526. if (parent_code) params.push({ cityIDID: parent_code });
  527. break;
  528. case "policy":
  529. interfaceCode = "03020104"; // 查询政策类型
  530. dataTypeName = "政策类型";
  531. break;
  532. default:
  533. throw new Error(`不支持的数据类型: ${data_type}`);
  534. }
  535. const result = await invokeIRISInterface(interfaceCode, params);
  536. if (result.success) {
  537. const data = result.data;
  538. const rows = data.rows || [];
  539. let responseText = `✅ 查询到 ${rows.length} 条${dataTypeName}数据\n\n`;
  540. if (rows.length > 0) {
  541. responseText += `**${dataTypeName}列表:**\n`;
  542. rows.forEach((item, index) => {
  543. responseText += `${index + 1}. ${item.code || ''} - ` +
  544. `${item.descripts || '未知'}\n`;
  545. });
  546. } else {
  547. responseText += "**未找到数据**";
  548. }
  549. return {
  550. content: [
  551. {
  552. type: "text",
  553. text: responseText
  554. }
  555. ]
  556. };
  557. } else {
  558. return {
  559. content: [
  560. {
  561. type: "text",
  562. text: `❌ 查询${dataTypeName}数据失败\n\n` +
  563. `错误代码: ${result.errorCode}\n` +
  564. `错误信息: ${result.errorMessage}`
  565. }
  566. ]
  567. };
  568. }
  569. }
  570. case "test_connection": {
  571. const result = await invokeIRISInterface("01050103", []);
  572. if (result.success) {
  573. return {
  574. content: [
  575. {
  576. type: "text",
  577. text: `✅ IRIS数据库连接测试成功!\n\n` +
  578. `服务器: ${IRIS_HOST}:${IRIS_PORT}\n` +
  579. `命名空间: ${IRIS_NAMESPACE}\n` +
  580. `用户名: ${IRIS_USERNAME}\n` +
  581. `医院接口调用成功,可以正常访问`
  582. }
  583. ]
  584. };
  585. } else {
  586. return {
  587. content: [
  588. {
  589. type: "text",
  590. text: `❌ IRIS数据库连接测试失败\n\n` +
  591. `错误代码: ${result.errorCode}\n` +
  592. `错误信息: ${result.errorMessage}\n\n` +
  593. `请检查:\n` +
  594. `1. IRIS服务器状态 (${IRIS_HOST}:${IRIS_PORT})\n` +
  595. `2. 用户名和密码是否正确\n` +
  596. `3. 网络连接是否正常`
  597. }
  598. ]
  599. };
  600. }
  601. }
  602. default:
  603. throw new Error(`未知的工具: ${name}`);
  604. }
  605. } catch (error) {
  606. console.error(`[DRG MCP Server] 工具调用异常:`, error.message);
  607. return {
  608. content: [
  609. {
  610. type: "text",
  611. text: `❌ 工具调用失败: ${error.message}`
  612. }
  613. ]
  614. };
  615. }
  616. });
  617. // 启动服务器
  618. async function main() {
  619. try {
  620. console.error('[DRG MCP Server] 启动MCP服务器...');
  621. const transport = new StdioServerTransport();
  622. await server.connect(transport);
  623. console.error('[DRG MCP Server] MCP服务器已启动,等待连接...');
  624. // 处理关闭信号
  625. process.on('SIGINT', async () => {
  626. console.error('[DRG MCP Server] 收到关闭信号,正在停止服务器...');
  627. await server.close();
  628. process.exit(0);
  629. });
  630. process.on('SIGTERM', async () => {
  631. console.error('[DRG MCP Server] 收到终止信号,正在停止服务器...');
  632. await server.close();
  633. process.exit(0);
  634. });
  635. } catch (error) {
  636. console.error('[DRG MCP Server] 服务器启动失败:', error);
  637. process.exit(1);
  638. }
  639. }
  640. // 运行主函数
  641. if (require.main === module) {
  642. main().catch(console.error);
  643. }