siglent-sdg-mcp 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 +467 -0
- package/build/connection.d.ts +29 -0
- package/build/connection.js +252 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +52 -0
- package/build/tools/arbitrary.d.ts +2 -0
- package/build/tools/arbitrary.js +83 -0
- package/build/tools/basic-wave.d.ts +2 -0
- package/build/tools/basic-wave.js +172 -0
- package/build/tools/burst.d.ts +2 -0
- package/build/tools/burst.js +191 -0
- package/build/tools/connection.d.ts +2 -0
- package/build/tools/connection.js +90 -0
- package/build/tools/modulation.d.ts +2 -0
- package/build/tools/modulation.js +200 -0
- package/build/tools/output.d.ts +2 -0
- package/build/tools/output.js +87 -0
- package/build/tools/scpi.d.ts +2 -0
- package/build/tools/scpi.js +65 -0
- package/build/tools/sweep.d.ts +2 -0
- package/build/tools/sweep.js +221 -0
- package/build/tools/utility.d.ts +2 -0
- package/build/tools/utility.js +126 -0
- package/package.json +49 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { connection } from "../connection.js";
|
|
3
|
+
export function registerSweepTools(server) {
|
|
4
|
+
server.tool("get_sweep", "Get the sweep parameters of a channel, including state, time, start/stop frequencies, sweep mode, direction, trigger source, carrier settings, and marker.", {
|
|
5
|
+
channel: z.enum(["C1", "C2"]).describe("Channel to query"),
|
|
6
|
+
}, { readOnlyHint: true }, async ({ channel }) => {
|
|
7
|
+
try {
|
|
8
|
+
const response = await connection.query(`${channel}:SWWV?`);
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: "text",
|
|
13
|
+
text: response,
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
isError: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
server.tool("configure_sweep", "Configure sweep on a channel. State must be set to ON before other parameters. Builds a single SWWV command with all specified parameters.", {
|
|
31
|
+
channel: z.enum(["C1", "C2"]).describe("Channel to configure"),
|
|
32
|
+
state: z
|
|
33
|
+
.enum(["ON", "OFF"])
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Sweep state. Must be ON before setting other params."),
|
|
36
|
+
time: z
|
|
37
|
+
.number()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Sweep time in seconds"),
|
|
40
|
+
start: z
|
|
41
|
+
.number()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Start frequency in Hz"),
|
|
44
|
+
stop: z
|
|
45
|
+
.number()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Stop frequency in Hz"),
|
|
48
|
+
center: z
|
|
49
|
+
.number()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Center frequency in Hz"),
|
|
52
|
+
span: z
|
|
53
|
+
.number()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Frequency span in Hz"),
|
|
56
|
+
sweep_mode: z
|
|
57
|
+
.enum(["LINE", "LOG", "STEP"])
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Sweep mode: LINE (linear), LOG (logarithmic), or STEP"),
|
|
60
|
+
direction: z
|
|
61
|
+
.enum(["UP", "DOWN", "UP_DOWN"])
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Sweep direction"),
|
|
64
|
+
trigger_source: z
|
|
65
|
+
.enum(["EXT", "INT", "MAN"])
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Trigger source: EXT (external), INT (internal), MAN (manual)"),
|
|
68
|
+
trigger_out: z
|
|
69
|
+
.enum(["ON", "OFF"])
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Trigger output state"),
|
|
72
|
+
trigger_edge: z
|
|
73
|
+
.enum(["RISE", "FALL"])
|
|
74
|
+
.optional()
|
|
75
|
+
.describe("Trigger edge (only for EXT or MAN trigger source)"),
|
|
76
|
+
start_hold_time: z
|
|
77
|
+
.number()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("Start hold time in seconds (0-300)"),
|
|
80
|
+
end_hold_time: z
|
|
81
|
+
.number()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("End hold time in seconds (0-300)"),
|
|
84
|
+
return_time: z
|
|
85
|
+
.number()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Return time in seconds (0-300)"),
|
|
88
|
+
symmetry: z
|
|
89
|
+
.number()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Symmetry in % (0-100) when direction is UP_DOWN"),
|
|
92
|
+
marker_state: z
|
|
93
|
+
.enum(["ON", "OFF"])
|
|
94
|
+
.optional()
|
|
95
|
+
.describe("Frequency marker state"),
|
|
96
|
+
marker_frequency: z
|
|
97
|
+
.number()
|
|
98
|
+
.optional()
|
|
99
|
+
.describe("Marker frequency in Hz"),
|
|
100
|
+
manual_trigger: z
|
|
101
|
+
.boolean()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("Send a manual trigger (only when trigger source is MAN)"),
|
|
104
|
+
carrier_waveform: z
|
|
105
|
+
.enum(["SINE", "SQUARE", "RAMP", "ARB"])
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("Carrier waveform type"),
|
|
108
|
+
carrier_frequency: z
|
|
109
|
+
.number()
|
|
110
|
+
.optional()
|
|
111
|
+
.describe("Carrier frequency in Hz"),
|
|
112
|
+
carrier_amplitude: z
|
|
113
|
+
.number()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe("Carrier amplitude in Vpp"),
|
|
116
|
+
carrier_offset: z
|
|
117
|
+
.number()
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("Carrier DC offset in volts"),
|
|
120
|
+
carrier_phase: z
|
|
121
|
+
.number()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe("Carrier phase in degrees (0-360)"),
|
|
124
|
+
}, { readOnlyHint: false }, async ({ channel, state, time, start, stop, center, span, sweep_mode, direction, trigger_source, trigger_out, trigger_edge, start_hold_time, end_hold_time, return_time, symmetry, marker_state, marker_frequency, manual_trigger, carrier_waveform, carrier_frequency, carrier_amplitude, carrier_offset, carrier_phase, }) => {
|
|
125
|
+
try {
|
|
126
|
+
const sent = [];
|
|
127
|
+
// State must be set first and separately
|
|
128
|
+
if (state) {
|
|
129
|
+
const cmd = `${channel}:SWWV STATE,${state}`;
|
|
130
|
+
await connection.sendCommand(cmd);
|
|
131
|
+
sent.push(cmd);
|
|
132
|
+
}
|
|
133
|
+
// Build remaining parameters as a single command
|
|
134
|
+
const parts = [];
|
|
135
|
+
if (time !== undefined)
|
|
136
|
+
parts.push(`TIME,${time}`);
|
|
137
|
+
if (start !== undefined)
|
|
138
|
+
parts.push(`START,${start}`);
|
|
139
|
+
if (stop !== undefined)
|
|
140
|
+
parts.push(`STOP,${stop}`);
|
|
141
|
+
if (center !== undefined)
|
|
142
|
+
parts.push(`CENTER,${center}`);
|
|
143
|
+
if (span !== undefined)
|
|
144
|
+
parts.push(`SPAN,${span}`);
|
|
145
|
+
if (sweep_mode)
|
|
146
|
+
parts.push(`SWMD,${sweep_mode}`);
|
|
147
|
+
if (direction)
|
|
148
|
+
parts.push(`DIR,${direction}`);
|
|
149
|
+
if (trigger_source)
|
|
150
|
+
parts.push(`TRSR,${trigger_source}`);
|
|
151
|
+
if (trigger_out)
|
|
152
|
+
parts.push(`TRMD,${trigger_out}`);
|
|
153
|
+
if (trigger_edge)
|
|
154
|
+
parts.push(`EDGE,${trigger_edge}`);
|
|
155
|
+
if (start_hold_time !== undefined)
|
|
156
|
+
parts.push(`STARTTIME,${start_hold_time}`);
|
|
157
|
+
if (end_hold_time !== undefined)
|
|
158
|
+
parts.push(`ENDTIME,${end_hold_time}`);
|
|
159
|
+
if (return_time !== undefined)
|
|
160
|
+
parts.push(`BACKTIME,${return_time}`);
|
|
161
|
+
if (symmetry !== undefined)
|
|
162
|
+
parts.push(`SYM,${symmetry}`);
|
|
163
|
+
if (marker_state)
|
|
164
|
+
parts.push(`MARK_STATE,${marker_state}`);
|
|
165
|
+
if (marker_frequency !== undefined)
|
|
166
|
+
parts.push(`MARK_FREQ,${marker_frequency}`);
|
|
167
|
+
if (carrier_waveform)
|
|
168
|
+
parts.push(`CARR,WVTP,${carrier_waveform}`);
|
|
169
|
+
if (carrier_frequency !== undefined)
|
|
170
|
+
parts.push(`CARR,FRQ,${carrier_frequency}`);
|
|
171
|
+
if (carrier_amplitude !== undefined)
|
|
172
|
+
parts.push(`CARR,AMP,${carrier_amplitude}`);
|
|
173
|
+
if (carrier_offset !== undefined)
|
|
174
|
+
parts.push(`CARR,OFST,${carrier_offset}`);
|
|
175
|
+
if (carrier_phase !== undefined)
|
|
176
|
+
parts.push(`CARR,PHSE,${carrier_phase}`);
|
|
177
|
+
if (parts.length > 0) {
|
|
178
|
+
const cmd = `${channel}:SWWV ${parts.join(",")}`;
|
|
179
|
+
await connection.sendCommand(cmd);
|
|
180
|
+
sent.push(cmd);
|
|
181
|
+
}
|
|
182
|
+
// Manual trigger is sent separately
|
|
183
|
+
if (manual_trigger) {
|
|
184
|
+
const cmd = `${channel}:SWWV MTRIG`;
|
|
185
|
+
await connection.sendCommand(cmd);
|
|
186
|
+
sent.push(cmd);
|
|
187
|
+
}
|
|
188
|
+
if (sent.length === 0) {
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: "Error: At least one parameter must be specified.",
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
isError: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: `Sweep configured:\n${sent.join("\n")}`,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
isError: true,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=sweep.js.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { connection } from "../connection.js";
|
|
3
|
+
export function registerUtilityTools(server) {
|
|
4
|
+
server.tool("reset", "Reset the waveform generator to factory default settings (*RST).", {}, { readOnlyHint: false }, async () => {
|
|
5
|
+
try {
|
|
6
|
+
await connection.sendCommand("*RST");
|
|
7
|
+
return {
|
|
8
|
+
content: [
|
|
9
|
+
{
|
|
10
|
+
type: "text",
|
|
11
|
+
text: "Device reset to factory defaults.",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
isError: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
server.tool("copy_channel", "Copy all parameters from one channel to another.", {
|
|
29
|
+
source: z.enum(["C1", "C2"]).describe("Source channel to copy from"),
|
|
30
|
+
destination: z.enum(["C1", "C2"]).describe("Destination channel to copy to"),
|
|
31
|
+
}, { readOnlyHint: false }, async ({ source, destination }) => {
|
|
32
|
+
try {
|
|
33
|
+
if (source === destination) {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: "Error: Source and destination channels must be different.",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
isError: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
await connection.sendCommand(`PACP ${destination},${source}`);
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: `Parameters copied from ${source} to ${destination}.`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: "text",
|
|
59
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
isError: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
server.tool("configure_sync", "Configure the sync output signal for a channel.", {
|
|
67
|
+
channel: z.enum(["C1", "C2"]).describe("Channel to configure"),
|
|
68
|
+
state: z.enum(["ON", "OFF"]).describe("Sync output state"),
|
|
69
|
+
type: z
|
|
70
|
+
.enum(["CH1", "CH2", "MOD_CH1", "MOD_CH2"])
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Sync source type"),
|
|
73
|
+
}, { readOnlyHint: false }, async ({ channel, state, type }) => {
|
|
74
|
+
try {
|
|
75
|
+
let cmd = `${channel}:SYNC ${state}`;
|
|
76
|
+
if (type) {
|
|
77
|
+
cmd += `,TYPE,${type}`;
|
|
78
|
+
}
|
|
79
|
+
await connection.sendCommand(cmd);
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: "text",
|
|
84
|
+
text: `Sync configured: ${cmd}`,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return {
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
server.tool("equal_phase", "Synchronize the phase of both channels (EQPHASE). Aligns the phase relationship between C1 and C2.", {}, { readOnlyHint: false }, async () => {
|
|
102
|
+
try {
|
|
103
|
+
await connection.sendCommand("EQPHASE");
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: "Phase synchronized between channels.",
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
isError: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=utility.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "siglent-sdg-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Siglent SDG waveform generator control via SCPI over TCP sockets",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"siglent-sdg-mcp": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build/**/*.js",
|
|
12
|
+
"build/**/*.d.ts"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"watch": "tsc --watch",
|
|
17
|
+
"start": "node build/index.js",
|
|
18
|
+
"dev": "tsc && node build/index.js",
|
|
19
|
+
"inspector": "npx @modelcontextprotocol/inspector node build/index.js",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"siglent",
|
|
28
|
+
"waveform-generator",
|
|
29
|
+
"scpi"
|
|
30
|
+
],
|
|
31
|
+
"author": "Magnus Johansson",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/magnusjohansson/siglent-sdg-mcp.git"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
39
|
+
"zod": "^3.25.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"typescript": "^5.7.0",
|
|
44
|
+
"vitest": "^3.0.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20"
|
|
48
|
+
}
|
|
49
|
+
}
|