tushare-mcp-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +148 -0
- package/README_CN.md +146 -0
- package/bin/cli.js +88 -0
- package/build_index.py +159 -0
- package/crawl_docs.py +321 -0
- package/docs/0019_/345/237/272/351/207/221/345/210/227/350/241/250.md +75 -0
- package/docs/0025_/350/202/241/347/245/250/345/210/227/350/241/250.md +95 -0
- package/docs/0026_/344/272/244/346/230/223/346/227/245/345/216/206.md +73 -0
- package/docs/0027_/345/216/206/345/217/262/346/227/245/347/272/277.md +81 -0
- package/docs/0028_/345/244/215/346/235/203/345/233/240/345/255/220.md +79 -0
- package/docs/0032_/346/257/217/346/227/245/346/214/207/346/240/207.md +89 -0
- package/docs/0033_/345/210/251/346/266/246/350/241/250.md +168 -0
- package/docs/0036_/350/265/204/344/272/247/350/264/237/345/200/272/350/241/250.md +234 -0
- package/docs/0044_/347/216/260/351/207/221/346/265/201/351/207/217/350/241/250.md +174 -0
- package/docs/0045_/344/270/232/347/273/251/351/242/204/345/221/212.md +85 -0
- package/docs/0046_/344/270/232/347/273/251/345/277/253/346/212/245.md +86 -0
- package/docs/0047_/346/262/252/346/267/261/346/270/257/351/200/232/350/265/204/351/207/221/346/265/201/345/220/221.md +75 -0
- package/docs/0048_/346/262/252/346/267/261/350/202/241/351/200/232/345/215/201/345/244/247/346/210/220/344/272/244/350/202/241.md +81 -0
- package/docs/0049_/346/270/257/350/202/241/351/200/232/345/215/201/345/244/247/346/210/220/344/272/244/350/202/241.md +129 -0
- package/docs/0058_/350/236/215/350/265/204/350/236/215/345/210/270/344/272/244/346/230/223/346/261/207/346/200/273.md +85 -0
- package/docs/0059_/350/236/215/350/265/204/350/236/215/345/210/270/344/272/244/346/230/223/346/230/216/347/273/206.md +128 -0
- package/docs/0061_/345/211/215/345/215/201/345/244/247/350/202/241/344/270/234.md +68 -0
- package/docs/0062_/345/211/215/345/215/201/345/244/247/346/265/201/351/200/232/350/202/241/344/270/234.md +68 -0
- package/docs/0079_/350/264/242/345/212/241/346/214/207/346/240/207/346/225/260/346/215/256.md +227 -0
- package/docs/0080_/350/264/242/345/212/241/345/256/241/350/256/241/346/204/217/350/247/201.md +60 -0
- package/docs/0081_/344/270/273/350/220/245/344/270/232/345/212/241/346/236/204/346/210/220.md +64 -0
- package/docs/0094_/346/214/207/346/225/260/345/237/272/346/234/254/344/277/241/346/201/257.md +107 -0
- package/docs/0095_/346/214/207/346/225/260/346/227/245/347/272/277/350/241/214/346/203/205.md +78 -0
- package/docs/0096_/346/214/207/346/225/260/346/210/220/345/210/206/345/222/214/346/235/203/351/207/215.md +59 -0
- package/docs/0100_/350/202/241/347/245/250/346/233/276/347/224/250/345/220/215.md +52 -0
- package/docs/0103_/345/210/206/347/272/242/351/200/201/350/202/241/346/225/260/346/215/256.md +72 -0
- package/docs/0106_/351/276/231/350/231/216/346/246/234/346/257/217/346/227/245/347/273/237/350/256/241.md +143 -0
- package/docs/0107_/351/276/231/350/231/216/346/246/234/346/234/272/346/236/204/344/272/244/346/230/223.md +75 -0
- package/docs/0109_/351/200/232/347/224/250/350/241/214/346/203/205/346/216/245/345/217/243.md +118 -0
- package/docs/0110_/350/202/241/346/235/203/350/264/250/346/212/274/347/273/237/350/256/241.md +72 -0
- package/docs/0111_/350/202/241/346/235/203/350/264/250/346/212/274/346/230/216/347/273/206.md +69 -0
- package/docs/0112_/344/270/212/345/270/202/345/205/254/345/217/270/345/237/272/346/234/254/344/277/241/346/201/257.md +77 -0
- package/docs/0118_/345/237/272/351/207/221/347/256/241/347/220/206/344/272/272.md +67 -0
- package/docs/0119_/345/237/272/351/207/221/345/207/200/345/200/274.md +63 -0
- package/docs/0120_/345/237/272/351/207/221/345/210/206/347/272/242.md +76 -0
- package/docs/0121_/345/237/272/351/207/221/346/214/201/344/273/223.md +76 -0
- package/docs/0123_IPO/346/226/260/350/202/241/344/270/212/345/270/202.md +81 -0
- package/docs/0124_/350/202/241/347/245/250/345/233/236/350/264/255.md +79 -0
- package/docs/0127_ETF/346/227/245/347/272/277/350/241/214/346/203/205.md +66 -0
- package/docs/0128_/345/244/247/347/233/230/346/214/207/346/225/260/346/257/217/346/227/245/346/214/207/346/240/207.md +66 -0
- package/docs/0135_/346/234/237/350/264/247/345/220/210/347/272/246/344/277/241/346/201/257.md +80 -0
- package/docs/0137_/346/234/237/350/264/247/344/272/244/346/230/223/346/227/245/345/216/206.md +113 -0
- package/docs/0138_/346/234/237/350/264/247/346/227/245/347/272/277/350/241/214/346/203/205.md +131 -0
- package/docs/0139_/346/234/237/350/264/247/346/257/217/346/227/245/346/214/201/344/273/223/346/216/222/345/220/215.md +98 -0
- package/docs/0140_/346/234/237/350/264/247/344/273/223/345/215/225/346/227/245/346/212/245.md +83 -0
- package/docs/0141_/346/234/237/350/264/247/346/257/217/346/227/245/347/273/223/347/256/227/345/217/202/346/225/260.md +123 -0
- package/docs/0143_/346/226/260/351/227/273/345/277/253/350/256/257.md +71 -0
- package/docs/0144_/345/221/250/347/272/277/350/241/214/346/203/205.md +108 -0
- package/docs/0145_/346/234/210/347/272/277/350/241/214/346/203/205.md +106 -0
- package/docs/0146_/345/244/215/346/235/203/350/241/214/346/203/205.md +68 -0
- package/docs/0149_Shibor/345/210/251/347/216/207.md +92 -0
- package/docs/0150_Shibor/346/212/245/344/273/267/346/225/260/346/215/256.md +112 -0
- package/docs/0151_LPR/350/264/267/346/254/276/345/237/272/347/241/200/345/210/251/347/216/207.md +84 -0
- package/docs/0152_Libor/345/210/251/347/216/207.md +108 -0
- package/docs/0153_Hibor/345/210/251/347/216/207.md +107 -0
- package/docs/0154_/346/226/260/351/227/273/350/201/224/346/222/255/346/226/207/345/255/227/347/250/277.md +61 -0
- package/docs/0155_/345/215/227/345/215/216/346/234/237/350/264/247/346/214/207/346/225/260/350/241/214/346/203/205.md +146 -0
- package/docs/0158_/346/234/237/346/235/203/345/220/210/347/272/246/344/277/241/346/201/257.md +82 -0
- package/docs/0159_/346/234/237/346/235/203/346/227/245/347/272/277/350/241/214/346/203/205.md +80 -0
- package/docs/0160_/351/231/220/345/224/256/350/202/241/350/247/243/347/246/201.md +107 -0
- package/docs/0161_/345/244/247/345/256/227/344/272/244/346/230/223.md +102 -0
- package/docs/0162_/350/264/242/346/212/245/346/212/253/351/234/262/346/227/245/346/234/237/350/241/250.md +66 -0
- package/docs/0166_/350/202/241/344/270/234/344/272/272/346/225/260.md +73 -0
- package/docs/0170_/344/270/252/350/202/241/350/265/204/351/207/221/346/265/201/345/220/221.md +130 -0
- package/docs/0171_/346/214/207/346/225/260/345/221/250/347/272/277/350/241/214/346/203/205.md +100 -0
- package/docs/0172_/346/214/207/346/225/260/346/234/210/347/272/277/350/241/214/346/203/205.md +104 -0
- package/docs/0173_/346/270/251/345/267/236/346/260/221/351/227/264/345/200/237/350/264/267/345/210/251/347/216/207.md +114 -0
- package/docs/0175_/350/202/241/344/270/234/345/242/236/345/207/217/346/214/201.md +99 -0
- package/docs/0176_/344/270/212/345/270/202/345/205/254/345/217/270/345/205/254/345/221/212.md +70 -0
- package/docs/0178_/345/244/226/346/261/207/345/237/272/347/241/200/344/277/241/346/201/257.md +92 -0
- package/docs/0179_/345/244/226/346/261/207/346/227/245/347/272/277/350/241/214/346/203/205.md +113 -0
- package/docs/0181_/347/224/263/344/270/207/350/241/214/344/270/232/345/210/206/347/261/273.md +600 -0
- package/docs/0183_/346/257/217/346/227/245/346/266/250/350/267/214/345/201/234/344/273/267/346/240/274.md +85 -0
- package/docs/0185_/345/217/257/350/275/254/345/200/272/345/237/272/347/241/200/344/277/241/346/201/257.md +83 -0
- package/docs/0186_/345/217/257/350/275/254/345/200/272/345/217/221/350/241/214.md +105 -0
- package/docs/0187_/345/217/257/350/275/254/345/200/272/350/241/214/346/203/205.md +115 -0
- package/docs/0188_/346/262/252/346/267/261/350/202/241/351/200/232/346/214/201/350/202/241/346/230/216/347/273/206.md +77 -0
- package/docs/0189_/346/234/237/350/264/247/344/270/273/345/212/233/344/270/216/350/277/236/347/273/255/345/220/210/347/272/246.md +80 -0
- package/docs/0191_/346/270/257/350/202/241/345/237/272/347/241/200/344/277/241/346/201/257.md +90 -0
- package/docs/0192_/346/270/257/350/202/241/346/227/245/347/272/277/350/241/214/346/203/205.md +91 -0
- package/docs/0193_/344/270/212/345/270/202/345/205/254/345/217/270/347/256/241/347/220/206/345/261/202.md +95 -0
- package/docs/0194_/347/256/241/347/220/206/345/261/202/350/226/252/351/205/254/345/222/214/346/214/201/350/202/241.md +82 -0
- package/docs/0195_/346/226/260/351/227/273/351/200/232/350/256/257/351/225/277/347/257/207.md +70 -0
- package/docs/0196_/346/270/257/350/202/241/351/200/232/346/257/217/346/227/245/346/210/220/344/272/244/347/273/237/350/256/241.md +89 -0
- package/docs/0197_/346/270/257/350/202/241/351/200/232/346/257/217/346/234/210/346/210/220/344/272/244/347/273/237/350/256/241.md +89 -0
- package/docs/0199_ETF/345/244/215/346/235/203/345/233/240/345/255/220.md +69 -0
- package/docs/0201_/345/233/275/345/200/272/346/224/266/347/233/212/347/216/207/346/233/262/347/272/277.md +74 -0
- package/docs/0207_/345/237/272/351/207/221/350/247/204/346/250/241.md +75 -0
- package/docs/0208_/345/237/272/351/207/221/347/273/217/347/220/206.md +74 -0
- package/docs/0211_/345/233/275/351/231/205/344/270/273/350/246/201/346/214/207/346/225/260.md +125 -0
- package/docs/0214_/346/257/217/346/227/245/345/201/234/345/244/215/347/211/214/344/277/241/346/201/257.md +74 -0
- package/docs/0215_/346/262/252/346/267/261/345/270/202/345/234/272/346/257/217/346/227/245/344/272/244/346/230/223/347/273/237/350/256/241.md +121 -0
- package/docs/0219_/347/276/216/345/233/275/345/233/275/345/200/272/346/224/266/347/233/212/347/216/207/346/233/262/347/272/277.md +82 -0
- package/docs/0227_/345/233/275/345/206/205/347/224/237/344/272/247/346/200/273/345/200/274GDP.md +77 -0
- package/docs/0228_/345/261/205/346/260/221/346/266/210/350/264/271/344/273/267/346/240/274/346/214/207/346/225/260CPI.md +84 -0
- package/docs/0242_/350/264/247/345/270/201/344/276/233/345/272/224/351/207/217/346/234/210.md +71 -0
- package/docs/0245_/345/267/245/344/270/232/347/224/237/344/272/247/350/200/205/345/207/272/345/216/202/344/273/267/346/240/274/346/214/207/346/225/260PPI.md +94 -0
- package/docs/0246_/345/217/257/350/275/254/345/200/272/350/275/254/350/202/241/344/273/267/345/217/230/345/212/250.md +57 -0
- package/docs/0247_/345/217/257/350/275/254/345/200/272/350/275/254/350/202/241/347/273/223/346/236/234.md +81 -0
- package/docs/0250_/346/270/257/350/202/241/344/272/244/346/230/223/346/227/245/345/216/206.md +65 -0
- package/docs/0252_/347/276/216/350/202/241/345/237/272/347/241/200/344/277/241/346/201/257.md +73 -0
- package/docs/0253_/347/276/216/350/202/241/344/272/244/346/230/223/346/227/245/345/216/206.md +67 -0
- package/docs/0254_/347/276/216/350/202/241/346/227/245/347/272/277/350/241/214/346/203/205.md +83 -0
- package/docs/0255_/345/244/207/347/224/250/350/241/214/346/203/205.md +86 -0
- package/docs/0256_/345/200/272/345/210/270/345/233/236/350/264/255/346/227/245/350/241/214/346/203/205.md +112 -0
- package/docs/0259_/345/220/214/350/212/261/351/241/272/350/241/214/344/270/232/346/246/202/345/277/265/346/235/277/345/235/227.md +70 -0
- package/docs/0260_/345/220/214/350/212/261/351/241/272/346/246/202/345/277/265/345/222/214/350/241/214/344/270/232/346/214/207/346/225/260/350/241/214/346/203/205.md +79 -0
- package/docs/0261_/345/220/214/350/212/261/351/241/272/350/241/214/344/270/232/346/246/202/345/277/265/346/210/220/345/210/206.md +60 -0
- package/docs/0262_/350/202/241/347/245/250/345/216/206/345/217/262/345/210/227/350/241/250.md +75 -0
- package/docs/0265_/345/220/204/346/270/240/351/201/223/345/205/254/345/213/237/345/237/272/351/207/221/351/224/200/345/224/256/344/277/235/346/234/211/350/247/204/346/250/241/345/215/240/346/257/224.md +62 -0
- package/docs/0266_/351/224/200/345/224/256/346/234/272/346/236/204/345/205/254/345/213/237/345/237/272/351/207/221/351/224/200/345/224/256/344/277/235/346/234/211/350/247/204/346/250/241.md +81 -0
- package/docs/0267_/345/210/270/345/225/206/346/234/210/345/272/246/351/207/221/350/202/241.md +54 -0
- package/docs/0269_/345/217/257/350/275/254/345/200/272/350/265/216/345/233/236/344/277/241/346/201/257.md +68 -0
- package/docs/0274_/344/270/255/345/244/256/347/273/223/347/256/227/347/263/273/347/273/237/346/214/201/350/202/241/346/230/216/347/273/206.md +71 -0
- package/docs/0275_/346/234/272/346/236/204/350/260/203/347/240/224/346/225/260/346/215/256.md +72 -0
- package/docs/0284_/344/270/212/346/265/267/351/273/204/351/207/221/345/237/272/347/241/200/344/277/241/346/201/257.md +73 -0
- package/docs/0285_/344/270/212/346/265/267/351/273/204/351/207/221/347/216/260/350/264/247/346/227/245/350/241/214/346/203/205.md +88 -0
- package/docs/0292_/345/210/270/345/225/206/347/233/210/345/210/251/351/242/204/346/265/213.md +89 -0
- package/docs/0293_/346/257/217/346/227/245/347/255/271/347/240/201/345/217/212/350/203/234/347/216/207.md +66 -0
- package/docs/0294_/346/257/217/346/227/245/347/255/271/347/240/201/345/210/206/345/270/203.md +59 -0
- package/docs/0295_/344/270/255/345/244/256/347/273/223/347/256/227/347/263/273/347/273/237/346/214/201/350/202/241/347/273/237/350/256/241.md +69 -0
- package/docs/0298_/346/266/250/350/267/214/345/201/234/345/222/214/347/202/270/346/235/277/346/225/260/346/215/256.md +83 -0
- package/docs/0304_/346/270/257/350/202/241/345/210/206/351/222/237/350/241/214/346/203/205.md +87 -0
- package/docs/0305_/345/217/257/350/275/254/345/200/272/347/245/250/351/235/242/345/210/251/347/216/207.md +63 -0
- package/docs/0308_/344/270/255/344/277/241/350/241/214/344/270/232/346/214/207/346/225/260/346/227/245/350/241/214/346/203/205.md +79 -0
- package/docs/0310_/347/244/276/350/236/215/345/242/236/351/207/217/346/234/210/345/272/246.md +82 -0
- package/docs/0311_/345/270/202/345/234/272/346/270/270/350/265/204/346/234/200/345/205/250/345/220/215/345/275/225.md +147 -0
- package/docs/0312_/346/270/270/350/265/204/344/272/244/346/230/223/346/257/217/346/227/245/346/230/216/347/273/206.md +59 -0
- package/docs/0325_/351/207/207/350/264/255/347/273/217/347/220/206/346/214/207/346/225/260PMI.md +120 -0
- package/docs/0326_/350/236/215/350/265/204/350/236/215/345/210/270/346/240/207/347/232/204/347/233/230/345/211/215.md +72 -0
- package/docs/0327_/347/224/263/344/270/207/350/241/214/344/270/232/346/214/207/346/225/260/346/227/245/350/241/214/346/203/205.md +78 -0
- package/docs/0328_/350/202/241/347/245/250/346/212/200/346/234/257/351/235/242/345/233/240/345/255/220.md +290 -0
- package/docs/0329_/346/257/217/346/227/245/350/202/241/346/234/254/347/233/230/345/211/215.md +62 -0
- package/docs/0331_/350/275/254/350/236/215/350/265/204/344/272/244/346/230/223/346/261/207/346/200/273.md +73 -0
- package/docs/0335_/347/224/263/344/270/207/350/241/214/344/270/232/346/210/220/345/210/206/345/210/206/347/272/247.md +82 -0
- package/docs/0336_/345/221/250/346/234/210/347/272/277/350/241/214/346/203/205/346/257/217/346/227/245/346/233/264/346/226/260.md +78 -0
- package/docs/0337_/346/234/237/350/264/247/345/221/250/346/234/210/347/272/277/350/241/214/346/203/205.md +48 -0
- package/docs/0338_/347/276/216/350/202/241/345/244/215/346/235/203/350/241/214/346/203/205.md +94 -0
- package/docs/0339_/346/270/257/350/202/241/345/244/215/346/235/203/350/241/214/346/203/205.md +92 -0
- package/docs/0341_/346/234/237/346/235/203/345/210/206/351/222/237/350/241/214/346/203/205.md +88 -0
- package/docs/0343_/350/241/214/344/270/232/350/265/204/351/207/221/346/265/201/345/220/221THS.md +67 -0
- package/docs/0344_/346/235/277/345/235/227/350/265/204/351/207/221/346/265/201/345/220/221DC.md +72 -0
- package/docs/0345_/345/244/247/347/233/230/350/265/204/351/207/221/346/265/201/345/220/221DC.md +75 -0
- package/docs/0348_/344/270/252/350/202/241/350/265/204/351/207/221/346/265/201/345/220/221THS.md +62 -0
- package/docs/0349_/344/270/252/350/202/241/350/265/204/351/207/221/346/265/201/345/220/221DC.md +82 -0
- package/docs/0362_/344/270/234/346/226/271/350/264/242/345/257/214/346/246/202/345/277/265/346/235/277/345/235/227.md +70 -0
- package/docs/0363_/344/270/234/346/226/271/350/264/242/345/257/214/346/246/202/345/277/265/346/210/220/345/210/206.md +72 -0
- package/docs/0365_/345/221/250/346/234/210/347/272/277/345/244/215/346/235/203/350/241/214/346/203/205/346/257/217/346/227/245/346/233/264/346/226/260.md +86 -0
- package/docs/0368_/346/234/237/350/264/247/345/220/210/347/272/246/346/266/250/350/267/214/345/201/234/344/273/267/346/240/274.md +80 -0
- package/docs/0370_/345/216/206/345/217/262/345/210/206/351/222/237.md +73 -0
- package/docs/0371_/346/235/277/345/235/227/350/265/204/351/207/221/346/265/201/345/220/221THS.md +65 -0
- package/docs/0372_/345/256/236/346/227/266/346/227/245/347/272/277.md +73 -0
- package/docs/0373_/344/270/255/344/277/241/350/241/214/344/270/232/346/210/220/345/210/206.md +124 -0
- package/docs/0374_/345/256/236/346/227/266/345/210/206/351/222/237.md +56 -0
- package/docs/0375_/345/214/227/344/272/244/346/211/200/346/226/260/346/227/247/344/273/243/347/240/201/345/257/271/347/205/247.md +60 -0
- package/docs/0385_ETF/345/237/272/346/234/254/344/277/241/346/201/257.md +110 -0
- package/docs/0386_ETF/345/237/272/345/207/206/346/214/207/346/225/260.md +70 -0
- package/docs/0387_ETF/345/216/206/345/217/262/345/210/206/351/222/237.md +73 -0
- package/docs/0388_/346/270/257/350/202/241/350/264/242/345/212/241/346/214/207/346/240/207/346/225/260/346/215/256.md +148 -0
- package/docs/0389_/346/270/257/350/202/241/345/210/251/346/266/246/350/241/250.md +92 -0
- package/docs/0390_/346/270/257/350/202/241/350/265/204/344/272/247/350/264/237/345/200/272/350/241/250.md +120 -0
- package/docs/0391_/346/270/257/350/202/241/347/216/260/351/207/221/346/265/201/351/207/217/350/241/250.md +115 -0
- package/docs/0393_/347/276/216/350/202/241/350/264/242/345/212/241/346/214/207/346/240/207/346/225/260/346/215/256.md +143 -0
- package/docs/0394_/347/276/216/350/202/241/345/210/251/346/266/246/350/241/250.md +79 -0
- package/docs/0395_/347/276/216/350/202/241/350/265/204/344/272/247/350/264/237/345/200/272/350/241/250.md +67 -0
- package/docs/0396_/347/276/216/350/202/241/347/216/260/351/207/221/346/265/201/351/207/217/350/241/250.md +79 -0
- package/docs/0397_ST/350/202/241/347/245/250/345/210/227/350/241/250.md +72 -0
- package/docs/0398_/346/262/252/346/267/261/346/270/257/351/200/232/350/202/241/347/245/250/345/210/227/350/241/250.md +82 -0
- package/docs/0399_AH/350/202/241/346/257/224/344/273/267.md +79 -0
- package/docs/0406_/345/233/275/345/256/266/346/224/277/347/255/226/345/272/223.md +70 -0
- package/docs/0408_ETF/344/273/275/351/242/235/350/247/204/346/250/241.md +65 -0
- package/docs/0415_/345/210/270/345/225/206/347/240/224/347/251/266/346/212/245/345/221/212.md +68 -0
- package/docs/0423_ST/351/243/216/351/231/251/350/255/246/347/244/272/346/235/277/350/202/241/347/245/250.md +65 -0
- package/docs/_api_index.json +1213 -0
- package/docs/_index.md +178 -0
- package/package.json +42 -0
- package/requirements.txt +2 -0
- package/server.py +736 -0
package/server.py
ADDED
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tushare MCP Server - 提供 Tushare 金融数据 API 接口
|
|
3
|
+
通过 MCP 协议暴露 Tushare Pro API,支持沪深股票、指数、基金、期货、期权等数据查询。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
from mcp.server.fastmcp import FastMCP
|
|
14
|
+
|
|
15
|
+
# ── 配置 ──────────────────────────────────────────────────────────────
|
|
16
|
+
TUSHARE_API_URL = "http://api.tushare.pro"
|
|
17
|
+
|
|
18
|
+
# 支持 .env 文件加载 token
|
|
19
|
+
def _load_env_token() -> str:
|
|
20
|
+
"""从 .env 文件或环境变量读取 TUSHARE_TOKEN"""
|
|
21
|
+
token = os.environ.get("TUSHARE_TOKEN", "")
|
|
22
|
+
if token:
|
|
23
|
+
return token
|
|
24
|
+
env_path = Path(__file__).parent / ".env"
|
|
25
|
+
if env_path.exists():
|
|
26
|
+
for line in env_path.read_text().splitlines():
|
|
27
|
+
line = line.strip()
|
|
28
|
+
if line.startswith("#") or "=" not in line:
|
|
29
|
+
continue
|
|
30
|
+
k, v = line.split("=", 1)
|
|
31
|
+
if k.strip() == "TUSHARE_TOKEN":
|
|
32
|
+
return v.strip().strip("'\"")
|
|
33
|
+
return ""
|
|
34
|
+
|
|
35
|
+
TUSHARE_TOKEN = _load_env_token()
|
|
36
|
+
|
|
37
|
+
mcp = FastMCP(
|
|
38
|
+
"Tushare",
|
|
39
|
+
instructions=(
|
|
40
|
+
"Tushare 金融数据 MCP 服务器。提供 A 股、指数、基金、期货、期权、债券、"
|
|
41
|
+
"外汇、港股、美股、宏观经济等全方位金融数据查询。\n"
|
|
42
|
+
"使用前请确保已设置 TUSHARE_TOKEN 环境变量。\n"
|
|
43
|
+
"所有日期参数格式为 YYYYMMDD,股票代码格式如 000001.SZ、600000.SH。\n"
|
|
44
|
+
"如需查看某个接口的详细参数说明,请使用 search_api_docs 工具搜索关键词。"
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# ── 加载爬取的文档与索引 ──────────────────────────────────────────────
|
|
49
|
+
DOCS_DIR = Path(__file__).parent / "docs"
|
|
50
|
+
_docs_cache: dict[str, str] = {} # filename_stem -> content
|
|
51
|
+
_api_index: dict[str, dict] = {} # api_name -> {title, doc_id, description, file}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _load_docs() -> dict[str, str]:
|
|
55
|
+
"""加载 docs/ 目录下所有 Markdown 文档到内存"""
|
|
56
|
+
if _docs_cache:
|
|
57
|
+
return _docs_cache
|
|
58
|
+
if not DOCS_DIR.exists():
|
|
59
|
+
return _docs_cache
|
|
60
|
+
for f in sorted(DOCS_DIR.glob("*.md")):
|
|
61
|
+
if f.name.startswith("_"):
|
|
62
|
+
continue
|
|
63
|
+
_docs_cache[f.stem] = f.read_text(encoding="utf-8")
|
|
64
|
+
return _docs_cache
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _load_api_index() -> dict[str, dict]:
|
|
68
|
+
"""加载 api_name -> 文档信息 的结构化索引"""
|
|
69
|
+
if _api_index:
|
|
70
|
+
return _api_index
|
|
71
|
+
index_path = DOCS_DIR / "_api_index.json"
|
|
72
|
+
if index_path.exists():
|
|
73
|
+
import json as _json
|
|
74
|
+
data = _json.loads(index_path.read_text(encoding="utf-8"))
|
|
75
|
+
_api_index.update(data)
|
|
76
|
+
return _api_index
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ── 复用 httpx 客户端 ─────────────────────────────────────────────────
|
|
80
|
+
_http_client: httpx.AsyncClient | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _get_client() -> httpx.AsyncClient:
|
|
84
|
+
global _http_client
|
|
85
|
+
if _http_client is None or _http_client.is_closed:
|
|
86
|
+
_http_client = httpx.AsyncClient(timeout=30)
|
|
87
|
+
return _http_client
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── 核心请求函数 ──────────────────────────────────────────────────────
|
|
91
|
+
async def _call_tushare(
|
|
92
|
+
api_name: str,
|
|
93
|
+
params: dict[str, Any] | None = None,
|
|
94
|
+
fields: str = "",
|
|
95
|
+
limit: int = 5000,
|
|
96
|
+
) -> dict[str, Any]:
|
|
97
|
+
"""调用 Tushare HTTP API 并返回结果。"""
|
|
98
|
+
if not TUSHARE_TOKEN:
|
|
99
|
+
return {"error": "未设置 TUSHARE_TOKEN 环境变量,请在启动 MCP 时配置,或在项目根目录创建 .env 文件。"}
|
|
100
|
+
|
|
101
|
+
payload: dict[str, Any] = {
|
|
102
|
+
"api_name": api_name,
|
|
103
|
+
"token": TUSHARE_TOKEN,
|
|
104
|
+
"params": params or {},
|
|
105
|
+
}
|
|
106
|
+
if fields:
|
|
107
|
+
payload["fields"] = fields
|
|
108
|
+
|
|
109
|
+
client = _get_client()
|
|
110
|
+
try:
|
|
111
|
+
resp = await client.post(TUSHARE_API_URL, json=payload)
|
|
112
|
+
resp.raise_for_status()
|
|
113
|
+
except httpx.TimeoutException:
|
|
114
|
+
return {"error": "请求超时,Tushare API 响应过慢。", "hint": "请稍后重试,或缩小查询范围(添加日期/代码过滤)。"}
|
|
115
|
+
except httpx.HTTPStatusError as e:
|
|
116
|
+
return {"error": f"HTTP {e.response.status_code}", "hint": "Tushare API 服务异常,请稍后重试。"}
|
|
117
|
+
except httpx.HTTPError as e:
|
|
118
|
+
return {"error": f"网络错误: {type(e).__name__}", "hint": "请检查网络连接。"}
|
|
119
|
+
data = resp.json()
|
|
120
|
+
|
|
121
|
+
if data.get("code") != 0:
|
|
122
|
+
msg = data.get("msg", "未知错误")
|
|
123
|
+
error_info: dict[str, Any] = {"error": msg, "code": data.get("code")}
|
|
124
|
+
# 为常见错误添加恢复建议
|
|
125
|
+
if "权限" in msg or "积分" in msg:
|
|
126
|
+
error_info["hint"] = "此接口需要更高积分权限。请在 tushare.pro 查看积分要求,或使用替代接口。"
|
|
127
|
+
elif "频率" in msg or "每分钟" in msg:
|
|
128
|
+
error_info["hint"] = "请求过于频繁,请稍后重试。建议:减少请求频率,或缩小查询范围。"
|
|
129
|
+
elif "参数" in msg:
|
|
130
|
+
error_info["hint"] = f"请用 get_api_doc('{api_name}') 查看正确的参数格式。"
|
|
131
|
+
return error_info
|
|
132
|
+
|
|
133
|
+
# 将 fields + items 转换为易读的 records 格式
|
|
134
|
+
result = data.get("data", {})
|
|
135
|
+
columns = result.get("fields", [])
|
|
136
|
+
items = result.get("items", [])
|
|
137
|
+
|
|
138
|
+
if not items:
|
|
139
|
+
return {"columns": columns, "records": [], "total": 0}
|
|
140
|
+
|
|
141
|
+
shown = min(len(items), limit)
|
|
142
|
+
records = [dict(zip(columns, row)) for row in items[:shown]]
|
|
143
|
+
out: dict[str, Any] = {
|
|
144
|
+
"columns": columns,
|
|
145
|
+
"records": records,
|
|
146
|
+
"total": len(items),
|
|
147
|
+
"shown": shown,
|
|
148
|
+
}
|
|
149
|
+
if len(items) > shown:
|
|
150
|
+
out["hint"] = f"共 {len(items)} 条记录,仅显示前 {shown} 条。可通过 start_date/end_date 缩小范围分批获取。"
|
|
151
|
+
return out
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ── 通用查询工具 ──────────────────────────────────────────────────────
|
|
155
|
+
@mcp.tool()
|
|
156
|
+
async def tushare_query(
|
|
157
|
+
api_name: str,
|
|
158
|
+
params: dict[str, Any] | None = None,
|
|
159
|
+
fields: str = "",
|
|
160
|
+
) -> str:
|
|
161
|
+
"""通用 Tushare API 查询工具,可调用任意 Tushare Pro 接口。
|
|
162
|
+
|
|
163
|
+
使用流程:
|
|
164
|
+
1. 先用 get_api_doc(api_name) 查看接口的详细参数说明
|
|
165
|
+
2. 或用 search_api_docs(keyword) 按关键词搜索接口
|
|
166
|
+
3. 然后用本工具传入 api_name 和 params 执行查询
|
|
167
|
+
|
|
168
|
+
通用说明:
|
|
169
|
+
- 日期格式: YYYYMMDD (如 20241231)
|
|
170
|
+
- 股票代码: 000001.SZ(深) 600000.SH(沪) 830799.BJ(北)
|
|
171
|
+
- 指数代码: 000001.SH(上证综指) 399001.SZ(深证成指)
|
|
172
|
+
- 基金代码: 510050.SH(场内ETF) 000001.OF(场外)
|
|
173
|
+
- 期货代码: CU2401.SHF(沪铜) IF2401.CFX(股指)
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
api_name: Tushare 接口名称,如 'daily', 'stock_basic', 'income' 等
|
|
177
|
+
params: 接口参数字典,如 {"ts_code": "000001.SZ", "start_date": "20240101"}
|
|
178
|
+
fields: 指定返回字段(逗号分隔),为空则返回全部默认字段
|
|
179
|
+
"""
|
|
180
|
+
result = await _call_tushare(api_name, params, fields)
|
|
181
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ── 便捷工具:股票基础 ───────────────────────────────────────────────
|
|
185
|
+
@mcp.tool()
|
|
186
|
+
async def get_stock_list(
|
|
187
|
+
list_status: str = "L",
|
|
188
|
+
exchange: str = "",
|
|
189
|
+
) -> str:
|
|
190
|
+
"""获取 A 股股票列表。
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
list_status: 上市状态。L-上市 D-退市 P-暂停上市,默认 L
|
|
194
|
+
exchange: 交易所。SSE-上交所 SZSE-深交所 BSE-北交所,留空返回全部
|
|
195
|
+
"""
|
|
196
|
+
result = await _call_tushare(
|
|
197
|
+
"stock_basic",
|
|
198
|
+
{"exchange": exchange, "list_status": list_status},
|
|
199
|
+
"ts_code,symbol,name,area,industry,market,list_date,list_status",
|
|
200
|
+
)
|
|
201
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@mcp.tool()
|
|
205
|
+
async def get_trade_calendar(
|
|
206
|
+
exchange: str = "SSE",
|
|
207
|
+
start_date: str = "",
|
|
208
|
+
end_date: str = "",
|
|
209
|
+
is_open: str = "",
|
|
210
|
+
) -> str:
|
|
211
|
+
"""获取交易日历。
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
exchange: 交易所代码。SSE-上交所 SZSE-深交所 CFFEX-中金所 SHFE-上期所
|
|
215
|
+
CZCE-郑商所 DCE-大商所 INE-上能源
|
|
216
|
+
start_date: 开始日期 YYYYMMDD
|
|
217
|
+
end_date: 结束日期 YYYYMMDD
|
|
218
|
+
is_open: 是否交易 '0'休市 '1'交易,留空返回全部
|
|
219
|
+
"""
|
|
220
|
+
params: dict[str, Any] = {"exchange": exchange}
|
|
221
|
+
if start_date:
|
|
222
|
+
params["start_date"] = start_date
|
|
223
|
+
if end_date:
|
|
224
|
+
params["end_date"] = end_date
|
|
225
|
+
if is_open:
|
|
226
|
+
params["is_open"] = is_open
|
|
227
|
+
result = await _call_tushare("trade_cal", params)
|
|
228
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ── 便捷工具:行情数据 ───────────────────────────────────────────────
|
|
232
|
+
@mcp.tool()
|
|
233
|
+
async def get_daily_bars(
|
|
234
|
+
ts_code: str = "",
|
|
235
|
+
trade_date: str = "",
|
|
236
|
+
start_date: str = "",
|
|
237
|
+
end_date: str = "",
|
|
238
|
+
) -> str:
|
|
239
|
+
"""获取 A 股日线行情(未复权),交易日 15 点后更新。
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
ts_code: 股票代码(如 000001.SZ),支持多个逗号分隔。与 trade_date 二选一
|
|
243
|
+
trade_date: 交易日期 YYYYMMDD,获取当天全市场行情。与 ts_code 二选一
|
|
244
|
+
start_date: 开始日期 YYYYMMDD
|
|
245
|
+
end_date: 结束日期 YYYYMMDD
|
|
246
|
+
"""
|
|
247
|
+
params: dict[str, Any] = {}
|
|
248
|
+
if ts_code:
|
|
249
|
+
params["ts_code"] = ts_code
|
|
250
|
+
if trade_date:
|
|
251
|
+
params["trade_date"] = trade_date
|
|
252
|
+
if start_date:
|
|
253
|
+
params["start_date"] = start_date
|
|
254
|
+
if end_date:
|
|
255
|
+
params["end_date"] = end_date
|
|
256
|
+
result = await _call_tushare("daily", params)
|
|
257
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@mcp.tool()
|
|
261
|
+
async def get_daily_basic(
|
|
262
|
+
ts_code: str = "",
|
|
263
|
+
trade_date: str = "",
|
|
264
|
+
start_date: str = "",
|
|
265
|
+
end_date: str = "",
|
|
266
|
+
) -> str:
|
|
267
|
+
"""获取每日指标:PE/PB/PS/总市值/流通市值/换手率/股息率等。
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
ts_code: 股票代码(如 000001.SZ)。与 trade_date 二选一
|
|
271
|
+
trade_date: 交易日期 YYYYMMDD。与 ts_code 二选一
|
|
272
|
+
start_date: 开始日期 YYYYMMDD
|
|
273
|
+
end_date: 结束日期 YYYYMMDD
|
|
274
|
+
"""
|
|
275
|
+
params: dict[str, Any] = {}
|
|
276
|
+
if ts_code:
|
|
277
|
+
params["ts_code"] = ts_code
|
|
278
|
+
if trade_date:
|
|
279
|
+
params["trade_date"] = trade_date
|
|
280
|
+
if start_date:
|
|
281
|
+
params["start_date"] = start_date
|
|
282
|
+
if end_date:
|
|
283
|
+
params["end_date"] = end_date
|
|
284
|
+
result = await _call_tushare("daily_basic", params)
|
|
285
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# ── 便捷工具:财务数据 ───────────────────────────────────────────────
|
|
289
|
+
@mcp.tool()
|
|
290
|
+
async def get_income(
|
|
291
|
+
ts_code: str,
|
|
292
|
+
period: str = "",
|
|
293
|
+
start_date: str = "",
|
|
294
|
+
end_date: str = "",
|
|
295
|
+
) -> str:
|
|
296
|
+
"""获取上市公司利润表。
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
ts_code: 股票代码(如 600000.SH)
|
|
300
|
+
period: 报告期 YYYYMMDD(如 20231231 表示2023年报)
|
|
301
|
+
start_date: 公告开始日期
|
|
302
|
+
end_date: 公告结束日期
|
|
303
|
+
"""
|
|
304
|
+
params: dict[str, Any] = {"ts_code": ts_code}
|
|
305
|
+
if period:
|
|
306
|
+
params["period"] = period
|
|
307
|
+
if start_date:
|
|
308
|
+
params["start_date"] = start_date
|
|
309
|
+
if end_date:
|
|
310
|
+
params["end_date"] = end_date
|
|
311
|
+
result = await _call_tushare("income", params)
|
|
312
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@mcp.tool()
|
|
316
|
+
async def get_balancesheet(
|
|
317
|
+
ts_code: str,
|
|
318
|
+
period: str = "",
|
|
319
|
+
start_date: str = "",
|
|
320
|
+
end_date: str = "",
|
|
321
|
+
) -> str:
|
|
322
|
+
"""获取上市公司资产负债表。
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
ts_code: 股票代码
|
|
326
|
+
period: 报告期 YYYYMMDD
|
|
327
|
+
start_date: 公告开始日期
|
|
328
|
+
end_date: 公告结束日期
|
|
329
|
+
"""
|
|
330
|
+
params: dict[str, Any] = {"ts_code": ts_code}
|
|
331
|
+
if period:
|
|
332
|
+
params["period"] = period
|
|
333
|
+
if start_date:
|
|
334
|
+
params["start_date"] = start_date
|
|
335
|
+
if end_date:
|
|
336
|
+
params["end_date"] = end_date
|
|
337
|
+
result = await _call_tushare("balancesheet", params)
|
|
338
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@mcp.tool()
|
|
342
|
+
async def get_cashflow(
|
|
343
|
+
ts_code: str,
|
|
344
|
+
period: str = "",
|
|
345
|
+
start_date: str = "",
|
|
346
|
+
end_date: str = "",
|
|
347
|
+
) -> str:
|
|
348
|
+
"""获取上市公司现金流量表。
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
ts_code: 股票代码
|
|
352
|
+
period: 报告期 YYYYMMDD
|
|
353
|
+
start_date: 公告开始日期
|
|
354
|
+
end_date: 公告结束日期
|
|
355
|
+
"""
|
|
356
|
+
params: dict[str, Any] = {"ts_code": ts_code}
|
|
357
|
+
if period:
|
|
358
|
+
params["period"] = period
|
|
359
|
+
if start_date:
|
|
360
|
+
params["start_date"] = start_date
|
|
361
|
+
if end_date:
|
|
362
|
+
params["end_date"] = end_date
|
|
363
|
+
result = await _call_tushare("cashflow", params)
|
|
364
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@mcp.tool()
|
|
368
|
+
async def get_fina_indicator(
|
|
369
|
+
ts_code: str,
|
|
370
|
+
period: str = "",
|
|
371
|
+
start_date: str = "",
|
|
372
|
+
end_date: str = "",
|
|
373
|
+
) -> str:
|
|
374
|
+
"""获取上市公司财务指标(ROE/ROA/毛利率/净利率等综合指标)。
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
ts_code: 股票代码
|
|
378
|
+
period: 报告期 YYYYMMDD
|
|
379
|
+
start_date: 公告开始日期
|
|
380
|
+
end_date: 公告结束日期
|
|
381
|
+
"""
|
|
382
|
+
params: dict[str, Any] = {"ts_code": ts_code}
|
|
383
|
+
if period:
|
|
384
|
+
params["period"] = period
|
|
385
|
+
if start_date:
|
|
386
|
+
params["start_date"] = start_date
|
|
387
|
+
if end_date:
|
|
388
|
+
params["end_date"] = end_date
|
|
389
|
+
result = await _call_tushare("fina_indicator", params)
|
|
390
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# ── 便捷工具:指数 ───────────────────────────────────────────────────
|
|
394
|
+
@mcp.tool()
|
|
395
|
+
async def get_index_daily(
|
|
396
|
+
ts_code: str,
|
|
397
|
+
start_date: str = "",
|
|
398
|
+
end_date: str = "",
|
|
399
|
+
) -> str:
|
|
400
|
+
"""获取指数日线行情。
|
|
401
|
+
|
|
402
|
+
常用指数代码:
|
|
403
|
+
- 000001.SH 上证综指
|
|
404
|
+
- 399001.SZ 深证成指
|
|
405
|
+
- 399006.SZ 创业板指
|
|
406
|
+
- 000300.SH 沪深300
|
|
407
|
+
- 000905.SH 中证500
|
|
408
|
+
- 000688.SH 科创50
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
ts_code: 指数代码
|
|
412
|
+
start_date: 开始日期 YYYYMMDD
|
|
413
|
+
end_date: 结束日期 YYYYMMDD
|
|
414
|
+
"""
|
|
415
|
+
params: dict[str, Any] = {"ts_code": ts_code}
|
|
416
|
+
if start_date:
|
|
417
|
+
params["start_date"] = start_date
|
|
418
|
+
if end_date:
|
|
419
|
+
params["end_date"] = end_date
|
|
420
|
+
result = await _call_tushare("index_daily", params)
|
|
421
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
# ── 便捷工具:资金流向 ───────────────────────────────────────────────
|
|
425
|
+
@mcp.tool()
|
|
426
|
+
async def get_moneyflow(
|
|
427
|
+
ts_code: str = "",
|
|
428
|
+
trade_date: str = "",
|
|
429
|
+
start_date: str = "",
|
|
430
|
+
end_date: str = "",
|
|
431
|
+
) -> str:
|
|
432
|
+
"""获取个股资金流向数据。
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
ts_code: 股票代码。与 trade_date 二选一
|
|
436
|
+
trade_date: 交易日期 YYYYMMDD。与 ts_code 二选一
|
|
437
|
+
start_date: 开始日期
|
|
438
|
+
end_date: 结束日期
|
|
439
|
+
"""
|
|
440
|
+
params: dict[str, Any] = {}
|
|
441
|
+
if ts_code:
|
|
442
|
+
params["ts_code"] = ts_code
|
|
443
|
+
if trade_date:
|
|
444
|
+
params["trade_date"] = trade_date
|
|
445
|
+
if start_date:
|
|
446
|
+
params["start_date"] = start_date
|
|
447
|
+
if end_date:
|
|
448
|
+
params["end_date"] = end_date
|
|
449
|
+
result = await _call_tushare("moneyflow", params)
|
|
450
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
# ── 便捷工具:概念板块 ───────────────────────────────────────────────
|
|
454
|
+
@mcp.tool()
|
|
455
|
+
async def get_concept(src: str = "ts") -> str:
|
|
456
|
+
"""获取概念股分类,分析市场热点主题。
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
src: 来源。ts-Tushare sina-新浪
|
|
460
|
+
"""
|
|
461
|
+
result = await _call_tushare("concept", {"src": src})
|
|
462
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@mcp.tool()
|
|
466
|
+
async def get_concept_detail(
|
|
467
|
+
concept_id: str = "",
|
|
468
|
+
ts_code: str = "",
|
|
469
|
+
) -> str:
|
|
470
|
+
"""查询概念股成分列表,或查某只股票属于哪些概念。
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
concept_id: 概念ID (从 get_concept 获取)。与 ts_code 二选一
|
|
474
|
+
ts_code: 股票代码。与 concept_id 二选一
|
|
475
|
+
"""
|
|
476
|
+
params: dict[str, Any] = {}
|
|
477
|
+
if concept_id:
|
|
478
|
+
params["id"] = concept_id
|
|
479
|
+
if ts_code:
|
|
480
|
+
params["ts_code"] = ts_code
|
|
481
|
+
result = await _call_tushare("concept_detail", params)
|
|
482
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
# ── 便捷工具:基金 ───────────────────────────────────────────────────
|
|
486
|
+
@mcp.tool()
|
|
487
|
+
async def get_fund_basic(
|
|
488
|
+
market: str = "E",
|
|
489
|
+
status: str = "L",
|
|
490
|
+
) -> str:
|
|
491
|
+
"""获取公募基金列表。
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
market: E-场内(ETF等) O-场外
|
|
495
|
+
status: D-摘牌 I-发行中 L-已上市
|
|
496
|
+
"""
|
|
497
|
+
result = await _call_tushare("fund_basic", {"market": market, "status": status})
|
|
498
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
@mcp.tool()
|
|
502
|
+
async def get_fund_nav(
|
|
503
|
+
ts_code: str,
|
|
504
|
+
start_date: str = "",
|
|
505
|
+
end_date: str = "",
|
|
506
|
+
) -> str:
|
|
507
|
+
"""获取基金净值数据。
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
ts_code: 基金代码(如 510050.SH)
|
|
511
|
+
start_date: 开始日期
|
|
512
|
+
end_date: 结束日期
|
|
513
|
+
"""
|
|
514
|
+
params: dict[str, Any] = {"ts_code": ts_code}
|
|
515
|
+
if start_date:
|
|
516
|
+
params["start_date"] = start_date
|
|
517
|
+
if end_date:
|
|
518
|
+
params["end_date"] = end_date
|
|
519
|
+
result = await _call_tushare("fund_nav", params)
|
|
520
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
# ── 便捷工具:宏观经济 ───────────────────────────────────────────────
|
|
524
|
+
@mcp.tool()
|
|
525
|
+
async def get_shibor(
|
|
526
|
+
start_date: str = "",
|
|
527
|
+
end_date: str = "",
|
|
528
|
+
) -> str:
|
|
529
|
+
"""获取 Shibor 拆借利率数据。
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
start_date: 开始日期 YYYYMMDD
|
|
533
|
+
end_date: 结束日期 YYYYMMDD
|
|
534
|
+
"""
|
|
535
|
+
params: dict[str, Any] = {}
|
|
536
|
+
if start_date:
|
|
537
|
+
params["start_date"] = start_date
|
|
538
|
+
if end_date:
|
|
539
|
+
params["end_date"] = end_date
|
|
540
|
+
result = await _call_tushare("shibor", params)
|
|
541
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
# ── API 文档工具 ──────────────────────────────────────────────────────
|
|
545
|
+
@mcp.tool()
|
|
546
|
+
async def get_api_doc(api_name: str) -> str:
|
|
547
|
+
"""按接口名获取 Tushare API 的完整文档(参数、字段、示例、限量说明等)。
|
|
548
|
+
|
|
549
|
+
这是查询某个已知接口详细用法的最佳方式。
|
|
550
|
+
如果不确定接口名,请先用 search_api_docs 搜索。
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
api_name: Tushare 接口名,如 'daily', 'stock_basic', 'income',
|
|
554
|
+
'fund_nav', 'index_daily', 'cb_daily', 'margin' 等
|
|
555
|
+
"""
|
|
556
|
+
index = _load_api_index()
|
|
557
|
+
docs = _load_docs()
|
|
558
|
+
|
|
559
|
+
# 检查是否有别名(同名冲突的接口)
|
|
560
|
+
aliases = [n for n in index if "__" in n and n.split("__")[0] == api_name]
|
|
561
|
+
|
|
562
|
+
# 精确匹配
|
|
563
|
+
if api_name in index:
|
|
564
|
+
file_stem = index[api_name]["file"].replace(".md", "")
|
|
565
|
+
if file_stem in docs:
|
|
566
|
+
result = docs[file_stem]
|
|
567
|
+
# 如果有别名,追加提示
|
|
568
|
+
for alias in aliases:
|
|
569
|
+
alias_info = index[alias]
|
|
570
|
+
result += f"\n\n---\n> 注意:还有一个同名接口 `{alias}`({alias_info['title']}),可用 get_api_doc('{alias}') 查看。"
|
|
571
|
+
return result
|
|
572
|
+
|
|
573
|
+
# 别名精确匹配(如 trade_cal__期货交易日历)
|
|
574
|
+
if api_name in index:
|
|
575
|
+
pass # already handled above
|
|
576
|
+
for name, info in index.items():
|
|
577
|
+
if name == api_name:
|
|
578
|
+
file_stem = info["file"].replace(".md", "")
|
|
579
|
+
if file_stem in docs:
|
|
580
|
+
return docs[file_stem]
|
|
581
|
+
|
|
582
|
+
# 模糊匹配:接口名包含关键词 (要求至少4字符,避免短词误匹配)
|
|
583
|
+
candidates = []
|
|
584
|
+
if len(api_name) >= 4:
|
|
585
|
+
for name, info in index.items():
|
|
586
|
+
if name.startswith("_"):
|
|
587
|
+
continue
|
|
588
|
+
# 双向包含,但要求被包含方至少4字符,防止 "st" in "not_exist" 类误匹配
|
|
589
|
+
if (api_name in name) or (name in api_name and len(name) >= 4):
|
|
590
|
+
candidates.append((name, info))
|
|
591
|
+
|
|
592
|
+
if len(candidates) == 1:
|
|
593
|
+
file_stem = candidates[0][1]["file"].replace(".md", "")
|
|
594
|
+
if file_stem in docs:
|
|
595
|
+
return docs[file_stem]
|
|
596
|
+
|
|
597
|
+
if candidates:
|
|
598
|
+
result = f"未找到精确匹配 '{api_name}',以下接口可能相关:\n\n"
|
|
599
|
+
for name, info in candidates:
|
|
600
|
+
result += f"- **{name}**: {info['title']} — {info['description']}\n"
|
|
601
|
+
result += "\n请使用精确的接口名重新查询。"
|
|
602
|
+
return result
|
|
603
|
+
|
|
604
|
+
return (
|
|
605
|
+
f"未找到接口 '{api_name}'。\n"
|
|
606
|
+
f"请使用 search_api_docs 按关键词搜索,或查看 tushare://api-reference 获取完整接口列表。"
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
@mcp.tool()
|
|
611
|
+
async def search_api_docs(keyword: str) -> str:
|
|
612
|
+
"""按关键词搜索 Tushare API 文档,适用于不确定接口名时的模糊查找。
|
|
613
|
+
|
|
614
|
+
搜索范围包括接口名、中文标题、参数名、字段描述等。
|
|
615
|
+
找到接口后,请用 get_api_doc(api_name) 获取完整文档。
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
keyword: 搜索关键词,如 "融资融券" "利润" "PE" "ETF" "港股" 等
|
|
619
|
+
"""
|
|
620
|
+
index = _load_api_index()
|
|
621
|
+
docs = _load_docs()
|
|
622
|
+
if not docs:
|
|
623
|
+
return "文档目录为空,请先运行 crawl_docs.py 爬取文档。"
|
|
624
|
+
|
|
625
|
+
# 短关键词用单词边界匹配减少噪音
|
|
626
|
+
if len(keyword) <= 3 and keyword.isascii():
|
|
627
|
+
pattern = re.compile(r"(?<![a-zA-Z])" + re.escape(keyword) + r"(?![a-zA-Z])", re.IGNORECASE)
|
|
628
|
+
else:
|
|
629
|
+
pattern = re.compile(re.escape(keyword), re.IGNORECASE)
|
|
630
|
+
|
|
631
|
+
scored_matches: list[tuple[int, str]] = [] # (score, result_text)
|
|
632
|
+
|
|
633
|
+
for name, content in docs.items():
|
|
634
|
+
hit_count = len(pattern.findall(content)) + len(pattern.findall(name))
|
|
635
|
+
if hit_count == 0:
|
|
636
|
+
continue
|
|
637
|
+
|
|
638
|
+
lines = content.split("\n")
|
|
639
|
+
title_line = lines[0] if lines else name
|
|
640
|
+
|
|
641
|
+
# 从索引中找到 api_name
|
|
642
|
+
api_name_str = ""
|
|
643
|
+
for api_n, info in index.items():
|
|
644
|
+
if info["file"].replace(".md", "") == name:
|
|
645
|
+
api_name_str = api_n
|
|
646
|
+
break
|
|
647
|
+
|
|
648
|
+
# 计算相关性分数
|
|
649
|
+
score = hit_count
|
|
650
|
+
# 标题匹配加权
|
|
651
|
+
if pattern.search(title_line):
|
|
652
|
+
score += 50
|
|
653
|
+
# 接口名匹配加权
|
|
654
|
+
if api_name_str and pattern.search(api_name_str):
|
|
655
|
+
score += 30
|
|
656
|
+
|
|
657
|
+
# 提取不重叠的上下文片段
|
|
658
|
+
relevant = []
|
|
659
|
+
covered = set()
|
|
660
|
+
for i, line in enumerate(lines):
|
|
661
|
+
if pattern.search(line) and i not in covered:
|
|
662
|
+
start = max(0, i - 1)
|
|
663
|
+
end = min(len(lines), i + 4)
|
|
664
|
+
snippet = "\n".join(lines[start:end])
|
|
665
|
+
relevant.append(snippet)
|
|
666
|
+
covered.update(range(start, end))
|
|
667
|
+
|
|
668
|
+
if relevant:
|
|
669
|
+
header = f"### {title_line}"
|
|
670
|
+
if api_name_str and not api_name_str.startswith("_"):
|
|
671
|
+
header += f" (接口: `{api_name_str}`)"
|
|
672
|
+
result = header + "\n\n"
|
|
673
|
+
result += "\n...\n".join(relevant[:2]) # 最多2个片段
|
|
674
|
+
scored_matches.append((score, result))
|
|
675
|
+
|
|
676
|
+
if not scored_matches:
|
|
677
|
+
return f"未找到与 '{keyword}' 相关的文档。请尝试其他关键词。"
|
|
678
|
+
|
|
679
|
+
# 按相关性排序,取前8
|
|
680
|
+
scored_matches.sort(key=lambda x: -x[0])
|
|
681
|
+
top = scored_matches[:8]
|
|
682
|
+
|
|
683
|
+
header = f"找到 {len(scored_matches)} 个相关文档(按相关度排序,显示前 {len(top)} 个):\n\n"
|
|
684
|
+
return header + "\n\n---\n\n".join(r for _, r in top)
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
@mcp.resource("tushare://api-reference")
|
|
688
|
+
def get_api_reference() -> str:
|
|
689
|
+
"""Tushare Pro API 完整接口索引(按类别分组),列出所有可用接口及简要说明。
|
|
690
|
+
使用 get_api_doc(api_name) 获取具体接口的完整文档。"""
|
|
691
|
+
index = _load_api_index()
|
|
692
|
+
if not index:
|
|
693
|
+
return "API 索引为空,请先运行 build_index.py。"
|
|
694
|
+
|
|
695
|
+
# 按 category 分组
|
|
696
|
+
cats: dict[str, list[tuple[str, dict]]] = {}
|
|
697
|
+
for api_name, info in sorted(index.items()):
|
|
698
|
+
if api_name.startswith("_"):
|
|
699
|
+
continue
|
|
700
|
+
cat = info.get("category", "其他")
|
|
701
|
+
cats.setdefault(cat, []).append((api_name, info))
|
|
702
|
+
|
|
703
|
+
# 固定分类顺序
|
|
704
|
+
CAT_ORDER = [
|
|
705
|
+
"沪深股票", "财务数据", "资金流向", "市场参考", "指数数据", "概念板块",
|
|
706
|
+
"基金数据", "期货数据", "期权数据", "债券/可转债", "港股数据", "美股数据",
|
|
707
|
+
"外汇数据", "黄金现货", "宏观经济", "交易日历", "新闻公告", "其他",
|
|
708
|
+
]
|
|
709
|
+
|
|
710
|
+
total = sum(len(v) for v in cats.values())
|
|
711
|
+
lines = [
|
|
712
|
+
"# Tushare Pro API 接口索引",
|
|
713
|
+
"",
|
|
714
|
+
f"共 {total} 个接口,分 {len(cats)} 个类别。使用 `get_api_doc(api_name)` 获取完整文档。",
|
|
715
|
+
"",
|
|
716
|
+
]
|
|
717
|
+
|
|
718
|
+
for cat in CAT_ORDER:
|
|
719
|
+
if cat not in cats:
|
|
720
|
+
continue
|
|
721
|
+
items = cats[cat]
|
|
722
|
+
lines.append(f"## {cat} ({len(items)})")
|
|
723
|
+
lines.append("")
|
|
724
|
+
lines.append("| 接口名 | 名称 | 说明 |")
|
|
725
|
+
lines.append("|---|---|---|")
|
|
726
|
+
for api_name, info in items:
|
|
727
|
+
desc = info["description"][:50] if info["description"] else ""
|
|
728
|
+
lines.append(f"| {api_name} | {info['title']} | {desc} |")
|
|
729
|
+
lines.append("")
|
|
730
|
+
|
|
731
|
+
return "\n".join(lines)
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
# ── 启动 ──────────────────────────────────────────────────────────────
|
|
735
|
+
if __name__ == "__main__":
|
|
736
|
+
mcp.run()
|