construct-shader-graph-mcp 0.4.0 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "construct-shader-graph-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Standalone MCP server for Construct Shader Graph",
5
5
  "type": "module",
6
6
  "files": [
@@ -186,8 +186,8 @@ Side-effecting calls:
186
186
  When the model does not know which node type to use:
187
187
 
188
188
  - Prefer `nodeTypes.search(query)` to search by concept.
189
- - Use `nodeTypes.list()` to inspect all available node types.
190
- - Use `nodeTypes.get(typeKey)` to inspect one exact type before creating it.
189
+ - Use `nodeTypes.list()` only for lightweight discovery of available names/categories/tags.
190
+ - Use `nodeTypes.get(typeKey)` to inspect one exact type in full before creating it.
191
191
  - Use `nodes.search(query)` as a convenience alias for node type search.
192
192
  - Use `uniforms.getNodeTypes()` to discover generated uniform-backed node types.
193
193
  - Use `customNodes.list()` to discover reusable custom node definitions already in the project.
package/src/server.mjs CHANGED
@@ -22,6 +22,7 @@ let localServer = null;
22
22
  let bridge = null;
23
23
  let controlServer = null;
24
24
  let isPrimaryInstance = false;
25
+ let promotionInFlight = null;
25
26
 
26
27
  function log(message, ...args) {
27
28
  console.error(`[construct-shader-graph-mcp] ${message}`, ...args);
@@ -706,7 +707,7 @@ function createProxyServer() {
706
707
  return server;
707
708
  }
708
709
 
709
- function callPrimaryTool(tool, input) {
710
+ function callPrimaryToolDirect(tool, input) {
710
711
  return new Promise((resolve, reject) => {
711
712
  const socket = net.createConnection({
712
713
  host: "127.0.0.1",
@@ -751,6 +752,66 @@ function callPrimaryTool(tool, input) {
751
752
  });
752
753
  }
753
754
 
755
+ async function tryPromoteToPrimary() {
756
+ if (isPrimaryInstance) return true;
757
+
758
+ // Deduplicate concurrent promotion attempts
759
+ if (promotionInFlight) return promotionInFlight;
760
+
761
+ promotionInFlight = (async () => {
762
+ try {
763
+ await startPrimaryBackend();
764
+ log("promoted to primary instance");
765
+ return true;
766
+ } catch (err) {
767
+ if (err?.code === "EADDRINUSE") {
768
+ log("promotion failed: another primary appeared");
769
+ } else {
770
+ log("promotion failed:", err.message);
771
+ }
772
+ // Reset partial state
773
+ isPrimaryInstance = false;
774
+ localServer = null;
775
+ bridge = null;
776
+ controlServer = null;
777
+ return false;
778
+ } finally {
779
+ promotionInFlight = null;
780
+ }
781
+ })();
782
+
783
+ return promotionInFlight;
784
+ }
785
+
786
+ function executeToolLocally(toolName, input) {
787
+ const tool = createToolDefinitions().find((t) => t.name === toolName);
788
+ if (!tool) throw new Error(`Unknown tool '${toolName}'`);
789
+ return tool.handler(input || {});
790
+ }
791
+
792
+ async function callPrimaryTool(toolName, input) {
793
+ // If we're already promoted, go local
794
+ if (isPrimaryInstance) {
795
+ return executeToolLocally(toolName, input);
796
+ }
797
+
798
+ try {
799
+ return await callPrimaryToolDirect(toolName, input);
800
+ } catch (error) {
801
+ // If connection refused, the primary is gone — try to take over
802
+ if (error?.code === "ECONNREFUSED") {
803
+ log("primary unreachable, attempting promotion...");
804
+ const promoted = await tryPromoteToPrimary();
805
+ if (promoted) {
806
+ return executeToolLocally(toolName, input);
807
+ }
808
+ // Another primary appeared while we promoted — retry via proxy
809
+ return callPrimaryToolDirect(toolName, input);
810
+ }
811
+ throw error;
812
+ }
813
+ }
814
+
754
815
  async function ensureBackend() {
755
816
  try {
756
817
  await startPrimaryBackend();