guohanghui commited on
Commit
e21d9d7
·
verified ·
1 Parent(s): 2f28b29

Update NeuroKit/mcp_output/mcp_plugin/mcp_service.py

Browse files
NeuroKit/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import sys
 
3
 
4
  # Path settings to include the local source directory
5
  source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
@@ -7,64 +8,1263 @@ if source_path not in sys.path:
7
  sys.path.insert(0, source_path)
8
 
9
  from fastmcp import FastMCP
 
10
  import neurokit2 as nk
11
 
12
  # Create the FastMCP service application
13
  mcp = FastMCP("neurokit_service")
14
 
15
- @mcp.tool(name="ecg_process", description="Process ECG data")
16
- def ecg_process(ecg_data: list, sampling_rate: int) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  """
18
  Process ECG data using NeuroKit2's ecg_process function.
19
 
20
  Parameters:
21
  - ecg_data: list of ECG signal values
22
- - sampling_rate: int, the sampling rate of the ECG data
 
23
 
24
  Returns:
25
- - dict: Contains success, result, and error fields
26
  """
27
  try:
28
- processed_data, info = nk.ecg_process(ecg=ecg_data, sampling_rate=sampling_rate)
29
- return {"success": True, "result": {"processed_data": processed_data, "info": info}, "error": None}
 
 
 
 
 
 
 
30
  except Exception as e:
31
- return {"success": False, "result": None, "error": str(e)}
 
32
 
33
- @mcp.tool(name="rsp_process", description="Process respiration data")
34
- def rsp_process(rsp_data: list, sampling_rate: int) -> dict:
 
35
  """
36
- Process respiration data using NeuroKit2's rsp_process function.
37
 
38
  Parameters:
39
- - rsp_data: list of respiration signal values
40
- - sampling_rate: int, the sampling rate of the respiration data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  Returns:
43
- - dict: Contains success, result, and error fields
44
  """
45
  try:
46
- processed_data, info = nk.rsp_process(rsp=rsp_data, sampling_rate=sampling_rate)
47
- return {"success": True, "result": {"processed_data": processed_data, "info": info}, "error": None}
 
 
 
 
 
 
48
  except Exception as e:
49
- return {"success": False, "result": None, "error": str(e)}
 
50
 
51
- @mcp.tool(name="eda_process", description="Process EDA data")
52
- def eda_process(eda_data: list, sampling_rate: int) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  """
54
  Process EDA data using NeuroKit2's eda_process function.
55
 
56
  Parameters:
57
  - eda_data: list of EDA signal values
58
- - sampling_rate: int, the sampling rate of the EDA data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  Returns:
61
- - dict: Contains success, result, and error fields
62
  """
63
  try:
64
- processed_data, info = nk.eda_process(eda=eda_data, sampling_rate=sampling_rate)
65
- return {"success": True, "result": {"processed_data": processed_data, "info": info}, "error": None}
 
 
 
 
66
  except Exception as e:
67
- return {"success": False, "result": None, "error": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  def create_app() -> FastMCP:
70
  """
 
1
  import os
2
  import sys
3
+ from typing import List, Optional, Dict, Any, Tuple
4
 
5
  # Path settings to include the local source directory
6
  source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
 
8
  sys.path.insert(0, source_path)
9
 
10
  from fastmcp import FastMCP
11
+ import numpy as np
12
  import neurokit2 as nk
13
 
14
  # Create the FastMCP service application
15
  mcp = FastMCP("neurokit_service")
16
 
17
+
18
+ # =====================================================
19
+ # ECG Tools - 心电图工具
20
+ # =====================================================
21
+
22
+ @mcp.tool(name="ecg_simulate", description="Simulate an ECG/EKG signal.")
23
+ def ecg_simulate(duration: int = 10, sampling_rate: int = 1000,
24
+ heart_rate: int = 70, noise: float = 0.01,
25
+ method: str = "ecgsyn") -> dict:
26
+ """
27
+ Simulate an ECG/EKG signal.
28
+
29
+ Parameters:
30
+ - duration (int): Recording length in seconds (default: 10)
31
+ - sampling_rate (int): Sampling rate in Hz (default: 1000)
32
+ - heart_rate (int): Heart rate in beats per minute (default: 70)
33
+ - noise (float): Noise level (default: 0.01)
34
+ - method (str): Simulation method - "ecgsyn" or "simple" (default: "ecgsyn")
35
+
36
+ Returns:
37
+ - dict: Contains success, ECG signal array, and metadata
38
+ """
39
+ try:
40
+ ecg = nk.ecg_simulate(
41
+ duration=duration,
42
+ sampling_rate=sampling_rate,
43
+ heart_rate=heart_rate,
44
+ noise=noise,
45
+ method=method
46
+ )
47
+ return {
48
+ "success": True,
49
+ "signal": ecg.tolist(),
50
+ "duration": duration,
51
+ "sampling_rate": sampling_rate,
52
+ "heart_rate": heart_rate,
53
+ "num_samples": len(ecg)
54
+ }
55
+ except Exception as e:
56
+ return {"success": False, "error": str(e)}
57
+
58
+
59
+ @mcp.tool(name="ecg_process", description="Process ECG data including cleaning, peak detection, and feature extraction.")
60
+ def ecg_process(ecg_data: List[float], sampling_rate: int = 1000,
61
+ method: str = "neurokit") -> dict:
62
  """
63
  Process ECG data using NeuroKit2's ecg_process function.
64
 
65
  Parameters:
66
  - ecg_data: list of ECG signal values
67
+ - sampling_rate: int, the sampling rate of the ECG data (default: 1000)
68
+ - method: str, peak detection method (default: "neurokit")
69
 
70
  Returns:
71
+ - dict: Contains processed data columns and R-peaks info
72
  """
73
  try:
74
+ signals, info = nk.ecg_process(ecg=ecg_data, sampling_rate=sampling_rate, method=method)
75
+ return {
76
+ "success": True,
77
+ "columns": list(signals.columns),
78
+ "num_samples": len(signals),
79
+ "r_peaks": info["ECG_R_Peaks"].tolist() if hasattr(info["ECG_R_Peaks"], 'tolist') else list(info["ECG_R_Peaks"]),
80
+ "sampling_rate": info.get("sampling_rate", sampling_rate),
81
+ "heart_rate_mean": float(signals["ECG_Rate"].mean()) if "ECG_Rate" in signals.columns else None
82
+ }
83
  except Exception as e:
84
+ return {"success": False, "error": str(e)}
85
+
86
 
87
+ @mcp.tool(name="ecg_clean", description="Clean an ECG signal by removing noise and artifacts.")
88
+ def ecg_clean(ecg_data: List[float], sampling_rate: int = 1000,
89
+ method: str = "neurokit") -> dict:
90
  """
91
+ Clean an ECG signal by removing noise and artifacts.
92
 
93
  Parameters:
94
+ - ecg_data: list of ECG signal values
95
+ - sampling_rate: int, the sampling rate (default: 1000)
96
+ - method: str, cleaning method (default: "neurokit")
97
+ Options: "neurokit", "biosppy", "pantompkins", "hamilton", "elgendi", "engzeemod"
98
+
99
+ Returns:
100
+ - dict: Contains cleaned ECG signal
101
+ """
102
+ try:
103
+ cleaned = nk.ecg_clean(ecg=ecg_data, sampling_rate=sampling_rate, method=method)
104
+ return {
105
+ "success": True,
106
+ "signal": cleaned.tolist(),
107
+ "method": method,
108
+ "num_samples": len(cleaned)
109
+ }
110
+ except Exception as e:
111
+ return {"success": False, "error": str(e)}
112
+
113
+
114
+ @mcp.tool(name="ecg_findpeaks", description="Find R-peaks in an ECG signal.")
115
+ def ecg_findpeaks(ecg_data: List[float], sampling_rate: int = 1000,
116
+ method: str = "neurokit") -> dict:
117
+ """
118
+ Find R-peaks in an ECG signal.
119
+
120
+ Parameters:
121
+ - ecg_data: list of ECG signal values (should be cleaned first)
122
+ - sampling_rate: int, the sampling rate (default: 1000)
123
+ - method: str, peak detection method (default: "neurokit")
124
 
125
  Returns:
126
+ - dict: Contains R-peak indices and count
127
  """
128
  try:
129
+ peaks, info = nk.ecg_peaks(ecg_cleaned=ecg_data, sampling_rate=sampling_rate, method=method)
130
+ r_peaks = info["ECG_R_Peaks"]
131
+ return {
132
+ "success": True,
133
+ "r_peaks": r_peaks.tolist() if hasattr(r_peaks, 'tolist') else list(r_peaks),
134
+ "num_peaks": len(r_peaks),
135
+ "method": method
136
+ }
137
  except Exception as e:
138
+ return {"success": False, "error": str(e)}
139
+
140
 
141
+ @mcp.tool(name="ecg_quality", description="Assess the quality of an ECG signal.")
142
+ def ecg_quality(ecg_data: List[float], sampling_rate: int = 1000,
143
+ method: str = "zhao2018", approach: str = "fuzzy") -> dict:
144
+ """
145
+ Assess the quality of an ECG signal.
146
+
147
+ Parameters:
148
+ - ecg_data: list of ECG signal values
149
+ - sampling_rate: int, the sampling rate (default: 1000)
150
+ - method: str, quality assessment method (default: "zhao2018")
151
+ - approach: str, approach for quality index (default: "fuzzy")
152
+
153
+ Returns:
154
+ - dict: Contains quality assessment results
155
+ """
156
+ try:
157
+ quality = nk.ecg_quality(ecg_cleaned=ecg_data, sampling_rate=sampling_rate,
158
+ method=method, approach=approach)
159
+ return {
160
+ "success": True,
161
+ "quality": quality.tolist() if hasattr(quality, 'tolist') else list(quality),
162
+ "mean_quality": float(np.mean(quality)),
163
+ "method": method
164
+ }
165
+ except Exception as e:
166
+ return {"success": False, "error": str(e)}
167
+
168
+
169
+ # =====================================================
170
+ # EDA Tools - 皮肤电活动工具
171
+ # =====================================================
172
+
173
+ @mcp.tool(name="eda_simulate", description="Simulate an Electrodermal Activity (EDA/GSR) signal.")
174
+ def eda_simulate(duration: int = 120, sampling_rate: int = 1000,
175
+ scr_number: int = 5, noise: float = 0.01,
176
+ drift: float = 0.01) -> dict:
177
+ """
178
+ Simulate an EDA/GSR signal.
179
+
180
+ Parameters:
181
+ - duration (int): Recording length in seconds (default: 120)
182
+ - sampling_rate (int): Sampling rate in Hz (default: 1000)
183
+ - scr_number (int): Number of skin conductance responses (default: 5)
184
+ - noise (float): Noise level (default: 0.01)
185
+ - drift (float): Baseline drift level (default: 0.01)
186
+
187
+ Returns:
188
+ - dict: Contains simulated EDA signal
189
+ """
190
+ try:
191
+ eda = nk.eda_simulate(
192
+ duration=duration,
193
+ sampling_rate=sampling_rate,
194
+ scr_number=scr_number,
195
+ noise=noise,
196
+ drift=drift
197
+ )
198
+ return {
199
+ "success": True,
200
+ "signal": eda.tolist(),
201
+ "duration": duration,
202
+ "sampling_rate": sampling_rate,
203
+ "num_samples": len(eda)
204
+ }
205
+ except Exception as e:
206
+ return {"success": False, "error": str(e)}
207
+
208
+
209
+ @mcp.tool(name="eda_process", description="Process EDA data including cleaning and decomposition.")
210
+ def eda_process(eda_data: List[float], sampling_rate: int = 1000,
211
+ method: str = "neurokit") -> dict:
212
  """
213
  Process EDA data using NeuroKit2's eda_process function.
214
 
215
  Parameters:
216
  - eda_data: list of EDA signal values
217
+ - sampling_rate: int, the sampling rate (default: 1000)
218
+ - method: str, processing method (default: "neurokit")
219
+
220
+ Returns:
221
+ - dict: Contains processed data columns and SCR peaks info
222
+ """
223
+ try:
224
+ signals, info = nk.eda_process(eda=eda_data, sampling_rate=sampling_rate, method=method)
225
+ return {
226
+ "success": True,
227
+ "columns": list(signals.columns),
228
+ "num_samples": len(signals),
229
+ "scr_peaks": info.get("SCR_Peaks", []),
230
+ "scr_onsets": info.get("SCR_Onsets", [])
231
+ }
232
+ except Exception as e:
233
+ return {"success": False, "error": str(e)}
234
+
235
+
236
+ @mcp.tool(name="eda_clean", description="Clean an EDA signal.")
237
+ def eda_clean(eda_data: List[float], sampling_rate: int = 1000,
238
+ method: str = "neurokit") -> dict:
239
+ """
240
+ Clean an EDA signal.
241
+
242
+ Parameters:
243
+ - eda_data: list of EDA signal values
244
+ - sampling_rate: int, the sampling rate (default: 1000)
245
+ - method: str, cleaning method (default: "neurokit")
246
+
247
+ Returns:
248
+ - dict: Contains cleaned EDA signal
249
+ """
250
+ try:
251
+ cleaned = nk.eda_clean(eda=eda_data, sampling_rate=sampling_rate, method=method)
252
+ return {
253
+ "success": True,
254
+ "signal": cleaned.tolist(),
255
+ "method": method,
256
+ "num_samples": len(cleaned)
257
+ }
258
+ except Exception as e:
259
+ return {"success": False, "error": str(e)}
260
+
261
+
262
+ @mcp.tool(name="eda_phasic", description="Decompose EDA signal into phasic and tonic components.")
263
+ def eda_phasic(eda_data: List[float], sampling_rate: int = 1000,
264
+ method: str = "highpass") -> dict:
265
+ """
266
+ Decompose EDA signal into phasic (SCR) and tonic (SCL) components.
267
+
268
+ Parameters:
269
+ - eda_data: list of cleaned EDA signal values
270
+ - sampling_rate: int, the sampling rate (default: 1000)
271
+ - method: str, decomposition method (default: "highpass")
272
+ Options: "highpass", "cvxeda", "smoothmedian", "sparse"
273
+
274
+ Returns:
275
+ - dict: Contains phasic and tonic components
276
+ """
277
+ try:
278
+ decomposed = nk.eda_phasic(eda_cleaned=eda_data, sampling_rate=sampling_rate, method=method)
279
+ return {
280
+ "success": True,
281
+ "phasic": decomposed["EDA_Phasic"].tolist(),
282
+ "tonic": decomposed["EDA_Tonic"].tolist(),
283
+ "method": method,
284
+ "num_samples": len(decomposed)
285
+ }
286
+ except Exception as e:
287
+ return {"success": False, "error": str(e)}
288
+
289
+
290
+ # =====================================================
291
+ # PPG Tools - 光电容积脉搏波工具
292
+ # =====================================================
293
+
294
+ @mcp.tool(name="ppg_simulate", description="Simulate a photoplethysmogram (PPG) signal.")
295
+ def ppg_simulate(duration: int = 120, sampling_rate: int = 1000,
296
+ heart_rate: int = 70, frequency_modulation: float = 0.2,
297
+ drift: float = 0.0) -> dict:
298
+ """
299
+ Simulate a PPG signal.
300
+
301
+ Parameters:
302
+ - duration (int): Recording length in seconds (default: 120)
303
+ - sampling_rate (int): Sampling rate in Hz (default: 1000)
304
+ - heart_rate (int): Heart rate in beats per minute (default: 70)
305
+ - frequency_modulation (float): RSA modulation level (default: 0.2)
306
+ - drift (float): Baseline drift level (default: 0.0)
307
+
308
+ Returns:
309
+ - dict: Contains simulated PPG signal
310
+ """
311
+ try:
312
+ ppg = nk.ppg_simulate(
313
+ duration=duration,
314
+ sampling_rate=sampling_rate,
315
+ heart_rate=heart_rate,
316
+ frequency_modulation=frequency_modulation,
317
+ drift=drift
318
+ )
319
+ return {
320
+ "success": True,
321
+ "signal": ppg.tolist(),
322
+ "duration": duration,
323
+ "sampling_rate": sampling_rate,
324
+ "heart_rate": heart_rate,
325
+ "num_samples": len(ppg)
326
+ }
327
+ except Exception as e:
328
+ return {"success": False, "error": str(e)}
329
+
330
+
331
+ @mcp.tool(name="ppg_process", description="Process PPG data including cleaning and peak detection.")
332
+ def ppg_process(ppg_data: List[float], sampling_rate: int = 1000) -> dict:
333
+ """
334
+ Process PPG data using NeuroKit2's ppg_process function.
335
+
336
+ Parameters:
337
+ - ppg_data: list of PPG signal values
338
+ - sampling_rate: int, the sampling rate (default: 1000)
339
+
340
+ Returns:
341
+ - dict: Contains processed data columns and peaks info
342
+ """
343
+ try:
344
+ signals, info = nk.ppg_process(ppg=ppg_data, sampling_rate=sampling_rate)
345
+ return {
346
+ "success": True,
347
+ "columns": list(signals.columns),
348
+ "num_samples": len(signals),
349
+ "peaks": info.get("PPG_Peaks", []),
350
+ "heart_rate_mean": float(signals["PPG_Rate"].mean()) if "PPG_Rate" in signals.columns else None
351
+ }
352
+ except Exception as e:
353
+ return {"success": False, "error": str(e)}
354
+
355
+
356
+ @mcp.tool(name="ppg_clean", description="Clean a PPG signal.")
357
+ def ppg_clean(ppg_data: List[float], sampling_rate: int = 1000,
358
+ method: str = "elgendi") -> dict:
359
+ """
360
+ Clean a PPG signal.
361
+
362
+ Parameters:
363
+ - ppg_data: list of PPG signal values
364
+ - sampling_rate: int, the sampling rate (default: 1000)
365
+ - method: str, cleaning method (default: "elgendi")
366
+
367
+ Returns:
368
+ - dict: Contains cleaned PPG signal
369
+ """
370
+ try:
371
+ cleaned = nk.ppg_clean(ppg=ppg_data, sampling_rate=sampling_rate, method=method)
372
+ return {
373
+ "success": True,
374
+ "signal": cleaned.tolist(),
375
+ "method": method,
376
+ "num_samples": len(cleaned)
377
+ }
378
+ except Exception as e:
379
+ return {"success": False, "error": str(e)}
380
+
381
+
382
+ # =====================================================
383
+ # RSP Tools - 呼吸信号工具
384
+ # =====================================================
385
+
386
+ @mcp.tool(name="rsp_simulate", description="Simulate a respiration signal.")
387
+ def rsp_simulate(duration: int = 120, sampling_rate: int = 1000,
388
+ respiratory_rate: int = 15, noise: float = 0.01) -> dict:
389
+ """
390
+ Simulate a respiration signal.
391
+
392
+ Parameters:
393
+ - duration (int): Recording length in seconds (default: 120)
394
+ - sampling_rate (int): Sampling rate in Hz (default: 1000)
395
+ - respiratory_rate (int): Respiratory rate in breaths per minute (default: 15)
396
+ - noise (float): Noise level (default: 0.01)
397
+
398
+ Returns:
399
+ - dict: Contains simulated RSP signal
400
+ """
401
+ try:
402
+ rsp = nk.rsp_simulate(
403
+ duration=duration,
404
+ sampling_rate=sampling_rate,
405
+ respiratory_rate=respiratory_rate,
406
+ noise=noise
407
+ )
408
+ return {
409
+ "success": True,
410
+ "signal": rsp.tolist(),
411
+ "duration": duration,
412
+ "sampling_rate": sampling_rate,
413
+ "respiratory_rate": respiratory_rate,
414
+ "num_samples": len(rsp)
415
+ }
416
+ except Exception as e:
417
+ return {"success": False, "error": str(e)}
418
+
419
+
420
+ @mcp.tool(name="rsp_process", description="Process respiration data including cleaning and feature extraction.")
421
+ def rsp_process(rsp_data: List[float], sampling_rate: int = 1000,
422
+ method: str = "khodadad2018") -> dict:
423
+ """
424
+ Process respiration data using NeuroKit2's rsp_process function.
425
+
426
+ Parameters:
427
+ - rsp_data: list of respiration signal values
428
+ - sampling_rate: int, the sampling rate (default: 1000)
429
+ - method: str, peak detection method (default: "khodadad2018")
430
+
431
+ Returns:
432
+ - dict: Contains processed data and peaks info
433
+ """
434
+ try:
435
+ signals, info = nk.rsp_process(rsp=rsp_data, sampling_rate=sampling_rate, method=method)
436
+ return {
437
+ "success": True,
438
+ "columns": list(signals.columns),
439
+ "num_samples": len(signals),
440
+ "peaks": list(info.get("RSP_Peaks", [])),
441
+ "troughs": list(info.get("RSP_Troughs", [])),
442
+ "rate_mean": float(signals["RSP_Rate"].mean()) if "RSP_Rate" in signals.columns else None
443
+ }
444
+ except Exception as e:
445
+ return {"success": False, "error": str(e)}
446
+
447
+
448
+ @mcp.tool(name="rsp_clean", description="Clean a respiration signal.")
449
+ def rsp_clean(rsp_data: List[float], sampling_rate: int = 1000,
450
+ method: str = "khodadad2018") -> dict:
451
+ """
452
+ Clean a respiration signal.
453
+
454
+ Parameters:
455
+ - rsp_data: list of respiration signal values
456
+ - sampling_rate: int, the sampling rate (default: 1000)
457
+ - method: str, cleaning method (default: "khodadad2018")
458
+
459
+ Returns:
460
+ - dict: Contains cleaned RSP signal
461
+ """
462
+ try:
463
+ cleaned = nk.rsp_clean(rsp=rsp_data, sampling_rate=sampling_rate, method=method)
464
+ return {
465
+ "success": True,
466
+ "signal": cleaned.tolist(),
467
+ "method": method,
468
+ "num_samples": len(cleaned)
469
+ }
470
+ except Exception as e:
471
+ return {"success": False, "error": str(e)}
472
+
473
+
474
+ # =====================================================
475
+ # EMG Tools - 肌电图工具
476
+ # =====================================================
477
+
478
+ @mcp.tool(name="emg_simulate", description="Simulate an EMG signal.")
479
+ def emg_simulate(duration: int = 10, sampling_rate: int = 1000,
480
+ burst_number: int = 5, burst_duration: float = 1.0,
481
+ noise: float = 0.01) -> dict:
482
+ """
483
+ Simulate an EMG signal.
484
+
485
+ Parameters:
486
+ - duration (int): Recording length in seconds (default: 10)
487
+ - sampling_rate (int): Sampling rate in Hz (default: 1000)
488
+ - burst_number (int): Number of muscle activations (default: 5)
489
+ - burst_duration (float): Duration of each burst in seconds (default: 1.0)
490
+ - noise (float): Noise level (default: 0.01)
491
+
492
+ Returns:
493
+ - dict: Contains simulated EMG signal
494
+ """
495
+ try:
496
+ emg = nk.emg_simulate(
497
+ duration=duration,
498
+ sampling_rate=sampling_rate,
499
+ burst_number=burst_number,
500
+ burst_duration=burst_duration,
501
+ noise=noise
502
+ )
503
+ return {
504
+ "success": True,
505
+ "signal": emg.tolist(),
506
+ "duration": duration,
507
+ "sampling_rate": sampling_rate,
508
+ "num_samples": len(emg)
509
+ }
510
+ except Exception as e:
511
+ return {"success": False, "error": str(e)}
512
+
513
+
514
+ @mcp.tool(name="emg_process", description="Process EMG data including cleaning and activation detection.")
515
+ def emg_process(emg_data: List[float], sampling_rate: int = 1000) -> dict:
516
+ """
517
+ Process EMG data using NeuroKit2's emg_process function.
518
+
519
+ Parameters:
520
+ - emg_data: list of EMG signal values
521
+ - sampling_rate: int, the sampling rate (default: 1000)
522
+
523
+ Returns:
524
+ - dict: Contains processed data columns
525
+ """
526
+ try:
527
+ signals, info = nk.emg_process(emg=emg_data, sampling_rate=sampling_rate)
528
+ return {
529
+ "success": True,
530
+ "columns": list(signals.columns),
531
+ "num_samples": len(signals),
532
+ "onsets": list(info.get("EMG_Onsets", [])),
533
+ "offsets": list(info.get("EMG_Offsets", []))
534
+ }
535
+ except Exception as e:
536
+ return {"success": False, "error": str(e)}
537
+
538
+
539
+ @mcp.tool(name="emg_clean", description="Clean an EMG signal.")
540
+ def emg_clean(emg_data: List[float], sampling_rate: int = 1000) -> dict:
541
+ """
542
+ Clean an EMG signal.
543
+
544
+ Parameters:
545
+ - emg_data: list of EMG signal values
546
+ - sampling_rate: int, the sampling rate (default: 1000)
547
+
548
+ Returns:
549
+ - dict: Contains cleaned EMG signal
550
+ """
551
+ try:
552
+ cleaned = nk.emg_clean(emg=emg_data, sampling_rate=sampling_rate)
553
+ return {
554
+ "success": True,
555
+ "signal": cleaned.tolist(),
556
+ "num_samples": len(cleaned)
557
+ }
558
+ except Exception as e:
559
+ return {"success": False, "error": str(e)}
560
+
561
+
562
+ @mcp.tool(name="emg_amplitude", description="Compute EMG amplitude envelope.")
563
+ def emg_amplitude(emg_data: List[float], sampling_rate: int = 1000,
564
+ method: str = "rms", size: int = None) -> dict:
565
+ """
566
+ Compute EMG amplitude envelope.
567
+
568
+ Parameters:
569
+ - emg_data: list of cleaned EMG signal values
570
+ - sampling_rate: int, the sampling rate (default: 1000)
571
+ - method: str, method for amplitude computation (default: "rms")
572
+ - size: int, window size in samples (default: None, auto-calculated)
573
+
574
+ Returns:
575
+ - dict: Contains amplitude envelope
576
+ """
577
+ try:
578
+ amplitude = nk.emg_amplitude(emg_cleaned=emg_data, sampling_rate=sampling_rate)
579
+ return {
580
+ "success": True,
581
+ "amplitude": amplitude.tolist(),
582
+ "num_samples": len(amplitude)
583
+ }
584
+ except Exception as e:
585
+ return {"success": False, "error": str(e)}
586
+
587
+
588
+ # =====================================================
589
+ # HRV Tools - 心率变异性工具
590
+ # =====================================================
591
+
592
+ @mcp.tool(name="hrv_time", description="Compute time-domain HRV indices.")
593
+ def hrv_time(peaks: List[int], sampling_rate: int = 1000) -> dict:
594
+ """
595
+ Compute time-domain HRV indices from R-peaks.
596
+
597
+ Parameters:
598
+ - peaks: list of R-peak sample indices
599
+ - sampling_rate: int, the sampling rate (default: 1000)
600
+
601
+ Returns:
602
+ - dict: Contains time-domain HRV metrics (SDNN, RMSSD, pNN50, etc.)
603
+ """
604
+ try:
605
+ hrv = nk.hrv_time(peaks=peaks, sampling_rate=sampling_rate)
606
+ result = {"success": True}
607
+ for col in hrv.columns:
608
+ val = hrv[col].values[0]
609
+ result[col] = float(val) if not np.isnan(val) else None
610
+ return result
611
+ except Exception as e:
612
+ return {"success": False, "error": str(e)}
613
+
614
+
615
+ @mcp.tool(name="hrv_frequency", description="Compute frequency-domain HRV indices.")
616
+ def hrv_frequency(peaks: List[int], sampling_rate: int = 1000,
617
+ psd_method: str = "welch") -> dict:
618
+ """
619
+ Compute frequency-domain HRV indices from R-peaks.
620
+
621
+ Parameters:
622
+ - peaks: list of R-peak sample indices
623
+ - sampling_rate: int, the sampling rate (default: 1000)
624
+ - psd_method: str, PSD estimation method (default: "welch")
625
 
626
  Returns:
627
+ - dict: Contains frequency-domain HRV metrics (LF, HF, LF/HF ratio, etc.)
628
  """
629
  try:
630
+ hrv = nk.hrv_frequency(peaks=peaks, sampling_rate=sampling_rate, psd_method=psd_method)
631
+ result = {"success": True}
632
+ for col in hrv.columns:
633
+ val = hrv[col].values[0]
634
+ result[col] = float(val) if not np.isnan(val) else None
635
+ return result
636
  except Exception as e:
637
+ return {"success": False, "error": str(e)}
638
+
639
+
640
+ @mcp.tool(name="hrv_nonlinear", description="Compute nonlinear HRV indices.")
641
+ def hrv_nonlinear(peaks: List[int], sampling_rate: int = 1000) -> dict:
642
+ """
643
+ Compute nonlinear HRV indices from R-peaks.
644
+
645
+ Parameters:
646
+ - peaks: list of R-peak sample indices
647
+ - sampling_rate: int, the sampling rate (default: 1000)
648
+
649
+ Returns:
650
+ - dict: Contains nonlinear HRV metrics (SD1, SD2, ApEn, SampEn, etc.)
651
+ """
652
+ try:
653
+ hrv = nk.hrv_nonlinear(peaks=peaks, sampling_rate=sampling_rate)
654
+ result = {"success": True}
655
+ for col in hrv.columns:
656
+ val = hrv[col].values[0]
657
+ result[col] = float(val) if not np.isnan(val) else None
658
+ return result
659
+ except Exception as e:
660
+ return {"success": False, "error": str(e)}
661
+
662
+
663
+ # =====================================================
664
+ # Signal Processing Tools - 通用信号处理工具
665
+ # =====================================================
666
+
667
+ @mcp.tool(name="signal_simulate", description="Simulate a continuous signal with specified frequency components.")
668
+ def signal_simulate(duration: float = 10.0, sampling_rate: int = 1000,
669
+ frequency: float = 1.0, amplitude: float = 0.5,
670
+ noise: float = 0.0) -> dict:
671
+ """
672
+ Simulate a continuous signal with specified frequency components.
673
+
674
+ Parameters:
675
+ - duration (float): Length in seconds (default: 10.0)
676
+ - sampling_rate (int): Sampling rate in Hz (default: 1000)
677
+ - frequency (float): Oscillatory frequency in Hz (default: 1.0)
678
+ - amplitude (float): Signal amplitude (default: 0.5)
679
+ - noise (float): Noise level (default: 0.0)
680
+
681
+ Returns:
682
+ - dict: Contains simulated signal
683
+ """
684
+ try:
685
+ signal = nk.signal_simulate(
686
+ duration=duration,
687
+ sampling_rate=sampling_rate,
688
+ frequency=frequency,
689
+ amplitude=amplitude,
690
+ noise=noise
691
+ )
692
+ return {
693
+ "success": True,
694
+ "signal": signal.tolist(),
695
+ "duration": duration,
696
+ "sampling_rate": sampling_rate,
697
+ "num_samples": len(signal)
698
+ }
699
+ except Exception as e:
700
+ return {"success": False, "error": str(e)}
701
+
702
+
703
+ @mcp.tool(name="signal_filter", description="Apply digital filter to a signal.")
704
+ def signal_filter(signal_data: List[float], sampling_rate: int = 1000,
705
+ lowcut: float = None, highcut: float = None,
706
+ method: str = "butterworth", order: int = 2) -> dict:
707
+ """
708
+ Apply digital filter to a signal.
709
+
710
+ Parameters:
711
+ - signal_data: list of signal values
712
+ - sampling_rate: int, the sampling rate (default: 1000)
713
+ - lowcut: float, low cutoff frequency for highpass (default: None)
714
+ - highcut: float, high cutoff frequency for lowpass (default: None)
715
+ - method: str, filter method (default: "butterworth")
716
+ Options: "butterworth", "fir", "bessel", "savgol"
717
+ - order: int, filter order (default: 2)
718
+
719
+ Returns:
720
+ - dict: Contains filtered signal
721
+ """
722
+ try:
723
+ filtered = nk.signal_filter(
724
+ signal=signal_data,
725
+ sampling_rate=sampling_rate,
726
+ lowcut=lowcut,
727
+ highcut=highcut,
728
+ method=method,
729
+ order=order
730
+ )
731
+ return {
732
+ "success": True,
733
+ "signal": filtered.tolist(),
734
+ "lowcut": lowcut,
735
+ "highcut": highcut,
736
+ "method": method,
737
+ "num_samples": len(filtered)
738
+ }
739
+ except Exception as e:
740
+ return {"success": False, "error": str(e)}
741
+
742
+
743
+ @mcp.tool(name="signal_resample", description="Resample a signal to a different sampling rate.")
744
+ def signal_resample(signal_data: List[float], desired_length: int = None,
745
+ sampling_rate: int = None, desired_sampling_rate: int = None,
746
+ method: str = "interpolation") -> dict:
747
+ """
748
+ Resample a signal to a different sampling rate or length.
749
+
750
+ Parameters:
751
+ - signal_data: list of signal values
752
+ - desired_length: int, desired number of samples (default: None)
753
+ - sampling_rate: int, original sampling rate (default: None)
754
+ - desired_sampling_rate: int, target sampling rate (default: None)
755
+ - method: str, resampling method (default: "interpolation")
756
+
757
+ Returns:
758
+ - dict: Contains resampled signal
759
+ """
760
+ try:
761
+ resampled = nk.signal_resample(
762
+ signal=signal_data,
763
+ desired_length=desired_length,
764
+ sampling_rate=sampling_rate,
765
+ desired_sampling_rate=desired_sampling_rate,
766
+ method=method
767
+ )
768
+ return {
769
+ "success": True,
770
+ "signal": resampled.tolist(),
771
+ "original_length": len(signal_data),
772
+ "new_length": len(resampled),
773
+ "method": method
774
+ }
775
+ except Exception as e:
776
+ return {"success": False, "error": str(e)}
777
+
778
+
779
+ @mcp.tool(name="signal_psd", description="Compute the Power Spectral Density (PSD) of a signal.")
780
+ def signal_psd(signal_data: List[float], sampling_rate: int = 1000,
781
+ method: str = "welch", min_frequency: float = 0.0,
782
+ max_frequency: float = None) -> dict:
783
+ """
784
+ Compute the Power Spectral Density (PSD) of a signal.
785
+
786
+ Parameters:
787
+ - signal_data: list of signal values
788
+ - sampling_rate: int, the sampling rate (default: 1000)
789
+ - method: str, PSD method (default: "welch")
790
+ Options: "welch", "fft", "multitapers", "burg"
791
+ - min_frequency: float, minimum frequency (default: 0.0)
792
+ - max_frequency: float, maximum frequency (default: None = Nyquist)
793
+
794
+ Returns:
795
+ - dict: Contains power spectral density and frequencies
796
+ """
797
+ try:
798
+ psd = nk.signal_psd(
799
+ signal=signal_data,
800
+ sampling_rate=sampling_rate,
801
+ method=method,
802
+ min_frequency=min_frequency,
803
+ max_frequency=max_frequency if max_frequency else np.inf,
804
+ show=False
805
+ )
806
+ return {
807
+ "success": True,
808
+ "frequency": psd["Frequency"].tolist(),
809
+ "power": psd["Power"].tolist(),
810
+ "method": method
811
+ }
812
+ except Exception as e:
813
+ return {"success": False, "error": str(e)}
814
+
815
+
816
+ @mcp.tool(name="signal_findpeaks", description="Find peaks in a signal.")
817
+ def signal_findpeaks(signal_data: List[float], height_min: float = None,
818
+ relative_height_min: float = None) -> dict:
819
+ """
820
+ Find peaks in a signal.
821
+
822
+ Parameters:
823
+ - signal_data: list of signal values
824
+ - height_min: float, minimum peak height (default: None)
825
+ - relative_height_min: float, minimum relative height (default: None)
826
+
827
+ Returns:
828
+ - dict: Contains peak indices and amplitudes
829
+ """
830
+ try:
831
+ info = nk.signal_findpeaks(
832
+ signal=signal_data,
833
+ height_min=height_min,
834
+ relative_height_min=relative_height_min
835
+ )
836
+ return {
837
+ "success": True,
838
+ "peaks": list(info.get("Peaks", [])),
839
+ "num_peaks": len(info.get("Peaks", [])),
840
+ "heights": list(info.get("Height", [])) if "Height" in info else []
841
+ }
842
+ except Exception as e:
843
+ return {"success": False, "error": str(e)}
844
+
845
+
846
+ @mcp.tool(name="signal_detrend", description="Remove trend from a signal.")
847
+ def signal_detrend(signal_data: List[float], method: str = "polynomial",
848
+ order: int = 1) -> dict:
849
+ """
850
+ Remove trend from a signal.
851
+
852
+ Parameters:
853
+ - signal_data: list of signal values
854
+ - method: str, detrending method (default: "polynomial")
855
+ Options: "polynomial", "tarvainen2002", "loess"
856
+ - order: int, polynomial order for polynomial detrending (default: 1)
857
+
858
+ Returns:
859
+ - dict: Contains detrended signal
860
+ """
861
+ try:
862
+ detrended = nk.signal_detrend(signal=signal_data, method=method, order=order)
863
+ return {
864
+ "success": True,
865
+ "signal": detrended.tolist(),
866
+ "method": method,
867
+ "num_samples": len(detrended)
868
+ }
869
+ except Exception as e:
870
+ return {"success": False, "error": str(e)}
871
+
872
+
873
+ @mcp.tool(name="signal_smooth", description="Smooth a signal.")
874
+ def signal_smooth(signal_data: List[float], method: str = "convolution",
875
+ kernel: str = "boxcar", size: int = 10) -> dict:
876
+ """
877
+ Smooth a signal using various methods.
878
+
879
+ Parameters:
880
+ - signal_data: list of signal values
881
+ - method: str, smoothing method (default: "convolution")
882
+ Options: "convolution", "loess", "savgol"
883
+ - kernel: str, kernel type for convolution (default: "boxcar")
884
+ - size: int, kernel/window size (default: 10)
885
+
886
+ Returns:
887
+ - dict: Contains smoothed signal
888
+ """
889
+ try:
890
+ smoothed = nk.signal_smooth(signal=signal_data, method=method, kernel=kernel, size=size)
891
+ return {
892
+ "success": True,
893
+ "signal": smoothed.tolist(),
894
+ "method": method,
895
+ "num_samples": len(smoothed)
896
+ }
897
+ except Exception as e:
898
+ return {"success": False, "error": str(e)}
899
+
900
+
901
+ # =====================================================
902
+ # Complexity Tools - 复杂度分析工具
903
+ # =====================================================
904
+
905
+ @mcp.tool(name="entropy_sample", description="Compute Sample Entropy of a signal.")
906
+ def entropy_sample(signal_data: List[float], dimension: int = 2,
907
+ tolerance: float = None) -> dict:
908
+ """
909
+ Compute Sample Entropy (SampEn) of a signal.
910
+
911
+ Parameters:
912
+ - signal_data: list of signal values
913
+ - dimension: int, embedding dimension (default: 2)
914
+ - tolerance: float, tolerance threshold (default: None, 0.2*std)
915
+
916
+ Returns:
917
+ - dict: Contains Sample Entropy value
918
+ """
919
+ try:
920
+ if tolerance is None:
921
+ tolerance = 0.2 * np.std(signal_data)
922
+ sampen, info = nk.entropy_sample(signal=signal_data, dimension=dimension, tolerance=tolerance)
923
+ return {
924
+ "success": True,
925
+ "sample_entropy": float(sampen),
926
+ "dimension": dimension,
927
+ "tolerance": tolerance
928
+ }
929
+ except Exception as e:
930
+ return {"success": False, "error": str(e)}
931
+
932
+
933
+ @mcp.tool(name="entropy_shannon", description="Compute Shannon Entropy of a signal.")
934
+ def entropy_shannon(signal_data: List[float], base: float = 2.0) -> dict:
935
+ """
936
+ Compute Shannon Entropy of a signal.
937
+
938
+ Parameters:
939
+ - signal_data: list of signal values
940
+ - base: float, logarithm base (default: 2.0)
941
+
942
+ Returns:
943
+ - dict: Contains Shannon Entropy value
944
+ """
945
+ try:
946
+ shanen, info = nk.entropy_shannon(signal=signal_data, base=base)
947
+ return {
948
+ "success": True,
949
+ "shannon_entropy": float(shanen),
950
+ "base": base
951
+ }
952
+ except Exception as e:
953
+ return {"success": False, "error": str(e)}
954
+
955
+
956
+ @mcp.tool(name="entropy_approximate", description="Compute Approximate Entropy of a signal.")
957
+ def entropy_approximate(signal_data: List[float], dimension: int = 2,
958
+ tolerance: float = None) -> dict:
959
+ """
960
+ Compute Approximate Entropy (ApEn) of a signal.
961
+
962
+ Parameters:
963
+ - signal_data: list of signal values
964
+ - dimension: int, embedding dimension (default: 2)
965
+ - tolerance: float, tolerance threshold (default: None, 0.2*std)
966
+
967
+ Returns:
968
+ - dict: Contains Approximate Entropy value
969
+ """
970
+ try:
971
+ if tolerance is None:
972
+ tolerance = 0.2 * np.std(signal_data)
973
+ apen, info = nk.entropy_approximate(signal=signal_data, dimension=dimension, tolerance=tolerance)
974
+ return {
975
+ "success": True,
976
+ "approximate_entropy": float(apen),
977
+ "dimension": dimension,
978
+ "tolerance": tolerance
979
+ }
980
+ except Exception as e:
981
+ return {"success": False, "error": str(e)}
982
+
983
+
984
+ @mcp.tool(name="entropy_fuzzy", description="Compute Fuzzy Entropy of a signal.")
985
+ def entropy_fuzzy(signal_data: List[float], dimension: int = 2,
986
+ tolerance: float = None) -> dict:
987
+ """
988
+ Compute Fuzzy Entropy (FuzzyEn) of a signal.
989
+
990
+ Parameters:
991
+ - signal_data: list of signal values
992
+ - dimension: int, embedding dimension (default: 2)
993
+ - tolerance: float, tolerance threshold (default: None, 0.2*std)
994
+
995
+ Returns:
996
+ - dict: Contains Fuzzy Entropy value
997
+ """
998
+ try:
999
+ if tolerance is None:
1000
+ tolerance = 0.2 * np.std(signal_data)
1001
+ fuzzen, info = nk.entropy_fuzzy(signal=signal_data, dimension=dimension, tolerance=tolerance)
1002
+ return {
1003
+ "success": True,
1004
+ "fuzzy_entropy": float(fuzzen),
1005
+ "dimension": dimension,
1006
+ "tolerance": tolerance
1007
+ }
1008
+ except Exception as e:
1009
+ return {"success": False, "error": str(e)}
1010
+
1011
+
1012
+ @mcp.tool(name="entropy_permutation", description="Compute Permutation Entropy of a signal.")
1013
+ def entropy_permutation(signal_data: List[float], dimension: int = 3,
1014
+ delay: int = 1) -> dict:
1015
+ """
1016
+ Compute Permutation Entropy (PermEn) of a signal.
1017
+
1018
+ Parameters:
1019
+ - signal_data: list of signal values
1020
+ - dimension: int, embedding dimension (default: 3)
1021
+ - delay: int, time delay (default: 1)
1022
+
1023
+ Returns:
1024
+ - dict: Contains Permutation Entropy value
1025
+ """
1026
+ try:
1027
+ permen, info = nk.entropy_permutation(signal=signal_data, dimension=dimension, delay=delay)
1028
+ return {
1029
+ "success": True,
1030
+ "permutation_entropy": float(permen),
1031
+ "dimension": dimension,
1032
+ "delay": delay
1033
+ }
1034
+ except Exception as e:
1035
+ return {"success": False, "error": str(e)}
1036
+
1037
+
1038
+ @mcp.tool(name="fractal_dfa", description="Compute Detrended Fluctuation Analysis (DFA).")
1039
+ def fractal_dfa(signal_data: List[float], multifractal: bool = False) -> dict:
1040
+ """
1041
+ Compute Detrended Fluctuation Analysis (DFA) of a signal.
1042
+
1043
+ Parameters:
1044
+ - signal_data: list of signal values
1045
+ - multifractal: bool, compute multifractal DFA (default: False)
1046
+
1047
+ Returns:
1048
+ - dict: Contains DFA exponent (alpha)
1049
+ """
1050
+ try:
1051
+ dfa, info = nk.fractal_dfa(signal=signal_data, multifractal=multifractal)
1052
+ return {
1053
+ "success": True,
1054
+ "dfa_alpha": float(dfa),
1055
+ "multifractal": multifractal
1056
+ }
1057
+ except Exception as e:
1058
+ return {"success": False, "error": str(e)}
1059
+
1060
+
1061
+ @mcp.tool(name="fractal_higuchi", description="Compute Higuchi Fractal Dimension.")
1062
+ def fractal_higuchi(signal_data: List[float], k_max: int = 10) -> dict:
1063
+ """
1064
+ Compute Higuchi Fractal Dimension (HFD) of a signal.
1065
+
1066
+ Parameters:
1067
+ - signal_data: list of signal values
1068
+ - k_max: int, maximum k value (default: 10)
1069
+
1070
+ Returns:
1071
+ - dict: Contains Higuchi Fractal Dimension
1072
+ """
1073
+ try:
1074
+ hfd, info = nk.fractal_higuchi(signal=signal_data, k_max=k_max)
1075
+ return {
1076
+ "success": True,
1077
+ "higuchi_fd": float(hfd),
1078
+ "k_max": k_max
1079
+ }
1080
+ except Exception as e:
1081
+ return {"success": False, "error": str(e)}
1082
+
1083
+
1084
+ @mcp.tool(name="complexity_hjorth", description="Compute Hjorth parameters of a signal.")
1085
+ def complexity_hjorth(signal_data: List[float]) -> dict:
1086
+ """
1087
+ Compute Hjorth parameters (Activity, Mobility, Complexity) of a signal.
1088
+
1089
+ Parameters:
1090
+ - signal_data: list of signal values
1091
+
1092
+ Returns:
1093
+ - dict: Contains Hjorth Activity, Mobility, and Complexity
1094
+ """
1095
+ try:
1096
+ hjorth = nk.complexity_hjorth(signal=signal_data)
1097
+ return {
1098
+ "success": True,
1099
+ "activity": float(hjorth[0]),
1100
+ "mobility": float(hjorth[1]),
1101
+ "complexity": float(hjorth[2])
1102
+ }
1103
+ except Exception as e:
1104
+ return {"success": False, "error": str(e)}
1105
+
1106
+
1107
+ # =====================================================
1108
+ # Information Tools - 信息工具
1109
+ # =====================================================
1110
+
1111
+ @mcp.tool(name="get_neurokit_info", description="Get NeuroKit2 library information and capabilities.")
1112
+ def get_neurokit_info() -> dict:
1113
+ """
1114
+ Get NeuroKit2 library information and capabilities.
1115
+
1116
+ Returns:
1117
+ - dict: Contains library information and main features.
1118
+ """
1119
+ try:
1120
+ return {
1121
+ "success": True,
1122
+ "name": "NeuroKit2",
1123
+ "version": nk.__version__,
1124
+ "description": "A Python toolbox for neurophysiological signal processing",
1125
+ "modules": {
1126
+ "ecg": "ECG (Electrocardiogram) processing - simulate, clean, peaks, process, quality",
1127
+ "eda": "EDA (Electrodermal Activity/GSR) processing - simulate, phasic/tonic decomposition",
1128
+ "ppg": "PPG (Photoplethysmogram) processing - simulate, clean, peaks, process",
1129
+ "rsp": "RSP (Respiration) processing - simulate, clean, rate analysis",
1130
+ "emg": "EMG (Electromyography) processing - simulate, clean, activation detection",
1131
+ "eog": "EOG (Electrooculography) processing",
1132
+ "eeg": "EEG (Electroencephalography) processing",
1133
+ "hrv": "HRV (Heart Rate Variability) - time, frequency, nonlinear domains",
1134
+ "signal": "General signal processing - filter, resample, PSD, peaks",
1135
+ "complexity": "Complexity analysis - entropy, fractal dimensions",
1136
+ "stats": "Statistical analysis functions"
1137
+ },
1138
+ "features": [
1139
+ "Signal simulation for ECG, PPG, EDA, RSP, EMG",
1140
+ "Comprehensive signal cleaning and artifact removal",
1141
+ "Peak detection algorithms for cardiac signals",
1142
+ "Heart Rate Variability (HRV) analysis",
1143
+ "Complexity and entropy measures",
1144
+ "Power spectral density analysis",
1145
+ "Event-related analysis",
1146
+ "Interval-related analysis"
1147
+ ]
1148
+ }
1149
+ except Exception as e:
1150
+ return {"success": False, "error": str(e)}
1151
+
1152
+
1153
+ @mcp.tool(name="list_signal_processing_methods", description="List available signal processing methods.")
1154
+ def list_signal_processing_methods() -> dict:
1155
+ """
1156
+ List available signal processing methods for various operations.
1157
+
1158
+ Returns:
1159
+ - dict: Contains available methods for different operations.
1160
+ """
1161
+ try:
1162
+ return {
1163
+ "success": True,
1164
+ "ecg_cleaning_methods": ["neurokit", "biosppy", "pantompkins", "hamilton", "elgendi", "engzeemod"],
1165
+ "ecg_peak_methods": ["neurokit", "pantompkins", "hamilton", "christov", "gamboa", "elgendi", "engzee", "kalidas2017", "martinez2003", "nabian2018", "rodrigues2020"],
1166
+ "eda_decomposition_methods": ["highpass", "cvxeda", "smoothmedian", "sparse"],
1167
+ "rsp_cleaning_methods": ["khodadad2018", "biosppy"],
1168
+ "filter_methods": ["butterworth", "butterworth_ba", "fir", "bessel", "savgol"],
1169
+ "psd_methods": ["welch", "fft", "multitapers", "burg", "lombscargle"],
1170
+ "detrend_methods": ["polynomial", "tarvainen2002", "loess"],
1171
+ "resample_methods": ["interpolation", "numpy", "fft", "poly", "pandas"]
1172
+ }
1173
+ except Exception as e:
1174
+ return {"success": False, "error": str(e)}
1175
+
1176
+
1177
+ @mcp.tool(name="list_hrv_indices", description="List available HRV indices and their descriptions.")
1178
+ def list_hrv_indices() -> dict:
1179
+ """
1180
+ List available HRV indices and their descriptions.
1181
+
1182
+ Returns:
1183
+ - dict: Contains HRV indices organized by domain.
1184
+ """
1185
+ try:
1186
+ return {
1187
+ "success": True,
1188
+ "time_domain": {
1189
+ "MeanNN": "Mean of RR intervals (ms)",
1190
+ "SDNN": "Standard deviation of RR intervals (ms)",
1191
+ "RMSSD": "Root mean square of successive differences (ms)",
1192
+ "SDSD": "Standard deviation of successive differences (ms)",
1193
+ "pNN50": "Percentage of successive RR intervals differing by >50ms (%)",
1194
+ "pNN20": "Percentage of successive RR intervals differing by >20ms (%)",
1195
+ "CVNN": "Coefficient of variation of RR intervals",
1196
+ "CVSD": "RMSSD divided by MeanNN",
1197
+ "MedianNN": "Median of RR intervals (ms)",
1198
+ "MadNN": "Median absolute deviation of RR intervals (ms)",
1199
+ "HTI": "HRV triangular index",
1200
+ "TINN": "Triangular interpolation of RR intervals (ms)"
1201
+ },
1202
+ "frequency_domain": {
1203
+ "ULF": "Ultra low frequency power (<0.0033 Hz)",
1204
+ "VLF": "Very low frequency power (0.0033-0.04 Hz)",
1205
+ "LF": "Low frequency power (0.04-0.15 Hz)",
1206
+ "HF": "High frequency power (0.15-0.4 Hz)",
1207
+ "VHF": "Very high frequency power (0.4-0.5 Hz)",
1208
+ "LFHF": "LF/HF ratio",
1209
+ "LFn": "Normalized LF power",
1210
+ "HFn": "Normalized HF power",
1211
+ "LnHF": "Log-transformed HF"
1212
+ },
1213
+ "nonlinear": {
1214
+ "SD1": "Poincaré plot SD1 (short-term variability)",
1215
+ "SD2": "Poincaré plot SD2 (long-term variability)",
1216
+ "SD1SD2": "SD1/SD2 ratio",
1217
+ "ApEn": "Approximate Entropy",
1218
+ "SampEn": "Sample Entropy",
1219
+ "DFA_alpha1": "Short-term DFA exponent",
1220
+ "DFA_alpha2": "Long-term DFA exponent"
1221
+ }
1222
+ }
1223
+ except Exception as e:
1224
+ return {"success": False, "error": str(e)}
1225
+
1226
+
1227
+ @mcp.tool(name="list_entropy_measures", description="List available entropy and complexity measures.")
1228
+ def list_entropy_measures() -> dict:
1229
+ """
1230
+ List available entropy and complexity measures.
1231
+
1232
+ Returns:
1233
+ - dict: Contains entropy and complexity measures with descriptions.
1234
+ """
1235
+ try:
1236
+ return {
1237
+ "success": True,
1238
+ "entropy_measures": {
1239
+ "ShannonEntropy": "Measures uncertainty/randomness - higher values = more random",
1240
+ "SampleEntropy": "Regularity measure - higher = more complex, lower = more regular",
1241
+ "ApproximateEntropy": "Similar to SampEn but counts self-matches",
1242
+ "FuzzyEntropy": "Like SampEn but with fuzzy membership functions",
1243
+ "PermutationEntropy": "Complexity measure based on ordinal patterns",
1244
+ "SpectralEntropy": "Entropy of the power spectral density",
1245
+ "SVDEntropy": "Entropy based on singular value decomposition",
1246
+ "DispersionEntropy": "Based on dispersion patterns",
1247
+ "BubbleEntropy": "Based on bubble sort operations"
1248
+ },
1249
+ "fractal_measures": {
1250
+ "DFA": "Detrended Fluctuation Analysis - measures self-similarity",
1251
+ "HiguchisFD": "Higuchi Fractal Dimension - curve complexity",
1252
+ "KatzFD": "Katz Fractal Dimension",
1253
+ "PetrosianFD": "Petrosian Fractal Dimension",
1254
+ "SevcikFD": "Sevcik/Normalized Fractal Dimension",
1255
+ "HurstExponent": "Long-range dependence measure"
1256
+ },
1257
+ "other_measures": {
1258
+ "HjorthActivity": "Signal power/variance",
1259
+ "HjorthMobility": "Mean frequency estimate",
1260
+ "HjorthComplexity": "Bandwidth estimate",
1261
+ "LempelZivComplexity": "Algorithmic complexity",
1262
+ "LyapunovExponent": "Chaos measure - sensitivity to initial conditions"
1263
+ }
1264
+ }
1265
+ except Exception as e:
1266
+ return {"success": False, "error": str(e)}
1267
+
1268
 
1269
  def create_app() -> FastMCP:
1270
  """