@syke1/mcp-server 1.4.10 → 1.4.12

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/dist/index.js CHANGED
@@ -607,6 +607,7 @@ async function main() {
607
607
  },
608
608
  // getAIInfoFn
609
609
  () => {
610
+ const forced = (0, config_1.getConfig)("aiProvider", "SYKE_AI_PROVIDER") || null;
610
611
  return {
611
612
  activeProvider: (0, provider_1.getProviderName)(),
612
613
  configured: {
@@ -614,6 +615,18 @@ async function main() {
614
615
  openai: !!(0, config_1.getConfig)("openaiKey", "OPENAI_KEY"),
615
616
  anthropic: !!(0, config_1.getConfig)("anthropicKey", "ANTHROPIC_KEY"),
616
617
  },
618
+ forced,
619
+ };
620
+ },
621
+ // setAIProviderFn
622
+ (provider) => {
623
+ (0, config_1.setConfig)("aiProvider", provider === "auto" ? null : provider);
624
+ (0, provider_1.resetAIProvider)();
625
+ const forced = (0, config_1.getConfig)("aiProvider", "SYKE_AI_PROVIDER") || null;
626
+ return {
627
+ success: true,
628
+ activeProvider: (0, provider_1.getProviderName)(),
629
+ forced,
617
630
  };
618
631
  });
619
632
  webServerHandle = { setFileCache: setWebFileCache };
@@ -465,6 +465,7 @@ document.addEventListener("DOMContentLoaded", async () => {
465
465
  setupProjectModal();
466
466
  setupLicenseModal();
467
467
  setupAIKeysModal();
468
+ setupAIProviderSelector();
468
469
  setupFileTree();
469
470
  initSSE();
470
471
  startHealthCheck();
@@ -4079,6 +4080,8 @@ function setupAIKeysModal() {
4079
4080
  var placeholders = { gemini: "AIzaSy...", openai: "sk-...", anthropic: "sk-ant-..." };
4080
4081
  input.placeholder = placeholders[provider] || "";
4081
4082
  }
4083
+ // Refresh provider selector to update enabled/disabled options
4084
+ if (window._refreshAIProviderSelector) window._refreshAIProviderSelector();
4082
4085
  }
4083
4086
  } catch (err) {
4084
4087
  var statusEl = row.querySelector(".ai-key-status");
@@ -4101,3 +4104,89 @@ function setupAIKeysModal() {
4101
4104
  }
4102
4105
  });
4103
4106
  }
4107
+
4108
+ // ══════════════════════════════════════════════════════════════
4109
+ // AI PROVIDER SELECTOR
4110
+ // ══════════════════════════════════════════════════════════════
4111
+ function setupAIProviderSelector() {
4112
+ const select = document.getElementById("ai-provider-select");
4113
+ const applyBtn = document.getElementById("btn-ai-apply");
4114
+ if (!select || !applyBtn) return;
4115
+
4116
+ // Refresh selector state from project-info
4117
+ async function refreshSelector() {
4118
+ try {
4119
+ const res = await fetch("/api/project-info");
4120
+ const info = await res.json();
4121
+ updateSelectorState(info.aiKeys || {}, info.aiProviderForced || null);
4122
+ } catch {}
4123
+ }
4124
+
4125
+ function updateSelectorState(aiKeys, forced) {
4126
+ // Enable/disable options based on configured keys
4127
+ var options = select.querySelectorAll("option");
4128
+ options.forEach(function(opt) {
4129
+ if (opt.value === "auto") {
4130
+ opt.disabled = false;
4131
+ return;
4132
+ }
4133
+ var hasKey = aiKeys[opt.value] || false;
4134
+ opt.disabled = !hasKey;
4135
+ });
4136
+
4137
+ // Set selected value
4138
+ if (forced && ["gemini", "openai", "anthropic"].includes(forced)) {
4139
+ select.value = forced;
4140
+ } else {
4141
+ select.value = "auto";
4142
+ }
4143
+ }
4144
+
4145
+ // APPLY button handler
4146
+ applyBtn.addEventListener("click", async function() {
4147
+ var provider = select.value;
4148
+ applyBtn.disabled = true;
4149
+ applyBtn.textContent = "...";
4150
+
4151
+ try {
4152
+ var res = await fetch("/api/set-ai-provider", {
4153
+ method: "POST",
4154
+ headers: { "Content-Type": "application/json" },
4155
+ body: JSON.stringify({ provider: provider }),
4156
+ });
4157
+ var data = await res.json();
4158
+ if (data.success) {
4159
+ applyBtn.textContent = "OK";
4160
+ applyBtn.classList.add("success");
4161
+ setTimeout(function() {
4162
+ applyBtn.textContent = "APPLY";
4163
+ applyBtn.classList.remove("success");
4164
+ }, 1500);
4165
+ // Refresh AI keys modal active display if visible
4166
+ var activeEl = document.getElementById("ai-keys-active");
4167
+ if (activeEl && data.activeProvider) {
4168
+ if (data.activeProvider !== "disabled") {
4169
+ activeEl.textContent = "Active: " + data.activeProvider;
4170
+ activeEl.style.color = "";
4171
+ } else {
4172
+ activeEl.textContent = "No AI provider configured";
4173
+ activeEl.style.color = "var(--text-secondary)";
4174
+ }
4175
+ }
4176
+ } else {
4177
+ applyBtn.textContent = "FAIL";
4178
+ setTimeout(function() { applyBtn.textContent = "APPLY"; }, 1500);
4179
+ }
4180
+ } catch {
4181
+ applyBtn.textContent = "ERR";
4182
+ setTimeout(function() { applyBtn.textContent = "APPLY"; }, 1500);
4183
+ }
4184
+ applyBtn.disabled = false;
4185
+ });
4186
+
4187
+ // Load initial state
4188
+ refreshSelector();
4189
+
4190
+ // Expose refresh for AI Keys modal to call after key changes
4191
+ window._refreshAIProviderSelector = refreshSelector;
4192
+ }
@@ -22,6 +22,13 @@
22
22
  <span id="license-badge" class="license-badge free">FREE</span>
23
23
  <button id="btn-license" class="top-btn license-btn">LICENSE</button>
24
24
  <button id="btn-ai-keys" class="top-btn ai-keys-btn">AI</button>
25
+ <select id="ai-provider-select" title="Select AI provider">
26
+ <option value="auto">AUTO</option>
27
+ <option value="gemini">GEMINI</option>
28
+ <option value="openai">OPENAI</option>
29
+ <option value="anthropic">ANTHROPIC</option>
30
+ </select>
31
+ <button id="btn-ai-apply" class="top-btn ai-apply-btn">APPLY</button>
25
32
  </div>
26
33
  <div class="project-selector">
27
34
  <span id="current-project" class="project-path">Loading...</span>
@@ -2066,6 +2066,54 @@ main {
2066
2066
  letter-spacing: 1.5px;
2067
2067
  }
2068
2068
 
2069
+ /* AI Provider Selector */
2070
+ #ai-provider-select {
2071
+ padding: 4px 8px;
2072
+ background: rgba(0,0,0,0.5);
2073
+ color: var(--text-primary);
2074
+ border: 1px solid var(--border);
2075
+ border-radius: 2px;
2076
+ font-family: inherit;
2077
+ font-size: 10px;
2078
+ letter-spacing: 1.5px;
2079
+ cursor: pointer;
2080
+ outline: none;
2081
+ transition: all 0.2s;
2082
+ margin-left: 4px;
2083
+ }
2084
+ #ai-provider-select:hover,
2085
+ #ai-provider-select:focus {
2086
+ border-color: var(--accent);
2087
+ box-shadow: 0 0 8px rgba(0,212,255,0.15);
2088
+ }
2089
+ #ai-provider-select option {
2090
+ background: var(--bg-secondary);
2091
+ color: var(--text-primary);
2092
+ font-family: inherit;
2093
+ padding: 4px 8px;
2094
+ }
2095
+ #ai-provider-select option:disabled {
2096
+ color: var(--text-secondary);
2097
+ opacity: 0.4;
2098
+ }
2099
+
2100
+ .ai-apply-btn {
2101
+ font-size: 9px !important;
2102
+ padding: 3px 10px !important;
2103
+ letter-spacing: 1.5px;
2104
+ color: var(--accent) !important;
2105
+ border-color: var(--accent-dim) !important;
2106
+ margin-left: 2px;
2107
+ }
2108
+ .ai-apply-btn:hover {
2109
+ background: rgba(0,212,255,0.08) !important;
2110
+ border-color: var(--accent) !important;
2111
+ }
2112
+ .ai-apply-btn.success {
2113
+ color: var(--risk-low) !important;
2114
+ border-color: var(--risk-low) !important;
2115
+ }
2116
+
2069
2117
  /* ═══════════════════════════════════════════ */
2070
2118
  /* AI Keys Modal */
2071
2119
  /* ═══════════════════════════════════════════ */
@@ -55,4 +55,9 @@ export declare function createWebServer(getGraphFn: () => DependencyGraph, initi
55
55
  openai: boolean;
56
56
  anthropic: boolean;
57
57
  };
58
+ forced: string | null;
59
+ }, setAIProviderFn?: (provider: string) => {
60
+ success: boolean;
61
+ activeProvider: string;
62
+ forced: string | null;
58
63
  }): WebServerHandle;
@@ -227,7 +227,7 @@ function acknowledgeWarnings() {
227
227
  function getAllWarnings() {
228
228
  return [...warningStore];
229
229
  }
230
- function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProjectRoot, getPackageName, getLicenseStatus, hasAIKeyFn, setLicenseKeyFn, setAIKeyFn, getAIInfoFn) {
230
+ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProjectRoot, getPackageName, getLicenseStatus, hasAIKeyFn, setLicenseKeyFn, setAIKeyFn, getAIInfoFn, setAIProviderFn) {
231
231
  const app = (0, express_1.default)();
232
232
  app.use(express_1.default.json());
233
233
  /** Check if current license is Pro (includes pro_trial) */
@@ -743,7 +743,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
743
743
  const maskedKey = rawKey.length > 10
744
744
  ? rawKey.substring(0, 9) + "····" + rawKey.substring(rawKey.length - 4)
745
745
  : "";
746
- const aiInfo = getAIInfoFn ? getAIInfoFn() : { activeProvider: "disabled", configured: { gemini: false, openai: false, anthropic: false } };
746
+ const aiInfo = getAIInfoFn ? getAIInfoFn() : { activeProvider: "disabled", configured: { gemini: false, openai: false, anthropic: false }, forced: null };
747
747
  res.json({
748
748
  projectRoot: getProjectRoot ? getProjectRoot() : graph.projectRoot,
749
749
  packageName: getPackageName ? getPackageName() : "",
@@ -758,6 +758,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
758
758
  sykeVersion,
759
759
  aiProvider: aiInfo.activeProvider,
760
760
  aiKeys: aiInfo.configured,
761
+ aiProviderForced: aiInfo.forced,
761
762
  });
762
763
  });
763
764
  // POST /api/set-license-key — Set or remove license key via dashboard
@@ -794,6 +795,25 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
794
795
  res.status(500).json({ success: false, error: err.message || "Unknown error" });
795
796
  }
796
797
  });
798
+ // POST /api/set-ai-provider — Set or clear forced AI provider
799
+ app.post("/api/set-ai-provider", (req, res) => {
800
+ if (!setAIProviderFn) {
801
+ res.status(501).json({ success: false, error: "Not supported" });
802
+ return;
803
+ }
804
+ const { provider } = req.body;
805
+ if (!provider || !["gemini", "openai", "anthropic", "auto"].includes(provider)) {
806
+ res.status(400).json({ success: false, error: "provider must be gemini, openai, anthropic, or auto" });
807
+ return;
808
+ }
809
+ try {
810
+ const result = setAIProviderFn(provider);
811
+ res.json(result);
812
+ }
813
+ catch (err) {
814
+ res.status(500).json({ success: false, error: err.message || "Unknown error" });
815
+ }
816
+ });
797
817
  // GET /api/browse-dirs — List subdirectories for folder browser
798
818
  app.get("/api/browse-dirs", (req, res) => {
799
819
  const dirPath = req.query.path || (process.platform === "win32" ? "C:\\" : "/");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syke1/mcp-server",
3
- "version": "1.4.10",
3
+ "version": "1.4.12",
4
4
  "mcpName": "io.github.khalomsky/syke",
5
5
  "description": "AI code impact analysis MCP server — dependency graphs, cascade detection, and a mandatory build gate for AI coding agents",
6
6
  "main": "dist/index.js",