@dynamic-mockups/mcp 1.0.1 → 1.0.3

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.
Files changed (3) hide show
  1. package/README.md +33 -47
  2. package/package.json +7 -3
  3. package/src/index.js +274 -39
package/README.md CHANGED
@@ -13,15 +13,39 @@ Add the following to your MCP client configuration file:
13
13
 
14
14
  ```json
15
15
  {
16
- "mcpServers": {
17
- "dynamic-mockups": {
18
- "command": "npx",
19
- "args": ["-y", "@dynamic-mockups/mcp"],
20
- "env": {
21
- "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
16
+ "mcpServers": {
17
+ "dynamic-mockups": {
18
+ "command": "npx",
19
+ "args": ["-y", "@dynamic-mockups/mcp"],
20
+ "env": {
21
+ "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
22
+ }
22
23
  }
23
- }
24
- }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ### Lovable
29
+
30
+ For Lovable, simply enter:
31
+ - **Server URL**: `https://mcp.dynamicmockups.com`
32
+ - **API Key**: Your Dynamic Mockups API key ([get one here](https://app.dynamicmockups.com/dashboard-api))
33
+
34
+ ### HTTP Transport
35
+
36
+ If you want to connect via HTTP instead of NPX, use:
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "dynamic-mockups": {
42
+ "type": "http",
43
+ "url": "https://mcp.dynamicmockups.com",
44
+ "headers": {
45
+ "x-api-key": "your_api_key_here"
46
+ }
47
+ }
48
+ }
25
49
  }
26
50
  ```
27
51
 
@@ -66,44 +90,6 @@ Ask your AI assistant:
66
90
  | API info | "What are the rate limits and supported file formats for Dynamic Mockups?" |
67
91
  | Print files | "Export print-ready files at 300 DPI for my poster mockup" |
68
92
 
69
- ## Development
70
-
71
- ### Local Installation
72
-
73
- ```bash
74
- git clone https://github.com/dynamic-mockups/mcp.git
75
- cd mcp-server
76
- npm install
77
- ```
78
-
79
- ### Run Locally
80
-
81
- ```bash
82
- DYNAMIC_MOCKUPS_API_KEY=your_key npm start
83
- ```
84
-
85
- ### Development Mode (with auto-reload)
86
-
87
- ```bash
88
- DYNAMIC_MOCKUPS_API_KEY=your_key npm run dev
89
- ```
90
-
91
- ### Use Local Version in MCP Client
92
-
93
- ```json
94
- {
95
- "mcpServers": {
96
- "dynamic-mockups": {
97
- "command": "node",
98
- "args": ["/path/to/mcp-server/src/index.js"],
99
- "env": {
100
- "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
101
- }
102
- }
103
- }
104
- }
105
- ```
106
-
107
93
  ## Error Handling
108
94
 
109
95
  The server returns clear error messages for common issues:
@@ -122,4 +108,4 @@ The server returns clear error messages for common issues:
122
108
 
123
109
  ## License
124
110
 
125
- MIT
111
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-mockups/mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Official Dynamic Mockups MCP Server - Generate product mockups with AI assistants",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -9,7 +9,9 @@
9
9
  },
10
10
  "scripts": {
11
11
  "start": "node src/index.js",
12
- "dev": "node --watch src/index.js"
12
+ "start:http": "node src/index.js --http",
13
+ "dev": "node --watch src/index.js",
14
+ "dev:http": "node --watch src/index.js --http"
13
15
  },
14
16
  "keywords": [
15
17
  "mcp",
@@ -34,7 +36,9 @@
34
36
  },
35
37
  "dependencies": {
36
38
  "@modelcontextprotocol/sdk": "^1.0.0",
37
- "axios": "^1.6.0"
39
+ "axios": "^1.6.0",
40
+ "express": "^4.21.0",
41
+ "cors": "^2.8.5"
38
42
  },
39
43
  "engines": {
40
44
  "node": ">=18.0.0"
package/src/index.js CHANGED
@@ -4,11 +4,19 @@
4
4
  * Dynamic Mockups MCP Server
5
5
  * Official MCP server for the Dynamic Mockups API
6
6
  * https://dynamicmockups.com
7
+ *
8
+ * Supports both stdio and HTTP/SSE transports:
9
+ * - stdio: Default when run directly (for Claude Desktop, Cursor, etc.)
10
+ * - HTTP/SSE: When imported and used with startHttpServer() (for web-based clients)
7
11
  */
8
12
 
9
13
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10
14
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
11
15
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
17
+ import express from "express";
18
+ import cors from "cors";
19
+ import { randomUUID } from "node:crypto";
12
20
  import axios from "axios";
13
21
  import { ResponseFormatter } from "./response-formatter.js";
14
22
 
@@ -80,25 +88,36 @@ const server = new Server(
80
88
  // HTTP Client
81
89
  // =============================================================================
82
90
 
83
- function createApiClient() {
91
+ /**
92
+ * Creates an API client with the provided API key.
93
+ * For stdio transport: uses environment variable
94
+ * For HTTP transport: uses client-provided API key from Authorization header
95
+ *
96
+ * @param {string} apiKey - The API key to use for requests
97
+ */
98
+ function createApiClient(apiKey) {
84
99
  return axios.create({
85
100
  baseURL: API_BASE_URL,
86
101
  headers: {
87
102
  "Accept": "application/json",
88
103
  "Content-Type": "application/json",
89
- "x-api-key": API_KEY || "",
104
+ "x-api-key": apiKey || "",
90
105
  },
91
106
  timeout: 60000, // 60 second timeout for render operations
92
107
  validateStatus: (status) => status < 500, // Only throw on 5xx errors
93
108
  });
94
109
  }
95
110
 
96
- function validateApiKey() {
97
- if (!API_KEY) {
111
+ /**
112
+ * Validates that an API key is present.
113
+ * @param {string} apiKey - The API key to validate
114
+ */
115
+ function validateApiKey(apiKey) {
116
+ if (!apiKey) {
98
117
  return ResponseFormatter.error(
99
118
  "API key not configured",
100
119
  {
101
- solution: "Set the DYNAMIC_MOCKUPS_API_KEY environment variable in your MCP client configuration.",
120
+ solution: "Provide your Dynamic Mockups API key. For HTTP transport, use the Authorization header (Bearer token). For stdio transport, set the DYNAMIC_MOCKUPS_API_KEY environment variable.",
102
121
  get_key_at: "https://app.dynamicmockups.com/dashboard-api",
103
122
  }
104
123
  );
@@ -106,6 +125,34 @@ function validateApiKey() {
106
125
  return null;
107
126
  }
108
127
 
128
+ /**
129
+ * Extracts the API key from various sources.
130
+ * Priority: requestInfo headers > environment variable
131
+ *
132
+ * @param {Object} extra - Extra info passed to handlers (contains requestInfo for HTTP transport)
133
+ */
134
+ function getApiKey(extra) {
135
+ // For HTTP transport: check Authorization header (Bearer token) or x-api-key header
136
+ if (extra?.requestInfo?.headers) {
137
+ const headers = extra.requestInfo.headers;
138
+
139
+ // Check Authorization: Bearer <token>
140
+ const authHeader = headers.authorization || headers.Authorization;
141
+ if (authHeader && authHeader.startsWith("Bearer ")) {
142
+ return authHeader.slice(7);
143
+ }
144
+
145
+ // Check x-api-key header
146
+ const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"];
147
+ if (apiKeyHeader) {
148
+ return apiKeyHeader;
149
+ }
150
+ }
151
+
152
+ // Fallback to environment variable (for stdio transport)
153
+ return API_KEY;
154
+ }
155
+
109
156
  // =============================================================================
110
157
  // Tool Definitions
111
158
  // =============================================================================
@@ -491,20 +538,22 @@ async function handleGetApiInfo(args) {
491
538
  return ResponseFormatter.ok(topicMap[topic] || API_KNOWLEDGE_BASE);
492
539
  }
493
540
 
494
- async function handleGetCatalogs() {
495
- const error = validateApiKey();
541
+ async function handleGetCatalogs(args, extra) {
542
+ const apiKey = getApiKey(extra);
543
+ const error = validateApiKey(apiKey);
496
544
  if (error) return error;
497
545
 
498
546
  try {
499
- const response = await createApiClient().get("/catalogs");
547
+ const response = await createApiClient(apiKey).get("/catalogs");
500
548
  return ResponseFormatter.fromApiResponse(response);
501
549
  } catch (err) {
502
550
  return ResponseFormatter.fromError(err, "Failed to get catalogs");
503
551
  }
504
552
  }
505
553
 
506
- async function handleGetCollections(args = {}) {
507
- const error = validateApiKey();
554
+ async function handleGetCollections(args = {}, extra) {
555
+ const apiKey = getApiKey(extra);
556
+ const error = validateApiKey(apiKey);
508
557
  if (error) return error;
509
558
 
510
559
  try {
@@ -514,30 +563,32 @@ async function handleGetCollections(args = {}) {
514
563
  params.append("include_all_catalogs", args.include_all_catalogs);
515
564
  }
516
565
 
517
- const response = await createApiClient().get(`/collections?${params}`);
566
+ const response = await createApiClient(apiKey).get(`/collections?${params}`);
518
567
  return ResponseFormatter.fromApiResponse(response);
519
568
  } catch (err) {
520
569
  return ResponseFormatter.fromError(err, "Failed to get collections");
521
570
  }
522
571
  }
523
572
 
524
- async function handleCreateCollection(args) {
525
- const error = validateApiKey();
573
+ async function handleCreateCollection(args, extra) {
574
+ const apiKey = getApiKey(extra);
575
+ const error = validateApiKey(apiKey);
526
576
  if (error) return error;
527
577
 
528
578
  try {
529
579
  const payload = { name: args.name };
530
580
  if (args.catalog_uuid) payload.catalog_uuid = args.catalog_uuid;
531
581
 
532
- const response = await createApiClient().post("/collections", payload);
582
+ const response = await createApiClient(apiKey).post("/collections", payload);
533
583
  return ResponseFormatter.fromApiResponse(response, `Collection "${args.name}" created`);
534
584
  } catch (err) {
535
585
  return ResponseFormatter.fromError(err, "Failed to create collection");
536
586
  }
537
587
  }
538
588
 
539
- async function handleGetMockups(args = {}) {
540
- const error = validateApiKey();
589
+ async function handleGetMockups(args = {}, extra) {
590
+ const apiKey = getApiKey(extra);
591
+ const error = validateApiKey(apiKey);
541
592
  if (error) return error;
542
593
 
543
594
  try {
@@ -549,27 +600,29 @@ async function handleGetMockups(args = {}) {
549
600
  }
550
601
  if (args.name) params.append("name", args.name);
551
602
 
552
- const response = await createApiClient().get(`/mockups?${params}`);
603
+ const response = await createApiClient(apiKey).get(`/mockups?${params}`);
553
604
  return ResponseFormatter.fromApiResponse(response);
554
605
  } catch (err) {
555
606
  return ResponseFormatter.fromError(err, "Failed to get mockups");
556
607
  }
557
608
  }
558
609
 
559
- async function handleGetMockupByUuid(args) {
560
- const error = validateApiKey();
610
+ async function handleGetMockupByUuid(args, extra) {
611
+ const apiKey = getApiKey(extra);
612
+ const error = validateApiKey(apiKey);
561
613
  if (error) return error;
562
614
 
563
615
  try {
564
- const response = await createApiClient().get(`/mockup/${args.uuid}`);
616
+ const response = await createApiClient(apiKey).get(`/mockup/${args.uuid}`);
565
617
  return ResponseFormatter.fromApiResponse(response);
566
618
  } catch (err) {
567
619
  return ResponseFormatter.fromError(err, "Failed to get mockup");
568
620
  }
569
621
  }
570
622
 
571
- async function handleCreateRender(args) {
572
- const error = validateApiKey();
623
+ async function handleCreateRender(args, extra) {
624
+ const apiKey = getApiKey(extra);
625
+ const error = validateApiKey(apiKey);
573
626
  if (error) return error;
574
627
 
575
628
  try {
@@ -581,22 +634,23 @@ async function handleCreateRender(args) {
581
634
  if (args.export_options) payload.export_options = args.export_options;
582
635
  if (args.text_layers) payload.text_layers = args.text_layers;
583
636
 
584
- const response = await createApiClient().post("/renders", payload);
637
+ const response = await createApiClient(apiKey).post("/renders", payload);
585
638
  return ResponseFormatter.fromApiResponse(response, "Render created (1 credit used)");
586
639
  } catch (err) {
587
640
  return ResponseFormatter.fromError(err, "Failed to create render");
588
641
  }
589
642
  }
590
643
 
591
- async function handleCreateBatchRender(args) {
592
- const error = validateApiKey();
644
+ async function handleCreateBatchRender(args, extra) {
645
+ const apiKey = getApiKey(extra);
646
+ const error = validateApiKey(apiKey);
593
647
  if (error) return error;
594
648
 
595
649
  try {
596
650
  const payload = { renders: args.renders };
597
651
  if (args.export_options) payload.export_options = args.export_options;
598
652
 
599
- const response = await createApiClient().post("/renders/batch", payload);
653
+ const response = await createApiClient(apiKey).post("/renders/batch", payload);
600
654
  const count = args.renders?.length || 0;
601
655
  return ResponseFormatter.fromApiResponse(response, `Batch render complete (${count} credits used)`);
602
656
  } catch (err) {
@@ -604,8 +658,9 @@ async function handleCreateBatchRender(args) {
604
658
  }
605
659
  }
606
660
 
607
- async function handleExportPrintFiles(args) {
608
- const error = validateApiKey();
661
+ async function handleExportPrintFiles(args, extra) {
662
+ const apiKey = getApiKey(extra);
663
+ const error = validateApiKey(apiKey);
609
664
  if (error) return error;
610
665
 
611
666
  try {
@@ -617,15 +672,16 @@ async function handleExportPrintFiles(args) {
617
672
  if (args.export_options) payload.export_options = args.export_options;
618
673
  if (args.text_layers) payload.text_layers = args.text_layers;
619
674
 
620
- const response = await createApiClient().post("/renders/print-files", payload);
675
+ const response = await createApiClient(apiKey).post("/renders/print-files", payload);
621
676
  return ResponseFormatter.fromApiResponse(response, "Print files exported");
622
677
  } catch (err) {
623
678
  return ResponseFormatter.fromError(err, "Failed to export print files");
624
679
  }
625
680
  }
626
681
 
627
- async function handleUploadPsd(args) {
628
- const error = validateApiKey();
682
+ async function handleUploadPsd(args, extra) {
683
+ const apiKey = getApiKey(extra);
684
+ const error = validateApiKey(apiKey);
629
685
  if (error) return error;
630
686
 
631
687
  try {
@@ -634,15 +690,16 @@ async function handleUploadPsd(args) {
634
690
  if (args.psd_category_id) payload.psd_category_id = args.psd_category_id;
635
691
  if (args.mockup_template) payload.mockup_template = args.mockup_template;
636
692
 
637
- const response = await createApiClient().post("/psd/upload", payload);
693
+ const response = await createApiClient(apiKey).post("/psd/upload", payload);
638
694
  return ResponseFormatter.fromApiResponse(response, "PSD uploaded successfully");
639
695
  } catch (err) {
640
696
  return ResponseFormatter.fromError(err, "Failed to upload PSD");
641
697
  }
642
698
  }
643
699
 
644
- async function handleDeletePsd(args) {
645
- const error = validateApiKey();
700
+ async function handleDeletePsd(args, extra) {
701
+ const apiKey = getApiKey(extra);
702
+ const error = validateApiKey(apiKey);
646
703
  if (error) return error;
647
704
 
648
705
  try {
@@ -651,7 +708,7 @@ async function handleDeletePsd(args) {
651
708
  payload.delete_related_mockups = args.delete_related_mockups;
652
709
  }
653
710
 
654
- const response = await createApiClient().post("/psd/delete", payload);
711
+ const response = await createApiClient(apiKey).post("/psd/delete", payload);
655
712
  return ResponseFormatter.fromApiResponse(response, "PSD deleted successfully");
656
713
  } catch (err) {
657
714
  return ResponseFormatter.fromError(err, "Failed to delete PSD");
@@ -682,7 +739,7 @@ const toolHandlers = {
682
739
 
683
740
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
684
741
 
685
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
742
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
686
743
  const { name, arguments: args } = request.params;
687
744
 
688
745
  const handler = toolHandlers[name];
@@ -691,7 +748,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
691
748
  }
692
749
 
693
750
  try {
694
- return await handler(args || {});
751
+ // Pass extra context (contains requestInfo with headers for HTTP transport)
752
+ return await handler(args || {}, extra);
695
753
  } catch (err) {
696
754
  return ResponseFormatter.fromError(err, `Error executing ${name}`);
697
755
  }
@@ -701,12 +759,189 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
701
759
  // Server Startup
702
760
  // =============================================================================
703
761
 
704
- async function main() {
762
+ /**
763
+ * Start the MCP server with stdio transport (default)
764
+ * Used by: Claude Desktop, Claude Code, Cursor, Windsurf
765
+ */
766
+ async function startStdioServer() {
705
767
  const transport = new StdioServerTransport();
706
768
  await server.connect(transport);
707
- console.error(`Dynamic Mockups MCP Server v${SERVER_VERSION} running`);
769
+ console.error(`Dynamic Mockups MCP Server v${SERVER_VERSION} running (stdio)`);
770
+ }
771
+
772
+ /**
773
+ * Start the MCP server with Streamable HTTP transport
774
+ * Used by: Web-based clients like Lovable that require a URL endpoint
775
+ *
776
+ * Uses the modern StreamableHTTPServerTransport which supports both
777
+ * SSE streaming and direct HTTP responses per the MCP specification.
778
+ *
779
+ * @param {Object} options - Server options
780
+ * @param {number} options.port - Port to listen on (default: 3000)
781
+ * @param {string} options.host - Host to bind to (default: '0.0.0.0')
782
+ * @param {string|string[]} options.corsOrigin - CORS origin(s) (default: '*')
783
+ * @returns {Promise<{app: Express, httpServer: Server}>}
784
+ */
785
+ async function startHttpServer(options = {}) {
786
+ const {
787
+ port = process.env.PORT || 3000,
788
+ host = process.env.HOST || "0.0.0.0",
789
+ corsOrigin = process.env.CORS_ORIGIN || "*",
790
+ } = options;
791
+
792
+ const app = express();
793
+
794
+ // CORS configuration - must allow MCP-specific headers and auth headers
795
+ app.use(cors({
796
+ origin: corsOrigin,
797
+ methods: ["GET", "POST", "DELETE", "OPTIONS"],
798
+ allowedHeaders: [
799
+ "Content-Type",
800
+ "Accept",
801
+ "Authorization",
802
+ "x-api-key",
803
+ "Mcp-Session-Id",
804
+ "Last-Event-Id",
805
+ "Mcp-Protocol-Version",
806
+ ],
807
+ exposedHeaders: ["Mcp-Session-Id"],
808
+ credentials: true,
809
+ }));
810
+
811
+ // Note: We don't use express.json() globally because StreamableHTTPServerTransport
812
+ // needs to read the raw body. We parse JSON only for non-MCP endpoints.
813
+
814
+ // Store active transports by session ID for multi-session support
815
+ const transports = new Map();
816
+
817
+ // Health check endpoint
818
+ app.get("/health", (req, res) => {
819
+ res.json({
820
+ status: "ok",
821
+ server: SERVER_NAME,
822
+ version: SERVER_VERSION,
823
+ transport: "streamable-http",
824
+ activeSessions: transports.size,
825
+ });
826
+ });
827
+
828
+ // API info endpoint (convenience endpoint, not MCP)
829
+ app.get("/api/info", (req, res) => {
830
+ res.json({
831
+ server: SERVER_NAME,
832
+ version: SERVER_VERSION,
833
+ api_key_configured: !!API_KEY,
834
+ tools: tools.map((t) => ({ name: t.name, description: t.description })),
835
+ endpoints: {
836
+ mcp: "/mcp",
837
+ health: "/health",
838
+ },
839
+ });
840
+ });
841
+
842
+ // MCP endpoint - handles all MCP communication (GET for SSE, POST for messages, DELETE for session termination)
843
+ // Available at both "/" and "/mcp" for flexibility
844
+ app.all(["/", "/mcp"], async (req, res) => {
845
+ // Check for existing session
846
+ const sessionId = req.headers["mcp-session-id"];
847
+
848
+ if (sessionId && transports.has(sessionId)) {
849
+ // Reuse existing transport for this session
850
+ const { transport } = transports.get(sessionId);
851
+ await transport.handleRequest(req, res);
852
+ return;
853
+ }
854
+
855
+ // For new connections (no session ID or unknown session), create new transport
856
+ if (req.method === "POST" || req.method === "GET") {
857
+ // Create a new MCP server instance for this connection
858
+ const connectionServer = new Server(
859
+ { name: SERVER_NAME, version: SERVER_VERSION },
860
+ { capabilities: { tools: {} } }
861
+ );
862
+
863
+ // Register the same handlers
864
+ connectionServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
865
+ connectionServer.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
866
+ const { name, arguments: args } = request.params;
867
+ const handler = toolHandlers[name];
868
+ if (!handler) {
869
+ return ResponseFormatter.error(`Unknown tool: ${name}`);
870
+ }
871
+ try {
872
+ // Pass extra context (contains requestInfo with headers for API key extraction)
873
+ return await handler(args || {}, extra);
874
+ } catch (err) {
875
+ return ResponseFormatter.fromError(err, `Error executing ${name}`);
876
+ }
877
+ });
878
+
879
+ // Create Streamable HTTP transport with session support
880
+ const transport = new StreamableHTTPServerTransport({
881
+ sessionIdGenerator: () => randomUUID(),
882
+ onsessioninitialized: (newSessionId) => {
883
+ console.error(`Session initialized: ${newSessionId}`);
884
+ transports.set(newSessionId, { transport, server: connectionServer });
885
+ },
886
+ onsessionclosed: (closedSessionId) => {
887
+ console.error(`Session closed: ${closedSessionId}`);
888
+ transports.delete(closedSessionId);
889
+ },
890
+ });
891
+
892
+ // Connect server to transport
893
+ await connectionServer.connect(transport);
894
+
895
+ // Handle the request
896
+ await transport.handleRequest(req, res);
897
+ return;
898
+ }
899
+
900
+ // Unknown session for DELETE or other methods
901
+ res.status(400).json({
902
+ jsonrpc: "2.0",
903
+ error: {
904
+ code: -32000,
905
+ message: "Bad Request: No valid session found",
906
+ },
907
+ id: null,
908
+ });
909
+ });
910
+
911
+ // Legacy SSE endpoint for backwards compatibility
912
+ app.get("/sse", (req, res) => {
913
+ res.redirect(307, "/");
914
+ });
915
+
916
+ const httpServer = app.listen(port, host, () => {
917
+ console.error(`Dynamic Mockups MCP Server v${SERVER_VERSION} running`);
918
+ console.error(`Streamable HTTP transport available at http://${host}:${port}`);
919
+ console.error(` - MCP endpoint: http://${host}:${port}/mcp`);
920
+ console.error(` - Health check: http://${host}:${port}/health`);
921
+ console.error(` - API info: http://${host}:${port}/api/info`);
922
+ });
923
+
924
+ return { app, httpServer };
925
+ }
926
+
927
+ /**
928
+ * Main entry point - determines transport based on command line args or environment
929
+ */
930
+ async function main() {
931
+ const args = process.argv.slice(2);
932
+ const useHttp = args.includes("--http") || process.env.MCP_TRANSPORT === "http";
933
+
934
+ if (useHttp) {
935
+ await startHttpServer();
936
+ } else {
937
+ await startStdioServer();
938
+ }
708
939
  }
709
940
 
941
+ // Export for programmatic use
942
+ export { startHttpServer, startStdioServer, server, tools, toolHandlers };
943
+
944
+ // Run if executed directly
710
945
  main().catch((err) => {
711
946
  console.error("Fatal error:", err);
712
947
  process.exit(1);