Skip to content

Commit 36cc065

Browse files
Merge pull request #25 from cervonwong/feature/configurable-mcp-server-url
Add configurable MCP server IP address and port instead of hard coded http://127.0.0.1:50300/sse
2 parents f068eeb + 95366e5 commit 36cc065

5 files changed

Lines changed: 402 additions & 24 deletions

File tree

DotNetPlugin.Impl/MCPServer.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,30 @@ class SimpleMcpServer
1919
private readonly HttpListener _listener = new HttpListener();
2020
private readonly Dictionary<string, MethodInfo> _commands = new Dictionary<string, MethodInfo>(StringComparer.OrdinalIgnoreCase);
2121
private readonly Type _targetType;
22+
private readonly McpServerConfig _config;
2223

2324
public bool IsActivelyDebugging
2425
{
2526
get { return Bridge.DbgIsDebugging(); }
2627
set { /* accept assignments from event callbacks; actual state comes from Bridge */ }
2728
}
2829

29-
public SimpleMcpServer(Type commandSourceType)
30+
public SimpleMcpServer(Type commandSourceType) : this(commandSourceType, McpServerConfig.Load())
31+
{
32+
}
33+
34+
public SimpleMcpServer(Type commandSourceType, McpServerConfig config)
3035
{
3136
//DisableServerHeader(); //Prob not needed
3237
_targetType = commandSourceType;
33-
string IPAddress = "+";
34-
Console.WriteLine("MCP server lising on " + IPAddress);
35-
_listener.Prefixes.Add("http://" + IPAddress + ":50300/sse/"); //Request come in without a trailing '/' but are still handled
36-
_listener.Prefixes.Add("http://" + IPAddress + ":50300/message/");
38+
_config = config ?? McpServerConfig.Load();
39+
40+
string baseUrl = _config.GetBaseUrl();
41+
Console.WriteLine($"MCP server listening on {baseUrl}");
42+
Console.WriteLine($"Connect via: {_config.GetDisplayUrl()}");
43+
44+
_listener.Prefixes.Add($"{baseUrl}sse/"); //Request come in without a trailing '/' but are still handled
45+
_listener.Prefixes.Add($"{baseUrl}message/");
3746

3847
//_listener.Prefixes.Add("http://127.0.0.1:50300/sse/"); //Request come in without a trailing '/' but are still handled
3948
//_listener.Prefixes.Add("http://127.0.0.1:50300/message/");
@@ -979,6 +988,6 @@ private string GetJsonSchemaType(Type type)
979988

980989

981990

982-
991+
983992
}
984993
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
using System;
2+
using System.Drawing;
3+
using System.Windows.Forms;
4+
5+
namespace DotNetPlugin
6+
{
7+
/// <summary>
8+
/// Configuration dialog for the MCP server settings.
9+
/// Allows users to configure the IP address and port for the HTTP listener.
10+
/// </summary>
11+
public class McpConfigDialog : Form
12+
{
13+
private TextBox txtIpAddress;
14+
private NumericUpDown numPort;
15+
private Button btnSave;
16+
private Button btnCancel;
17+
private Label lblHelp;
18+
private Label lblCurrentUrl;
19+
20+
public string IpAddress => txtIpAddress.Text.Trim();
21+
public int Port => (int)numPort.Value;
22+
23+
public McpConfigDialog(McpServerConfig currentConfig)
24+
{
25+
InitializeComponents(currentConfig);
26+
UpdateUrlPreview();
27+
}
28+
29+
private void InitializeComponents(McpServerConfig config)
30+
{
31+
// Form settings
32+
Text = "MCP Server Configuration";
33+
Width = 420;
34+
Height = 280;
35+
FormBorderStyle = FormBorderStyle.FixedDialog;
36+
StartPosition = FormStartPosition.CenterParent;
37+
MaximizeBox = false;
38+
MinimizeBox = false;
39+
40+
// IP Address Label
41+
var lblIp = new Label
42+
{
43+
Text = "IP Address:",
44+
Left = 15,
45+
Top = 20,
46+
Width = 80,
47+
TextAlign = ContentAlignment.MiddleRight
48+
};
49+
50+
// IP Address TextBox
51+
txtIpAddress = new TextBox
52+
{
53+
Left = 100,
54+
Top = 18,
55+
Width = 180,
56+
Text = config.IpAddress
57+
};
58+
txtIpAddress.TextChanged += (s, e) => UpdateUrlPreview();
59+
60+
// IP Help Label
61+
var lblIpHelp = new Label
62+
{
63+
Text = "(+, *, localhost, or IP)",
64+
Left = 285,
65+
Top = 20,
66+
Width = 110,
67+
ForeColor = Color.Gray,
68+
Font = new Font(Font.FontFamily, 8)
69+
};
70+
71+
// Port Label
72+
var lblPort = new Label
73+
{
74+
Text = "Port:",
75+
Left = 15,
76+
Top = 55,
77+
Width = 80,
78+
TextAlign = ContentAlignment.MiddleRight
79+
};
80+
81+
// Port NumericUpDown
82+
numPort = new NumericUpDown
83+
{
84+
Left = 100,
85+
Top = 53,
86+
Width = 100,
87+
Minimum = 1,
88+
Maximum = 65535,
89+
Value = config.Port
90+
};
91+
numPort.ValueChanged += (s, e) => UpdateUrlPreview();
92+
93+
// URL Preview Label
94+
var lblUrlTitle = new Label
95+
{
96+
Text = "Server URL:",
97+
Left = 15,
98+
Top = 95,
99+
Width = 80,
100+
TextAlign = ContentAlignment.MiddleRight
101+
};
102+
103+
lblCurrentUrl = new Label
104+
{
105+
Left = 100,
106+
Top = 95,
107+
Width = 280,
108+
Height = 20,
109+
ForeColor = Color.DarkBlue,
110+
Font = new Font(Font.FontFamily, 9, FontStyle.Bold)
111+
};
112+
113+
// Help text
114+
lblHelp = new Label
115+
{
116+
Left = 15,
117+
Top = 125,
118+
Width = 375,
119+
Height = 60,
120+
Text = "Notes:\n" +
121+
"• Use '+' or '*' to listen on all interfaces (requires admin or urlacl)\n" +
122+
"• Use '127.0.0.1' or 'localhost' for local-only access\n" +
123+
"• Restart the MCP server after changing settings",
124+
ForeColor = Color.DimGray,
125+
Font = new Font(Font.FontFamily, 8)
126+
};
127+
128+
// Save Button
129+
btnSave = new Button
130+
{
131+
Text = "Save",
132+
Left = 210,
133+
Top = 195,
134+
Width = 85,
135+
Height = 30,
136+
DialogResult = DialogResult.OK
137+
};
138+
btnSave.Click += BtnSave_Click;
139+
140+
// Cancel Button
141+
btnCancel = new Button
142+
{
143+
Text = "Cancel",
144+
Left = 305,
145+
Top = 195,
146+
Width = 85,
147+
Height = 30,
148+
DialogResult = DialogResult.Cancel
149+
};
150+
151+
// Add controls
152+
Controls.AddRange(new Control[]
153+
{
154+
lblIp, txtIpAddress, lblIpHelp,
155+
lblPort, numPort,
156+
lblUrlTitle, lblCurrentUrl,
157+
lblHelp,
158+
btnSave, btnCancel
159+
});
160+
161+
AcceptButton = btnSave;
162+
CancelButton = btnCancel;
163+
}
164+
165+
private void UpdateUrlPreview()
166+
{
167+
string ip = string.IsNullOrWhiteSpace(txtIpAddress.Text) ? "+" : txtIpAddress.Text.Trim();
168+
string displayIp = (ip == "+" || ip == "*") ? "127.0.0.1" : ip;
169+
lblCurrentUrl.Text = $"http://{displayIp}:{(int)numPort.Value}/sse";
170+
}
171+
172+
private void BtnSave_Click(object sender, EventArgs e)
173+
{
174+
// Validate IP address
175+
if (!McpServerConfig.IsValidIpAddress(txtIpAddress.Text.Trim()))
176+
{
177+
MessageBox.Show(
178+
"Invalid IP address format.\n\nValid values:\n• + (all interfaces)\n• * (all interfaces)\n• localhost\n• A valid IP address (e.g., 127.0.0.1, 192.168.1.100)",
179+
"Validation Error",
180+
MessageBoxButtons.OK,
181+
MessageBoxIcon.Warning);
182+
txtIpAddress.Focus();
183+
txtIpAddress.SelectAll();
184+
DialogResult = DialogResult.None;
185+
return;
186+
}
187+
188+
// Port validation is handled by NumericUpDown control
189+
DialogResult = DialogResult.OK;
190+
}
191+
}
192+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.IO;
3+
using System.Web.Script.Serialization;
4+
5+
namespace DotNetPlugin
6+
{
7+
/// <summary>
8+
/// Configuration for the MCP server including IP address and port settings.
9+
/// Settings are persisted to a JSON file in the plugin directory.
10+
/// </summary>
11+
public class McpServerConfig
12+
{
13+
public string IpAddress { get; set; } = "+";
14+
public int Port { get; set; } = 50300;
15+
16+
private static string _configPath;
17+
18+
/// <summary>
19+
/// Gets the path to the configuration file in the plugin directory.
20+
/// </summary>
21+
public static string ConfigPath
22+
{
23+
get
24+
{
25+
if (_configPath == null)
26+
{
27+
var assemblyDir = Path.GetDirectoryName(typeof(McpServerConfig).Assembly.Location);
28+
_configPath = Path.Combine(assemblyDir, "mcp_config.json");
29+
}
30+
return _configPath;
31+
}
32+
}
33+
34+
/// <summary>
35+
/// Gets the base URL for the HTTP listener (e.g., "http://+:50300/").
36+
/// </summary>
37+
public string GetBaseUrl()
38+
{
39+
return $"http://{IpAddress}:{Port}/";
40+
}
41+
42+
/// <summary>
43+
/// Gets the SSE endpoint URL for display purposes.
44+
/// </summary>
45+
public string GetDisplayUrl()
46+
{
47+
string displayIp = IpAddress == "+" ? "127.0.0.1" : IpAddress;
48+
return $"http://{displayIp}:{Port}/sse";
49+
}
50+
51+
/// <summary>
52+
/// Loads the configuration from the JSON file, or returns default settings if the file doesn't exist.
53+
/// </summary>
54+
public static McpServerConfig Load()
55+
{
56+
try
57+
{
58+
if (File.Exists(ConfigPath))
59+
{
60+
var json = File.ReadAllText(ConfigPath);
61+
var serializer = new JavaScriptSerializer();
62+
var config = serializer.Deserialize<McpServerConfig>(json);
63+
if (config != null)
64+
{
65+
// Validate loaded values
66+
if (string.IsNullOrWhiteSpace(config.IpAddress))
67+
config.IpAddress = "+";
68+
if (config.Port < 1 || config.Port > 65535)
69+
config.Port = 50300;
70+
return config;
71+
}
72+
}
73+
}
74+
catch (Exception ex)
75+
{
76+
Console.WriteLine($"[McpServerConfig] Error loading config: {ex.Message}. Using defaults.");
77+
}
78+
79+
return new McpServerConfig();
80+
}
81+
82+
/// <summary>
83+
/// Saves the current configuration to the JSON file.
84+
/// </summary>
85+
public void Save()
86+
{
87+
try
88+
{
89+
var serializer = new JavaScriptSerializer();
90+
var json = serializer.Serialize(this);
91+
92+
// Ensure directory exists
93+
var dir = Path.GetDirectoryName(ConfigPath);
94+
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
95+
{
96+
Directory.CreateDirectory(dir);
97+
}
98+
99+
File.WriteAllText(ConfigPath, json);
100+
Console.WriteLine($"[McpServerConfig] Configuration saved to: {ConfigPath}");
101+
}
102+
catch (Exception ex)
103+
{
104+
Console.WriteLine($"[McpServerConfig] Error saving config: {ex.Message}");
105+
}
106+
}
107+
108+
/// <summary>
109+
/// Validates the IP address format.
110+
/// </summary>
111+
public static bool IsValidIpAddress(string ip)
112+
{
113+
if (string.IsNullOrWhiteSpace(ip))
114+
return false;
115+
116+
// Special values for HttpListener
117+
if (ip == "+" || ip == "*" || ip == "localhost")
118+
return true;
119+
120+
// Check for valid IP address format
121+
System.Net.IPAddress address;
122+
return System.Net.IPAddress.TryParse(ip, out address);
123+
}
124+
125+
/// <summary>
126+
/// Validates the port number.
127+
/// </summary>
128+
public static bool IsValidPort(int port)
129+
{
130+
return port >= 1 && port <= 65535;
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)