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.
- package/README.md +38 -0
- package/package.json +4 -1
- 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
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
35
|
+
const portArg = args.find(arg => arg.startsWith('--port='));
|
|
36
|
+
if (portArg) {
|
|
37
|
+
process.env.PORT = portArg.split('=')[1];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
26
40
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
117
|
+
logger.info('Motion API key configured successfully');
|
|
37
118
|
|
|
38
|
-
app
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
logger.info(
|
|
169
|
+
process.on('SIGTERM', () => {
|
|
170
|
+
logger.info('Received SIGTERM, shutting down gracefully');
|
|
171
|
+
process.exit(0);
|
|
45
172
|
});
|
|
46
173
|
|
|
47
|
-
|
|
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 };
|