rfhub-mcp 0.3.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/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # rfhub-mcp
2
+
3
+ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server for RFHub.
4
+
5
+ ## Usage
6
+
7
+ ### Via npx (no install)
8
+
9
+ ```bash
10
+ npx rfhub-mcp
11
+ ```
package/dist/server.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import ArfcnCalculator4G from "./tools/arfcn-calculator-4g.js";
5
+ import ArfcnCalculator5G from "./tools/arfcn-calculator-5g.js";
6
+ import ArfcnCalculatorNTN from "./tools/arfcn-calculator-ntn.js";
7
+ const mcpServer = new McpServer({
8
+ name: "rfhub-mcp",
9
+ version: "0.1.0",
10
+ title: "RFHUB MCP Server",
11
+ description: "RFHUB MCP Server",
12
+ websiteUrl: "https://rfhub.cn/mcp-server",
13
+ });
14
+ new ArfcnCalculator4G(mcpServer).registerAllTools();
15
+ new ArfcnCalculator5G(mcpServer).registerAllTools();
16
+ new ArfcnCalculatorNTN(mcpServer).registerAllTools();
17
+ async function main() {
18
+ const transport = new StdioServerTransport();
19
+ await mcpServer.connect(transport);
20
+ console.log("MCP server is running...");
21
+ }
22
+ main().catch((error) => {
23
+ console.error("Server error:", error);
24
+ process.exit(1);
25
+ });
@@ -0,0 +1,137 @@
1
+ import * as z from "zod/v4";
2
+ import { safeApiPost } from "../utils/request.js";
3
+ import { mcpExResponse } from "../utils/response.js";
4
+ export default class ArfcnCalculator4G {
5
+ mcpServer;
6
+ constructor(mcpServer) {
7
+ mcpServer = mcpServer;
8
+ }
9
+ registerAllTools = () => {
10
+ this.registerComputeTool();
11
+ };
12
+ registerComputeTool = () => {
13
+ const ModeEnum = z.enum(["n", "f"]);
14
+ const DirectionEnum = z.enum(["uplink", "downlink"]);
15
+ const DuplexModeEnum = z.enum(["TDD", "FDD"]);
16
+ const computeInputSchema = z.object({
17
+ Mode: ModeEnum.optional().describe("计算模式,例如 n 表示频点模式,f 表示频率模式"),
18
+ DataTransmissionDirection: DirectionEnum.describe("数据传输方向,可选上行(uplink)、下行(downlink)"),
19
+ NREF: z.number().int().optional().describe('频点参考(NREF),例如 504999。用于在频点模式(Mode=`"n"`)下确定中心频率。'),
20
+ FREF: z.number().optional().describe('频率参考(FREF),单位为 MHz,例如 3500.0。用于在频率模式(Mode=`"f"`)下直接指定中心频率。'),
21
+ Band: z.string().optional().describe('频段标识符,例如 "b3"、"b41" 等'),
22
+ Bandwidth: z.number().positive().optional().describe("信道带宽,单位为 MHz,例如 100 表示 100MHz"),
23
+ });
24
+ const computeOutputSchema = z.object({
25
+ Mode: ModeEnum.nullable().optional(),
26
+ Mode_Error: z.string().nullable().optional(),
27
+ DataTransmissionDirection: DirectionEnum.nullable().optional(),
28
+ DataTransmissionDirection_Error: z.string().nullable().optional(),
29
+ NREF: z.number().int().nullable().optional(),
30
+ NREF_Error: z.string().nullable().optional(),
31
+ FREF: z.number().nullable().optional(),
32
+ FREF_Error: z.string().nullable().optional(),
33
+ BandOptions: z.array(z.string()).nullable().optional(),
34
+ BandOptions_Error: z.string().nullable().optional(),
35
+ Band: z.string().nullable().optional(),
36
+ Band_Error: z.string().nullable().optional(),
37
+ FREF_Low: z.number().nullable().optional(),
38
+ FREF_High: z.number().nullable().optional(),
39
+ NREF_First: z.number().int().nullable().optional(),
40
+ NREF_Last: z.number().int().nullable().optional(),
41
+ DuplexMode: DuplexModeEnum.nullable().optional(),
42
+ SCS: z.string().nullable().optional(),
43
+ BandwidthOptions: z.array(z.string()).nullable().optional(),
44
+ BandwidthOptions_Error: z.string().nullable().optional(),
45
+ Bandwidth: z.number().nullable().optional(),
46
+ Bandwidth_Error: z.string().nullable().optional(),
47
+ NRB: z.number().int().nullable().optional(),
48
+ FREF_AvailableLow: z.number().nullable().optional(),
49
+ FREF_AvailableHigh: z.number().nullable().optional(),
50
+ });
51
+ const apiResultSchema = z.object({
52
+ Mode: z.string().nullable().optional(),
53
+ Mode_Error: z.string().nullable().optional(),
54
+ DataTransmissionDirection: z.string().nullable().optional(),
55
+ DataTransmissionDirection_Error: z.string().nullable().optional(),
56
+ NREF: z.string().nullable().optional(),
57
+ NREF_Error: z.string().nullable().optional(),
58
+ FREF: z.string().nullable().optional(),
59
+ FREF_Error: z.string().nullable().optional(),
60
+ BandOptions: z.array(z.string()).nullable().optional(),
61
+ BandOptions_Error: z.string().nullable().optional(),
62
+ Band: z.string().nullable().optional(),
63
+ Band_Error: z.string().nullable().optional(),
64
+ FREF_Low: z.string().nullable().optional(),
65
+ FREF_High: z.string().nullable().optional(),
66
+ NREF_First: z.string().nullable().optional(),
67
+ NREF_Last: z.string().nullable().optional(),
68
+ DuplexMode: DuplexModeEnum.nullable().optional(),
69
+ SCS: z.string().nullable().optional(),
70
+ BandwidthOptions: z.array(z.string()).nullable().optional(),
71
+ BandwidthOptions_Error: z.string().nullable().optional(),
72
+ Bandwidth: z.string().nullable().optional(),
73
+ Bandwidth_Error: z.string().nullable().optional(),
74
+ NRB: z.string().nullable().optional(),
75
+ FREF_AvailableLow: z.string().nullable().optional(),
76
+ FREF_AvailableHigh: z.string().nullable().optional(),
77
+ });
78
+ this.mcpServer.registerTool("arfcn_calculator_4g_compute", {
79
+ title: "4G/LTE频点频率转换与对应频带信息",
80
+ description: "支持4G LTE网络中频率与频点之间的相互转换,并附带特定频段的详细信息展示",
81
+ inputSchema: computeInputSchema,
82
+ outputSchema: computeOutputSchema,
83
+ }, async (input) => {
84
+ try {
85
+ const apiRequest = {
86
+ Mode: input.Mode ?? (input.NREF && "n"),
87
+ DataTransmissionDirection: input.DataTransmissionDirection,
88
+ Band: input.Band?.replaceAll("b", "").replaceAll("B", "") ?? null,
89
+ NERF: input.NREF?.toString() ?? null,
90
+ FERF: input.FREF?.toString() ?? null,
91
+ Bandwidth: input.Bandwidth?.toString() ?? null,
92
+ };
93
+ const result = await safeApiPost("/api/arfcn-calculator-4g/compute", apiRequest);
94
+ const apiResult = apiResultSchema.parse(result);
95
+ const structuredContent = {
96
+ Mode: apiResult.Mode ?? null,
97
+ Mode_Error: apiResult.Mode_Error ?? null,
98
+ DataTransmissionDirection: apiResult.DataTransmissionDirection ?? null,
99
+ DataTransmissionDirection_Error: apiResult.DataTransmissionDirection_Error ?? null,
100
+ NREF: apiResult.NREF ?? null,
101
+ NREF_Error: apiResult.NREF_Error ?? null,
102
+ FREF: apiResult.FREF ?? null,
103
+ FREF_Error: apiResult.FREF_Error ?? null,
104
+ BandOptions: apiResult.BandOptions ?? null,
105
+ BandOptions_Error: apiResult.BandOptions_Error ?? null,
106
+ Band: apiResult.Band ?? null,
107
+ Band_Error: apiResult.Band_Error ?? null,
108
+ FREF_Low: apiResult.FREF_Low ?? null,
109
+ FREF_High: apiResult.FREF_High ?? null,
110
+ NREF_First: apiResult.NREF_First ?? null,
111
+ NREF_Last: apiResult.NREF_Last ?? null,
112
+ DuplexMode: apiResult.DuplexMode ?? null,
113
+ SCS: apiResult.SCS ?? null,
114
+ BandwidthOptions: apiResult.BandwidthOptions ?? null,
115
+ BandwidthOptions_Error: apiResult.BandwidthOptions_Error ?? null,
116
+ Bandwidth: apiResult.Bandwidth ?? null,
117
+ Bandwidth_Error: apiResult.Bandwidth_Error ?? null,
118
+ NRB: apiResult.NRB ?? null,
119
+ FREF_AvailableLow: apiResult.FREF_AvailableLow ?? null,
120
+ FREF_AvailableHigh: apiResult.FREF_AvailableHigh ?? null,
121
+ };
122
+ return {
123
+ content: [
124
+ {
125
+ type: "text",
126
+ text: JSON.stringify(structuredContent, null, 2),
127
+ },
128
+ ],
129
+ structuredContent,
130
+ };
131
+ }
132
+ catch {
133
+ return mcpExResponse;
134
+ }
135
+ });
136
+ };
137
+ }
@@ -0,0 +1,207 @@
1
+ import * as z from "zod/v4";
2
+ import { safeApiPost } from "../utils/request.js";
3
+ import { mcpExResponse } from "../utils/response.js";
4
+ export default class ArfcnCalculator5G {
5
+ mcpServer;
6
+ constructor(mcpServer) {
7
+ mcpServer = mcpServer;
8
+ }
9
+ registerAllTools = () => {
10
+ this.registerComputeTool();
11
+ };
12
+ registerComputeTool = () => {
13
+ const ModeEnum = z.enum(["n", "f"]);
14
+ const DirectionEnum = z.enum(["uplink", "downlink"]);
15
+ const DuplexModeEnum = z.enum(["TDD", "FDD"]);
16
+ const computeInputSchema = z.object({
17
+ Mode: ModeEnum.nullable().optional().describe("计算模式,例如 n 表示频点模式,f 表示频率模式"),
18
+ DataTransmissionDirection: DirectionEnum.describe("数据传输方向,可选上行(uplink)、下行(downlink)"),
19
+ NREF: z.number().int().nullable().optional().describe('频点参考值(NREF),例如 `"504999"`。用于在频点模式(Mode=`"n"`)下确定中心频率。'),
20
+ FREF: z.number().nullable().optional().describe('频率参考值(FREF),例如 `"3500.0"`。用于在频率模式(Mode=`"f"`)下直接指定中心频率。'),
21
+ Band: z.string().nullable().optional().describe('频段标识符,例如 `"n78"`、`"n41"` 等。用于确定频段的上下行配置、信道栅格(raster)及边界频率。'),
22
+ FRaster: z.number().int().nullable().optional().describe("频率栅格(Frequency Raster),单位为 kHz,例如 5、15。用于频点与频率之间的转换计算。"),
23
+ SCS: z.number().int().nullable().optional().describe("子载波间隔(Subcarrier Spacing),单位为 kHz,例如 15、30。影响资源块结构和带宽计算。"),
24
+ Bandwidth: z
25
+ .number()
26
+ .nullable()
27
+ .optional()
28
+ .describe("信道带宽(Channel Bandwidth),单位为 MHz,例如 5、20、100。用于确定传输带宽配置(Transmission Bandwidth Configuration)。"),
29
+ SSBlockSCS: z.number().int().nullable().optional().describe("SSB(Synchronization Signal Block)的子载波间隔,单位为 kHz,例如 15、30。"),
30
+ SSBlockPattern: z
31
+ .string()
32
+ .nullable()
33
+ .optional()
34
+ .describe('SSB 波束扫描图案类型,例如 `"Case A"`、`"Case B"`、`"Case C"`(FR1)或 `"Case D"`、`"Case E"`(FR2),影响 SSB 时域位置和数量。'),
35
+ GSCN: z
36
+ .number()
37
+ .int()
38
+ .nullable()
39
+ .optional()
40
+ .describe("全局同步信道号(Global Synchronization Channel Number),用于粗粒度频率搜索,特别是在非服务小区初始接入时。例如 504990"),
41
+ SSREF: z.number().nullable().optional().describe("SSB 中心频率对应的参考频率(单位:Hz 或 MHz,依上下文而定),常用于从 GSCN 或 SSB 配置反推绝对频率。"),
42
+ N: z.number().int().nullable().optional().describe("用于 SSB 频域位置计算的参数 N,通常与 GSCN 或 raster 相关。例如 6312"),
43
+ M: z.number().int().nullable().optional().describe("SSB 频域偏移步长因子,取决于频段和 SSB 子载波间隔。例如 1、3、5"),
44
+ });
45
+ const computeOutputSchema = z.object({
46
+ Mode: ModeEnum.nullable().optional(),
47
+ Mode_Error: z.string().nullable().optional(),
48
+ DataTransmissionDirection: DirectionEnum.nullable().optional(),
49
+ DataTransmissionDirection_Error: z.string().nullable().optional(),
50
+ NREF: z.number().int().nullable().optional(),
51
+ NREF_Error: z.string().nullable().optional(),
52
+ FREF: z.number().nullable().optional(),
53
+ FREF_Error: z.string().nullable().optional(),
54
+ FGlobalRaster: z.number().nullable().optional(), // 全局频率栅格,kHz(数值)
55
+ FrequencyRangeDesignation: z.string().nullable().optional(), // 频率范围标识符
56
+ BandOptions: z.array(z.string()).nullable().optional(),
57
+ BandOptions_Error: z.string().nullable().optional(),
58
+ Band: z.string().nullable().optional(),
59
+ Band_Error: z.string().nullable().optional(),
60
+ FREF_Low: z.number().nullable().optional(),
61
+ FREF_High: z.number().nullable().optional(),
62
+ FRasterOptions_Error: z.string().nullable().optional(),
63
+ FRasterOptions: z.array(z.string()).nullable().optional(),
64
+ FRaster: z.number().nullable().optional(),
65
+ FRaster_Error: z.string().nullable().optional(),
66
+ NREF_StepSize: z.number().int().nullable().optional(),
67
+ NREF_First: z.number().int().nullable().optional(),
68
+ NREF_Last: z.number().int().nullable().optional(),
69
+ DuplexMode: DuplexModeEnum.nullable().optional(),
70
+ SCSOptions: z.array(z.string()).nullable().optional(),
71
+ SCSOptions_Error: z.string().nullable().optional(),
72
+ SCS: z.string().nullable().optional(),
73
+ SCS_Error: z.string().nullable().optional(),
74
+ BandwidthOptions: z.array(z.string()).nullable().optional(),
75
+ BandwidthOptions_Error: z.string().nullable().optional(),
76
+ Bandwidth: z.number().nullable().optional(), // 带宽单位可能是 MHz,但以数字形式
77
+ Bandwidth_Error: z.string().nullable().optional(),
78
+ NRB: z.number().int().nullable().optional(), // 资源块数量,整数
79
+ MinimumGuardband: z.number().nullable().optional(), // 最小保护带宽,kHz(数值)
80
+ FREF_AvailableLow: z.number().nullable().optional(),
81
+ FREF_AvailableHigh: z.number().nullable().optional(),
82
+ SSBlockSCSOptions: z.array(z.number()).nullable().optional(),
83
+ SSBlockSCSOptions_Error: z.string().nullable().optional(),
84
+ SSBlockSCS: z.number().nullable().optional(),
85
+ SSBlockSCS_Error: z.string().nullable().optional(),
86
+ SSBlockPatternOptions: z.array(z.string()).nullable().optional(),
87
+ SSBlockPatternOptions_Error: z.string().nullable().optional(),
88
+ SSBlockPattern: z.string().nullable().optional(),
89
+ SSBlockPattern_Error: z.string().nullable().optional(),
90
+ NRaster: z.number().nullable().optional(),
91
+ GSCN_First: z.number().int().nullable().optional(),
92
+ GSCN_Last: z.number().int().nullable().optional(),
93
+ GSCN_StepSize: z.number().int().nullable().optional(),
94
+ GSCN: z.number().int().nullable().optional(),
95
+ GSCN_Error: z.string().nullable().optional(),
96
+ SSREF: z.number().nullable().optional(),
97
+ SSREF_Error: z.string().nullable().optional(),
98
+ SSBlockNREF: z.number().int().nullable().optional(),
99
+ N: z.number().int().nullable().optional(),
100
+ N_Error: z.string().nullable().optional(),
101
+ MOptions: z.array(z.number()).nullable().optional(),
102
+ MOptions_Error: z.string().nullable().optional(),
103
+ M: z.number().int().nullable().optional(),
104
+ M_Error: z.string().nullable().optional(),
105
+ });
106
+ const apiResultSchema = z.object({
107
+ Mode: ModeEnum.nullable().optional(),
108
+ Mode_Error: z.string().nullable().optional(),
109
+ DataTransmissionDirection: DirectionEnum.nullable().optional(),
110
+ DataTransmissionDirection_Error: z.string().nullable().optional(),
111
+ NREF: z.string().nullable().optional(),
112
+ NREF_Error: z.string().nullable().optional(),
113
+ FREF: z.string().nullable().optional(),
114
+ FREF_Error: z.string().nullable().optional(),
115
+ FGlobalRaster: z.string().nullable().optional(), // 全局频率栅格,kHz
116
+ FrequencyRangeDesignation: z.string().nullable().optional(), // 频率范围标识符
117
+ BandOptions: z.array(z.string()).nullable().optional(),
118
+ BandOptions_Error: z.string().nullable().optional(),
119
+ Band: z.string().nullable().optional(),
120
+ Band_Error: z.string().nullable().optional(),
121
+ FREF_Low: z.string().nullable().optional(),
122
+ FREF_High: z.string().nullable().optional(),
123
+ FRasterOptions_Error: z.string().nullable().optional(),
124
+ FRasterOptions: z.array(z.string()).nullable().optional(),
125
+ FRaster: z.string().nullable().optional(),
126
+ FRaster_Error: z.string().nullable().optional(),
127
+ NREF_StepSize: z.string().nullable().optional(),
128
+ NREF_First: z.string().nullable().optional(),
129
+ NREF_Last: z.string().nullable().optional(),
130
+ DuplexMode: DuplexModeEnum.nullable().optional(),
131
+ SCSOptions: z.array(z.string()).nullable().optional(),
132
+ SCSOptions_Error: z.string().nullable().optional(),
133
+ SCS: z.string().nullable().optional(),
134
+ SCS_Error: z.string().nullable().optional(),
135
+ BandwidthOptions: z.array(z.string()).nullable().optional(),
136
+ BandwidthOptions_Error: z.string().nullable().optional(),
137
+ Bandwidth: z.string().nullable().optional(),
138
+ Bandwidth_Error: z.string().nullable().optional(),
139
+ NRB: z.string().nullable().optional(),
140
+ MinimumGuardband: z.string().nullable().optional(),
141
+ FREF_AvailableLow: z.string().nullable().optional(),
142
+ FREF_AvailableHigh: z.string().nullable().optional(),
143
+ SSBlockSCSOptions: z.array(z.string()).nullable().optional(),
144
+ SSBlockSCSOptions_Error: z.string().nullable().optional(),
145
+ SSBlockSCS: z.string().nullable().optional(),
146
+ SSBlockSCS_Error: z.string().nullable().optional(),
147
+ SSBlockPatternOptions: z.array(z.string()).nullable().optional(),
148
+ SSBlockPatternOptions_Error: z.string().nullable().optional(),
149
+ SSBlockPattern: z.string().nullable().optional(),
150
+ SSBlockPattern_Error: z.string().nullable().optional(),
151
+ NRaster: z.string().nullable().optional(),
152
+ GSCN_First: z.string().nullable().optional(),
153
+ GSCN_Last: z.string().nullable().optional(),
154
+ GSCN_StepSize: z.string().nullable().optional(),
155
+ GSCN: z.string().nullable().optional(),
156
+ GSCN_Error: z.string().nullable().optional(),
157
+ SSREF: z.string().nullable().optional(),
158
+ SSREF_Error: z.string().nullable().optional(),
159
+ SSBlockNREF: z.string().nullable().optional(),
160
+ N: z.string().nullable().optional(),
161
+ N_Error: z.string().nullable().optional(),
162
+ MOptions: z.array(z.string()).nullable().optional(),
163
+ MOptions_Error: z.string().nullable().optional(),
164
+ M: z.string().nullable().optional(),
165
+ M_Error: z.string().nullable().optional(),
166
+ });
167
+ this.mcpServer.registerTool("arfcn_calculator_5g_compute", {
168
+ title: "5G/NR频点频率转换与对应频带信息",
169
+ description: "支持5G NR网络中频率与频点之间的相互转换,并附带特定频段的详细信息展示",
170
+ inputSchema: computeInputSchema,
171
+ outputSchema: computeOutputSchema,
172
+ }, async (input) => {
173
+ try {
174
+ const apiRequest = {
175
+ Mode: input.Mode ?? (input.NREF && "n"),
176
+ DataTransmissionDirection: input.DataTransmissionDirection,
177
+ Band: input.Band?.replaceAll("b", "").replaceAll("B", "") ?? null,
178
+ NERF: input.NREF?.toString() ?? null,
179
+ FERF: input.FREF?.toString() ?? null,
180
+ Bandwidth: input.Bandwidth?.toString() ?? null,
181
+ FRaster: input.FRaster?.toString() ?? null,
182
+ SCS: input.SCS?.toString() ?? null,
183
+ SSBlockSCS: input.SSBlockSCS?.toString() ?? null,
184
+ GSCN: input.GSCN?.toString() ?? null,
185
+ SSREF: input.SSREF?.toString() ?? null,
186
+ SSBlockPattern: input.SSBlockPattern?.toString() ?? null,
187
+ N: input.N?.toString() ?? null,
188
+ M: input.M?.toString() ?? null,
189
+ };
190
+ const result = await safeApiPost("/api/arfcn-calculator-5g/compute", apiRequest);
191
+ const apiResult = apiResultSchema.parse(result);
192
+ return {
193
+ content: [
194
+ {
195
+ type: "text",
196
+ text: JSON.stringify(apiResult, null, 2),
197
+ },
198
+ ],
199
+ structuredContent: apiResult,
200
+ };
201
+ }
202
+ catch {
203
+ return mcpExResponse;
204
+ }
205
+ });
206
+ };
207
+ }
@@ -0,0 +1,207 @@
1
+ import * as z from "zod/v4";
2
+ import { safeApiPost } from "../utils/request.js";
3
+ import { mcpExResponse } from "../utils/response.js";
4
+ export default class ArfcnCalculatorNTN {
5
+ mcpServer;
6
+ constructor(mcpServer) {
7
+ mcpServer = mcpServer;
8
+ }
9
+ registerAllTools = () => {
10
+ this.registerComputeTool();
11
+ };
12
+ registerComputeTool = () => {
13
+ const ModeEnum = z.enum(["n", "f"]);
14
+ const DirectionEnum = z.enum(["uplink", "downlink"]);
15
+ const DuplexModeEnum = z.enum(["TDD", "FDD"]);
16
+ const computeInputSchema = z.object({
17
+ Mode: ModeEnum.nullable().optional().describe("计算模式,例如 n 表示频点模式,f 表示频率模式"),
18
+ DataTransmissionDirection: DirectionEnum.describe("数据传输方向,可选上行(uplink)、下行(downlink)"),
19
+ NREF: z.number().int().nullable().optional().describe('频点参考值(NREF),例如 `"504999"`。用于在频点模式(Mode=`"n"`)下确定中心频率。'),
20
+ FREF: z.number().nullable().optional().describe('频率参考值(FREF),例如 `"3500.0"`。用于在频率模式(Mode=`"f"`)下直接指定中心频率。'),
21
+ Band: z.string().nullable().optional().describe('频段标识符,例如 `"n78"`、`"n41"` 等。用于确定频段的上下行配置、信道栅格(raster)及边界频率。'),
22
+ FRaster: z.number().int().nullable().optional().describe("频率栅格(Frequency Raster),单位为 kHz,例如 5、15。用于频点与频率之间的转换计算。"),
23
+ SCS: z.number().int().nullable().optional().describe("子载波间隔(Subcarrier Spacing),单位为 kHz,例如 15、30。影响资源块结构和带宽计算。"),
24
+ Bandwidth: z
25
+ .number()
26
+ .nullable()
27
+ .optional()
28
+ .describe("信道带宽(Channel Bandwidth),单位为 MHz,例如 5、20、100。用于确定传输带宽配置(Transmission Bandwidth Configuration)。"),
29
+ SSBlockSCS: z.number().int().nullable().optional().describe("SSB(Synchronization Signal Block)的子载波间隔,单位为 kHz,例如 15、30。"),
30
+ SSBlockPattern: z
31
+ .string()
32
+ .nullable()
33
+ .optional()
34
+ .describe('SSB 波束扫描图案类型,例如 `"Case A"`、`"Case B"`、`"Case C"`(FR1)或 `"Case D"`、`"Case E"`(FR2),影响 SSB 时域位置和数量。'),
35
+ GSCN: z
36
+ .number()
37
+ .int()
38
+ .nullable()
39
+ .optional()
40
+ .describe("全局同步信道号(Global Synchronization Channel Number),用于粗粒度频率搜索,特别是在非服务小区初始接入时。例如 504990"),
41
+ SSREF: z.number().nullable().optional().describe("SSB 中心频率对应的参考频率(单位:Hz 或 MHz,依上下文而定),常用于从 GSCN 或 SSB 配置反推绝对频率。"),
42
+ N: z.number().int().nullable().optional().describe("用于 SSB 频域位置计算的参数 N,通常与 GSCN 或 raster 相关。例如 6312"),
43
+ M: z.number().int().nullable().optional().describe("SSB 频域偏移步长因子,取决于频段和 SSB 子载波间隔。例如 1、3、5"),
44
+ });
45
+ const computeOutputSchema = z.object({
46
+ Mode: ModeEnum.nullable().optional(),
47
+ Mode_Error: z.string().nullable().optional(),
48
+ DataTransmissionDirection: DirectionEnum.nullable().optional(),
49
+ DataTransmissionDirection_Error: z.string().nullable().optional(),
50
+ NREF: z.number().int().nullable().optional(),
51
+ NREF_Error: z.string().nullable().optional(),
52
+ FREF: z.number().nullable().optional(),
53
+ FREF_Error: z.string().nullable().optional(),
54
+ FGlobalRaster: z.number().nullable().optional(), // 全局频率栅格,kHz(数值)
55
+ FrequencyRangeDesignation: z.string().nullable().optional(), // 频率范围标识符
56
+ BandOptions: z.array(z.string()).nullable().optional(),
57
+ BandOptions_Error: z.string().nullable().optional(),
58
+ Band: z.string().nullable().optional(),
59
+ Band_Error: z.string().nullable().optional(),
60
+ FREF_Low: z.number().nullable().optional(),
61
+ FREF_High: z.number().nullable().optional(),
62
+ FRasterOptions_Error: z.string().nullable().optional(),
63
+ FRasterOptions: z.array(z.string()).nullable().optional(),
64
+ FRaster: z.number().nullable().optional(),
65
+ FRaster_Error: z.string().nullable().optional(),
66
+ NREF_StepSize: z.number().int().nullable().optional(),
67
+ NREF_First: z.number().int().nullable().optional(),
68
+ NREF_Last: z.number().int().nullable().optional(),
69
+ DuplexMode: DuplexModeEnum.nullable().optional(),
70
+ SCSOptions: z.array(z.string()).nullable().optional(),
71
+ SCSOptions_Error: z.string().nullable().optional(),
72
+ SCS: z.string().nullable().optional(),
73
+ SCS_Error: z.string().nullable().optional(),
74
+ BandwidthOptions: z.array(z.string()).nullable().optional(),
75
+ BandwidthOptions_Error: z.string().nullable().optional(),
76
+ Bandwidth: z.number().nullable().optional(), // 带宽单位可能是 MHz,但以数字形式
77
+ Bandwidth_Error: z.string().nullable().optional(),
78
+ NRB: z.number().int().nullable().optional(), // 资源块数量,整数
79
+ MinimumGuardband: z.number().nullable().optional(), // 最小保护带宽,kHz(数值)
80
+ FREF_AvailableLow: z.number().nullable().optional(),
81
+ FREF_AvailableHigh: z.number().nullable().optional(),
82
+ SSBlockSCSOptions: z.array(z.number()).nullable().optional(),
83
+ SSBlockSCSOptions_Error: z.string().nullable().optional(),
84
+ SSBlockSCS: z.number().nullable().optional(),
85
+ SSBlockSCS_Error: z.string().nullable().optional(),
86
+ SSBlockPatternOptions: z.array(z.string()).nullable().optional(),
87
+ SSBlockPatternOptions_Error: z.string().nullable().optional(),
88
+ SSBlockPattern: z.string().nullable().optional(),
89
+ SSBlockPattern_Error: z.string().nullable().optional(),
90
+ NRaster: z.number().nullable().optional(),
91
+ GSCN_First: z.number().int().nullable().optional(),
92
+ GSCN_Last: z.number().int().nullable().optional(),
93
+ GSCN_StepSize: z.number().int().nullable().optional(),
94
+ GSCN: z.number().int().nullable().optional(),
95
+ GSCN_Error: z.string().nullable().optional(),
96
+ SSREF: z.number().nullable().optional(),
97
+ SSREF_Error: z.string().nullable().optional(),
98
+ SSBlockNREF: z.number().int().nullable().optional(),
99
+ N: z.number().int().nullable().optional(),
100
+ N_Error: z.string().nullable().optional(),
101
+ MOptions: z.array(z.number()).nullable().optional(),
102
+ MOptions_Error: z.string().nullable().optional(),
103
+ M: z.number().int().nullable().optional(),
104
+ M_Error: z.string().nullable().optional(),
105
+ });
106
+ const apiResultSchema = z.object({
107
+ Mode: ModeEnum.nullable().optional(),
108
+ Mode_Error: z.string().nullable().optional(),
109
+ DataTransmissionDirection: DirectionEnum.nullable().optional(),
110
+ DataTransmissionDirection_Error: z.string().nullable().optional(),
111
+ NREF: z.string().nullable().optional(),
112
+ NREF_Error: z.string().nullable().optional(),
113
+ FREF: z.string().nullable().optional(),
114
+ FREF_Error: z.string().nullable().optional(),
115
+ FGlobalRaster: z.string().nullable().optional(), // 全局频率栅格,kHz
116
+ FrequencyRangeDesignation: z.string().nullable().optional(), // 频率范围标识符
117
+ BandOptions: z.array(z.string()).nullable().optional(),
118
+ BandOptions_Error: z.string().nullable().optional(),
119
+ Band: z.string().nullable().optional(),
120
+ Band_Error: z.string().nullable().optional(),
121
+ FREF_Low: z.string().nullable().optional(),
122
+ FREF_High: z.string().nullable().optional(),
123
+ FRasterOptions_Error: z.string().nullable().optional(),
124
+ FRasterOptions: z.array(z.string()).nullable().optional(),
125
+ FRaster: z.string().nullable().optional(),
126
+ FRaster_Error: z.string().nullable().optional(),
127
+ NREF_StepSize: z.string().nullable().optional(),
128
+ NREF_First: z.string().nullable().optional(),
129
+ NREF_Last: z.string().nullable().optional(),
130
+ DuplexMode: DuplexModeEnum.nullable().optional(),
131
+ SCSOptions: z.array(z.string()).nullable().optional(),
132
+ SCSOptions_Error: z.string().nullable().optional(),
133
+ SCS: z.string().nullable().optional(),
134
+ SCS_Error: z.string().nullable().optional(),
135
+ BandwidthOptions: z.array(z.string()).nullable().optional(),
136
+ BandwidthOptions_Error: z.string().nullable().optional(),
137
+ Bandwidth: z.string().nullable().optional(),
138
+ Bandwidth_Error: z.string().nullable().optional(),
139
+ NRB: z.string().nullable().optional(),
140
+ MinimumGuardband: z.string().nullable().optional(),
141
+ FREF_AvailableLow: z.string().nullable().optional(),
142
+ FREF_AvailableHigh: z.string().nullable().optional(),
143
+ SSBlockSCSOptions: z.array(z.string()).nullable().optional(),
144
+ SSBlockSCSOptions_Error: z.string().nullable().optional(),
145
+ SSBlockSCS: z.string().nullable().optional(),
146
+ SSBlockSCS_Error: z.string().nullable().optional(),
147
+ SSBlockPatternOptions: z.array(z.string()).nullable().optional(),
148
+ SSBlockPatternOptions_Error: z.string().nullable().optional(),
149
+ SSBlockPattern: z.string().nullable().optional(),
150
+ SSBlockPattern_Error: z.string().nullable().optional(),
151
+ NRaster: z.string().nullable().optional(),
152
+ GSCN_First: z.string().nullable().optional(),
153
+ GSCN_Last: z.string().nullable().optional(),
154
+ GSCN_StepSize: z.string().nullable().optional(),
155
+ GSCN: z.string().nullable().optional(),
156
+ GSCN_Error: z.string().nullable().optional(),
157
+ SSREF: z.string().nullable().optional(),
158
+ SSREF_Error: z.string().nullable().optional(),
159
+ SSBlockNREF: z.string().nullable().optional(),
160
+ N: z.string().nullable().optional(),
161
+ N_Error: z.string().nullable().optional(),
162
+ MOptions: z.array(z.string()).nullable().optional(),
163
+ MOptions_Error: z.string().nullable().optional(),
164
+ M: z.string().nullable().optional(),
165
+ M_Error: z.string().nullable().optional(),
166
+ });
167
+ this.mcpServer.registerTool("arfcn_calculator_ntn_compute", {
168
+ title: "NTN频点频率转换与对应频带信息",
169
+ description: "支持NTN网络中频率与频点之间的相互转换,并附带特定频段的详细信息展示",
170
+ inputSchema: computeInputSchema,
171
+ outputSchema: computeOutputSchema,
172
+ }, async (input) => {
173
+ try {
174
+ const apiRequest = {
175
+ Mode: input.Mode ?? (input.NREF && "n"),
176
+ DataTransmissionDirection: input.DataTransmissionDirection,
177
+ Band: input.Band?.replaceAll("b", "").replaceAll("B", "") ?? null,
178
+ NERF: input.NREF?.toString() ?? null,
179
+ FERF: input.FREF?.toString() ?? null,
180
+ Bandwidth: input.Bandwidth?.toString() ?? null,
181
+ FRaster: input.FRaster?.toString() ?? null,
182
+ SCS: input.SCS?.toString() ?? null,
183
+ SSBlockSCS: input.SSBlockSCS?.toString() ?? null,
184
+ GSCN: input.GSCN?.toString() ?? null,
185
+ SSREF: input.SSREF?.toString() ?? null,
186
+ SSBlockPattern: input.SSBlockPattern?.toString() ?? null,
187
+ N: input.N?.toString() ?? null,
188
+ M: input.M?.toString() ?? null,
189
+ };
190
+ const result = await safeApiPost("/api/arfcn-calculator-ntn/compute", apiRequest);
191
+ const apiResult = apiResultSchema.parse(result);
192
+ return {
193
+ content: [
194
+ {
195
+ type: "text",
196
+ text: JSON.stringify(apiResult, null, 2),
197
+ },
198
+ ],
199
+ structuredContent: apiResult,
200
+ };
201
+ }
202
+ catch {
203
+ return mcpExResponse;
204
+ }
205
+ });
206
+ };
207
+ }
@@ -0,0 +1,21 @@
1
+ const BASE_URL = "https://rfhub.cn";
2
+ /**
3
+ * 安全的 POST JSON 请求函数(自动拼接 BASE_URL)
4
+ * @param {string} endpoint - 接口路径,如 '/api/arfcn-calculator-4g'
5
+ * @param {object} data - 请求体数据
6
+ * @returns {Promise<object | null>}
7
+ */
8
+ export async function safeApiPost(endpoint, data) {
9
+ const url = BASE_URL + endpoint;
10
+ const response = await fetch(url, {
11
+ method: "POST",
12
+ body: JSON.stringify(data),
13
+ headers: {
14
+ "Content-Type": "application/json",
15
+ },
16
+ });
17
+ if (!response.ok)
18
+ throw new Error("Network response was not ok");
19
+ const text = await response.text();
20
+ return JSON.parse(text);
21
+ }
@@ -0,0 +1,8 @@
1
+ export const mcpExResponse = {
2
+ content: [
3
+ {
4
+ type: "text",
5
+ text: "计算异常",
6
+ },
7
+ ],
8
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "rfhub-mcp",
3
+ "version": "0.3.0",
4
+ "description": "",
5
+ "main": "dist/server.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "rfhub-mcp": "./dist/server.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "rimraf dist && tsc",
15
+ "start": "tsx src/server.ts"
16
+ },
17
+ "keywords": [],
18
+ "author": "",
19
+ "license": "MIT",
20
+ "packageManager": "pnpm@10.24.0",
21
+ "devDependencies": {
22
+ "@types/node": "^25.0.10",
23
+ "rimraf": "^6.1.2",
24
+ "tsx": "^4.21.0",
25
+ "typescript": "^5.9.3"
26
+ },
27
+ "dependencies": {
28
+ "@modelcontextprotocol/sdk": "^1.25.3",
29
+ "zod": "^4.3.6"
30
+ }
31
+ }