motionmcp 1.0.0 → 1.0.2

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 +38 -0
  2. package/package.json +4 -1
  3. package/src/index.js +150 -18
package/README.md CHANGED
@@ -30,6 +30,44 @@ A Model Context Protocol (MCP) server that provides LLMs with direct access to t
30
30
  - Node.js 18 or higher
31
31
  - Motion API key (get from https://app.usemotion.com/settings/api)
32
32
 
33
+ ## Providing Your Motion API Key
34
+
35
+ The Motion MCP Server supports multiple ways to provide your API key:
36
+
37
+ ### Environment Variable
38
+ ```bash
39
+ MOTION_API_KEY=your-key npx motionmcp
40
+ ```
41
+
42
+ ### Command Line Arguments
43
+ ```bash
44
+ # With API key only
45
+ npx motionmcp --api-key=your-key
46
+
47
+ # With API key and custom port
48
+ npx motionmcp --api-key=your-key --port=4000
49
+ ```
50
+
51
+ ### Config File
52
+ Create a config file in your home directory:
53
+ ```bash
54
+ echo '{"apiKey": "your-key", "port": 4000}' > ~/.motionmcp.json
55
+ npx motionmcp
56
+ ```
57
+
58
+ ### Interactive Prompt
59
+ If no API key is provided through the above methods, the server will prompt you:
60
+ ```bash
61
+ npx motionmcp
62
+ # Will prompt: "Please enter your Motion API key: "
63
+ ```
64
+
65
+ The server checks for the API key in this order:
66
+ 1. Environment variable (`MOTION_API_KEY`)
67
+ 2. Command line argument (`--api-key=`)
68
+ 3. Config file (`~/.motionmcp.json`)
69
+ 4. Interactive prompt
70
+
33
71
  ## Installation
34
72
 
35
73
  1. Clone the repository
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "motionmcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "",
5
5
  "main": "index.js",
6
+ "bin": {
7
+ "motionmcp": "./src/index.js"
8
+ },
6
9
  "scripts": {
7
10
  "start": "node src/index.js",
8
11
  "dev": "node src/index.js",
package/src/index.js CHANGED
@@ -1,6 +1,12 @@
1
+ #!/usr/bin/env node
2
+
1
3
  const express = require('express');
2
4
  const cors = require('cors');
3
5
  const winston = require('winston');
6
+ const readline = require('readline');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const os = require('os');
4
10
  require('dotenv').config();
5
11
 
6
12
  const motionRoutes = require('./routes/motion');
@@ -18,30 +24,156 @@ const logger = winston.createLogger({
18
24
  ]
19
25
  });
20
26
 
21
- const app = express();
22
- const PORT = process.env.PORT || 3000;
27
+ // Parse command line arguments
28
+ function parseCommandLineArgs() {
29
+ const args = process.argv.slice(2);
30
+ const apiKeyArg = args.find(arg => arg.startsWith('--api-key='));
31
+ if (apiKeyArg) {
32
+ process.env.MOTION_API_KEY = apiKeyArg.split('=')[1];
33
+ }
23
34
 
24
- app.use(cors());
25
- app.use(express.json());
35
+ const portArg = args.find(arg => arg.startsWith('--port='));
36
+ if (portArg) {
37
+ process.env.PORT = portArg.split('=')[1];
38
+ }
39
+ }
26
40
 
27
- app.use((req, res, next) => {
28
- logger.info(`${req.method} ${req.path}`, { ip: req.ip });
29
- next();
30
- });
41
+ // Check for config file in user's home directory
42
+ function loadConfigFile() {
43
+ const configPath = path.join(os.homedir(), '.motionmcp.json');
44
+ if (fs.existsSync(configPath)) {
45
+ try {
46
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
47
+ if (config.apiKey && !process.env.MOTION_API_KEY) {
48
+ process.env.MOTION_API_KEY = config.apiKey;
49
+ }
50
+ if (config.port && !process.env.PORT) {
51
+ process.env.PORT = config.port;
52
+ }
53
+ } catch (err) {
54
+ logger.warn('Failed to parse config file:', err.message);
55
+ }
56
+ }
57
+ }
31
58
 
32
- app.get('/health', (req, res) => {
33
- res.json({ status: 'ok', timestamp: new Date().toISOString() });
34
- });
59
+ // Interactive prompt for API key
60
+ async function promptForApiKey() {
61
+ const rl = readline.createInterface({
62
+ input: process.stdin,
63
+ output: process.stdout
64
+ });
65
+
66
+ return new Promise((resolve) => {
67
+ rl.question('Please enter your Motion API key: ', (answer) => {
68
+ rl.close();
69
+ const apiKey = answer.trim();
70
+ if (apiKey) {
71
+ process.env.MOTION_API_KEY = apiKey;
72
+ }
73
+ resolve(apiKey);
74
+ });
75
+ });
76
+ }
77
+
78
+ // Get API key from various sources
79
+ async function getApiKey() {
80
+ // Check if already set in environment
81
+ if (process.env.MOTION_API_KEY) {
82
+ return process.env.MOTION_API_KEY;
83
+ }
84
+
85
+ // Parse command line arguments
86
+ parseCommandLineArgs();
87
+ if (process.env.MOTION_API_KEY) {
88
+ return process.env.MOTION_API_KEY;
89
+ }
90
+
91
+ // Load from config file
92
+ loadConfigFile();
93
+ if (process.env.MOTION_API_KEY) {
94
+ return process.env.MOTION_API_KEY;
95
+ }
96
+
97
+ // Prompt user interactively
98
+ logger.info('No API key found in environment variables, command line args, or config file.');
99
+ const apiKey = await promptForApiKey();
100
+ return apiKey;
101
+ }
102
+
103
+ // Initialize and start the server
104
+ async function startServer() {
105
+ try {
106
+ const apiKey = await getApiKey();
107
+
108
+ if (!apiKey) {
109
+ logger.error('API key is required to run Motion MCP Server');
110
+ logger.info('You can provide it via:');
111
+ logger.info(' Environment variable: MOTION_API_KEY=your-key npx motionmcp');
112
+ logger.info(' Command line arg: npx motionmcp --api-key=your-key');
113
+ logger.info(' Config file: echo \'{"apiKey": "your-key"}\' > ~/.motionmcp.json');
114
+ process.exit(1);
115
+ }
35
116
 
36
- app.use('/api/motion', motionRoutes);
117
+ logger.info('Motion API key configured successfully');
37
118
 
38
- app.use((err, req, res, next) => {
39
- logger.error('Unhandled error:', err);
40
- res.status(500).json({ error: 'Internal server error' });
119
+ const app = express();
120
+ const PORT = process.env.PORT || 3000;
121
+
122
+ app.use(cors());
123
+ app.use(express.json());
124
+
125
+ // Add API key to request context
126
+ app.use((req, res, next) => {
127
+ req.motionApiKey = process.env.MOTION_API_KEY;
128
+ next();
129
+ });
130
+
131
+ app.use((req, res, next) => {
132
+ logger.info(`${req.method} ${req.path}`, { ip: req.ip });
133
+ next();
134
+ });
135
+
136
+ app.get('/health', (req, res) => {
137
+ res.json({
138
+ status: 'ok',
139
+ timestamp: new Date().toISOString(),
140
+ hasApiKey: !!process.env.MOTION_API_KEY
141
+ });
142
+ });
143
+
144
+ app.use('/api/motion', motionRoutes);
145
+
146
+ app.use((err, req, res, next) => {
147
+ logger.error('Unhandled error:', err);
148
+ res.status(500).json({ error: 'Internal server error' });
149
+ });
150
+
151
+ app.listen(PORT, () => {
152
+ logger.info(`Motion MCP Server running on port ${PORT}`);
153
+ logger.info(`Health check available at http://localhost:${PORT}/health`);
154
+ });
155
+
156
+ return app;
157
+ } catch (error) {
158
+ logger.error('Failed to start server:', error);
159
+ process.exit(1);
160
+ }
161
+ }
162
+
163
+ // Handle graceful shutdown
164
+ process.on('SIGINT', () => {
165
+ logger.info('Received SIGINT, shutting down gracefully');
166
+ process.exit(0);
41
167
  });
42
168
 
43
- app.listen(PORT, () => {
44
- logger.info(`Motion MCP Server running on port ${PORT}`);
169
+ process.on('SIGTERM', () => {
170
+ logger.info('Received SIGTERM, shutting down gracefully');
171
+ process.exit(0);
45
172
  });
46
173
 
47
- module.exports = app;
174
+ // Start the server if this file is run directly
175
+ if (require.main === module) {
176
+ startServer().catch(console.error);
177
+ }
178
+
179
+ module.exports = { startServer };