motionmcp 1.0.2 → 2.1.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/LICENSE +202 -0
- package/README.md +241 -450
- package/dist/handlers/CommentHandler.d.ts +9 -0
- package/dist/handlers/CommentHandler.d.ts.map +1 -0
- package/dist/handlers/CommentHandler.js +66 -0
- package/dist/handlers/CommentHandler.js.map +1 -0
- package/dist/handlers/CustomFieldHandler.d.ts +14 -0
- package/dist/handlers/CustomFieldHandler.d.ts.map +1 -0
- package/dist/handlers/CustomFieldHandler.js +95 -0
- package/dist/handlers/CustomFieldHandler.js.map +1 -0
- package/dist/handlers/HandlerFactory.d.ts +15 -0
- package/dist/handlers/HandlerFactory.d.ts.map +1 -0
- package/dist/handlers/HandlerFactory.js +58 -0
- package/dist/handlers/HandlerFactory.js.map +1 -0
- package/dist/handlers/ProjectHandler.d.ts +10 -0
- package/dist/handlers/ProjectHandler.d.ts.map +1 -0
- package/dist/handlers/ProjectHandler.js +63 -0
- package/dist/handlers/ProjectHandler.js.map +1 -0
- package/dist/handlers/RecurringTaskHandler.d.ts +10 -0
- package/dist/handlers/RecurringTaskHandler.d.ts.map +1 -0
- package/dist/handlers/RecurringTaskHandler.js +68 -0
- package/dist/handlers/RecurringTaskHandler.js.map +1 -0
- package/dist/handlers/ScheduleHandler.d.ts +8 -0
- package/dist/handlers/ScheduleHandler.d.ts.map +1 -0
- package/dist/handlers/ScheduleHandler.js +43 -0
- package/dist/handlers/ScheduleHandler.js.map +1 -0
- package/dist/handlers/SearchHandler.d.ts +10 -0
- package/dist/handlers/SearchHandler.d.ts.map +1 -0
- package/dist/handlers/SearchHandler.js +116 -0
- package/dist/handlers/SearchHandler.js.map +1 -0
- package/dist/handlers/StatusHandler.d.ts +8 -0
- package/dist/handlers/StatusHandler.d.ts.map +1 -0
- package/dist/handlers/StatusHandler.js +22 -0
- package/dist/handlers/StatusHandler.js.map +1 -0
- package/dist/handlers/TaskHandler.d.ts +22 -0
- package/dist/handlers/TaskHandler.d.ts.map +1 -0
- package/dist/handlers/TaskHandler.js +324 -0
- package/dist/handlers/TaskHandler.js.map +1 -0
- package/dist/handlers/UserHandler.d.ts +9 -0
- package/dist/handlers/UserHandler.d.ts.map +1 -0
- package/dist/handlers/UserHandler.js +36 -0
- package/dist/handlers/UserHandler.js.map +1 -0
- package/dist/handlers/WorkspaceHandler.d.ts +10 -0
- package/dist/handlers/WorkspaceHandler.d.ts.map +1 -0
- package/dist/handlers/WorkspaceHandler.js +49 -0
- package/dist/handlers/WorkspaceHandler.js.map +1 -0
- package/dist/handlers/base/BaseHandler.d.ts +16 -0
- package/dist/handlers/base/BaseHandler.d.ts.map +1 -0
- package/dist/handlers/base/BaseHandler.js +31 -0
- package/dist/handlers/base/BaseHandler.js.map +1 -0
- package/dist/handlers/base/HandlerInterface.d.ts +18 -0
- package/dist/handlers/base/HandlerInterface.d.ts.map +1 -0
- package/dist/handlers/base/HandlerInterface.js +3 -0
- package/dist/handlers/base/HandlerInterface.js.map +1 -0
- package/dist/handlers/index.d.ts +14 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +31 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/mcp-server.d.ts +15 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +145 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/schemas/motion.d.ts +4971 -0
- package/dist/schemas/motion.d.ts.map +1 -0
- package/dist/schemas/motion.js +328 -0
- package/dist/schemas/motion.js.map +1 -0
- package/dist/services/motionApi.d.ts +199 -0
- package/dist/services/motionApi.d.ts.map +1 -0
- package/dist/services/motionApi.js +1950 -0
- package/dist/services/motionApi.js.map +1 -0
- package/dist/tools/ToolConfigurator.d.ts +19 -0
- package/dist/tools/ToolConfigurator.d.ts.map +1 -0
- package/dist/tools/ToolConfigurator.js +89 -0
- package/dist/tools/ToolConfigurator.js.map +1 -0
- package/dist/tools/ToolDefinitions.d.ts +25 -0
- package/dist/tools/ToolDefinitions.d.ts.map +1 -0
- package/dist/tools/ToolDefinitions.js +508 -0
- package/dist/tools/ToolDefinitions.js.map +1 -0
- package/dist/tools/ToolRegistry.d.ts +16 -0
- package/dist/tools/ToolRegistry.d.ts.map +1 -0
- package/dist/tools/ToolRegistry.js +89 -0
- package/dist/tools/ToolRegistry.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +21 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/mcp-tool-args.d.ts +123 -0
- package/dist/types/mcp-tool-args.d.ts.map +1 -0
- package/dist/types/mcp-tool-args.js +7 -0
- package/dist/types/mcp-tool-args.js.map +1 -0
- package/dist/types/mcp.d.ts +32 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +3 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/types/motion.d.ts +304 -0
- package/dist/types/motion.d.ts.map +1 -0
- package/dist/types/motion.js +3 -0
- package/dist/types/motion.js.map +1 -0
- package/dist/utils/cache.d.ts +25 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +135 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/constants.d.ts +88 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +188 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/errorHandling.d.ts +50 -0
- package/dist/utils/errorHandling.d.ts.map +1 -0
- package/dist/utils/errorHandling.js +86 -0
- package/dist/utils/errorHandling.js.map +1 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +38 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +47 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/pagination.d.ts +61 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +168 -0
- package/dist/utils/pagination.js.map +1 -0
- package/dist/utils/paginationNew.d.ts +44 -0
- package/dist/utils/paginationNew.d.ts.map +1 -0
- package/dist/utils/paginationNew.js +149 -0
- package/dist/utils/paginationNew.js.map +1 -0
- package/dist/utils/parameterUtils.d.ts +79 -0
- package/dist/utils/parameterUtils.d.ts.map +1 -0
- package/dist/utils/parameterUtils.js +189 -0
- package/dist/utils/parameterUtils.js.map +1 -0
- package/dist/utils/responseFormatters.d.ts +95 -0
- package/dist/utils/responseFormatters.d.ts.map +1 -0
- package/dist/utils/responseFormatters.js +342 -0
- package/dist/utils/responseFormatters.js.map +1 -0
- package/dist/utils/responseWrapper.d.ts +38 -0
- package/dist/utils/responseWrapper.d.ts.map +1 -0
- package/dist/utils/responseWrapper.js +201 -0
- package/dist/utils/responseWrapper.js.map +1 -0
- package/dist/utils/sanitize.d.ts +51 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +138 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/utils/validator.d.ts +37 -0
- package/dist/utils/validator.d.ts.map +1 -0
- package/dist/utils/validator.js +74 -0
- package/dist/utils/validator.js.map +1 -0
- package/dist/utils/workspaceResolver.d.ts +40 -0
- package/dist/utils/workspaceResolver.d.ts.map +1 -0
- package/dist/utils/workspaceResolver.js +207 -0
- package/dist/utils/workspaceResolver.js.map +1 -0
- package/package.json +41 -17
- package/.claude/settings.local.json +0 -15
- package/.env.example +0 -3
- package/sample.png +0 -0
- package/src/index.js +0 -179
- package/src/mcp-server.js +0 -1137
- package/src/routes/motion.js +0 -152
- package/src/services/motionApi.js +0 -1177
- package/src/worker.js +0 -248
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WorkspaceResolver - Centralized workspace resolution logic
|
|
4
|
+
*
|
|
5
|
+
* This class handles all workspace resolution patterns used throughout
|
|
6
|
+
* the Motion MCP Server, including ID resolution, name lookups, and
|
|
7
|
+
* fallback to default workspace behavior.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.WorkspaceResolver = void 0;
|
|
11
|
+
const constants_1 = require("./constants");
|
|
12
|
+
const errorHandling_1 = require("./errorHandling");
|
|
13
|
+
const logger_1 = require("./logger");
|
|
14
|
+
class WorkspaceResolver {
|
|
15
|
+
constructor(motionApiService) {
|
|
16
|
+
if (!motionApiService) {
|
|
17
|
+
throw new Error('MotionApiService is required for WorkspaceResolver');
|
|
18
|
+
}
|
|
19
|
+
this.motionService = motionApiService;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Main workspace resolution method - handles all workspace resolution patterns
|
|
23
|
+
*/
|
|
24
|
+
async resolveWorkspace(args = {}, options = {}) {
|
|
25
|
+
const { workspaceId, workspaceName } = args;
|
|
26
|
+
const { fallbackToDefault = constants_1.DEFAULTS.WORKSPACE_FALLBACK_TO_DEFAULT, validateAccess = constants_1.DEFAULTS.WORKSPACE_VALIDATE_ACCESS
|
|
27
|
+
// useCache = DEFAULTS.WORKSPACE_USE_CACHE // Will be used in future caching implementation
|
|
28
|
+
} = options;
|
|
29
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Starting workspace resolution', {
|
|
30
|
+
method: 'resolveWorkspace',
|
|
31
|
+
workspaceId,
|
|
32
|
+
workspaceName,
|
|
33
|
+
fallbackToDefault,
|
|
34
|
+
validateAccess
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
let resolvedWorkspace = null;
|
|
38
|
+
// Case 1: Direct workspace ID provided
|
|
39
|
+
if (workspaceId) {
|
|
40
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Resolving by workspace ID', {
|
|
41
|
+
method: 'resolveWorkspace',
|
|
42
|
+
workspaceId
|
|
43
|
+
});
|
|
44
|
+
if (validateAccess) {
|
|
45
|
+
resolvedWorkspace = await this.resolveByWorkspaceId(workspaceId);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// When not validating, just return the ID as-is
|
|
49
|
+
resolvedWorkspace = {
|
|
50
|
+
id: workspaceId,
|
|
51
|
+
name: 'Unknown Workspace',
|
|
52
|
+
teamId: 'unknown',
|
|
53
|
+
type: constants_1.WORKSPACE_TYPES.UNKNOWN,
|
|
54
|
+
labels: [],
|
|
55
|
+
statuses: []
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Case 2: Workspace name provided
|
|
60
|
+
else if (workspaceName) {
|
|
61
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Resolving by workspace name', {
|
|
62
|
+
method: 'resolveWorkspace',
|
|
63
|
+
workspaceName
|
|
64
|
+
});
|
|
65
|
+
resolvedWorkspace = await this.resolveByWorkspaceName(workspaceName);
|
|
66
|
+
}
|
|
67
|
+
// Case 3: Fallback to default workspace
|
|
68
|
+
else if (fallbackToDefault) {
|
|
69
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Falling back to default workspace', {
|
|
70
|
+
method: 'resolveWorkspace'
|
|
71
|
+
});
|
|
72
|
+
resolvedWorkspace = await this.resolveDefaultWorkspace();
|
|
73
|
+
}
|
|
74
|
+
// Case 4: No workspace could be resolved
|
|
75
|
+
if (!resolvedWorkspace) {
|
|
76
|
+
throw new errorHandling_1.WorkspaceError('No workspace specified and no default workspace available', constants_1.ERROR_CODES.NO_DEFAULT_WORKSPACE, { fallbackToDefault });
|
|
77
|
+
}
|
|
78
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Workspace resolved successfully', {
|
|
79
|
+
method: 'resolveWorkspace',
|
|
80
|
+
resolvedId: resolvedWorkspace.id,
|
|
81
|
+
resolvedName: resolvedWorkspace.name
|
|
82
|
+
});
|
|
83
|
+
return resolvedWorkspace;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to resolve workspace', {
|
|
87
|
+
method: 'resolveWorkspace',
|
|
88
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
89
|
+
workspaceId,
|
|
90
|
+
workspaceName
|
|
91
|
+
});
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Resolve workspace by ID - validates the workspace exists
|
|
97
|
+
*/
|
|
98
|
+
async resolveByWorkspaceId(workspaceId) {
|
|
99
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Fetching workspace by ID', {
|
|
100
|
+
method: 'resolveByWorkspaceId',
|
|
101
|
+
workspaceId
|
|
102
|
+
});
|
|
103
|
+
try {
|
|
104
|
+
const workspaces = await this.motionService.getWorkspaces();
|
|
105
|
+
const workspace = workspaces.find((w) => w.id === workspaceId);
|
|
106
|
+
if (!workspace) {
|
|
107
|
+
throw new errorHandling_1.WorkspaceError(`Workspace with ID "${workspaceId}" not found`, constants_1.ERROR_CODES.WORKSPACE_NOT_FOUND, { workspaceId, availableCount: workspaces.length });
|
|
108
|
+
}
|
|
109
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Workspace found by ID', {
|
|
110
|
+
method: 'resolveByWorkspaceId',
|
|
111
|
+
workspaceId,
|
|
112
|
+
workspaceName: workspace.name
|
|
113
|
+
});
|
|
114
|
+
return workspace;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error instanceof errorHandling_1.WorkspaceError) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to fetch workspace by ID', {
|
|
121
|
+
method: 'resolveByWorkspaceId',
|
|
122
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
123
|
+
});
|
|
124
|
+
throw new errorHandling_1.WorkspaceError(`Failed to validate workspace ID "${workspaceId}"`, constants_1.ERROR_CODES.WORKSPACE_ACCESS_DENIED, { workspaceId });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Resolve workspace by name - finds matching workspace
|
|
129
|
+
*/
|
|
130
|
+
async resolveByWorkspaceName(workspaceName) {
|
|
131
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Fetching workspace by name', {
|
|
132
|
+
method: 'resolveByWorkspaceName',
|
|
133
|
+
workspaceName
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
const workspaces = await this.motionService.getWorkspaces();
|
|
137
|
+
// Exact match first
|
|
138
|
+
let workspace = workspaces.find((w) => w.name === workspaceName);
|
|
139
|
+
// Case-insensitive match as fallback
|
|
140
|
+
if (!workspace) {
|
|
141
|
+
const lowerName = workspaceName.toLowerCase();
|
|
142
|
+
workspace = workspaces.find((w) => w.name.toLowerCase() === lowerName);
|
|
143
|
+
}
|
|
144
|
+
if (!workspace) {
|
|
145
|
+
// Provide helpful error with available workspace names
|
|
146
|
+
const availableNames = workspaces.map((w) => w.name).join(', ');
|
|
147
|
+
throw new errorHandling_1.WorkspaceError(`Workspace "${workspaceName}" not found. Available workspaces: ${availableNames}`, constants_1.ERROR_CODES.WORKSPACE_NOT_FOUND, {
|
|
148
|
+
requestedName: workspaceName,
|
|
149
|
+
availableWorkspaces: workspaces.map((w) => ({
|
|
150
|
+
id: w.id,
|
|
151
|
+
name: w.name
|
|
152
|
+
}))
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Workspace found by name', {
|
|
156
|
+
method: 'resolveByWorkspaceName',
|
|
157
|
+
workspaceName,
|
|
158
|
+
workspaceId: workspace.id
|
|
159
|
+
});
|
|
160
|
+
return workspace;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (error instanceof errorHandling_1.WorkspaceError) {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to fetch workspace by name', {
|
|
167
|
+
method: 'resolveByWorkspaceName',
|
|
168
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
169
|
+
});
|
|
170
|
+
throw new errorHandling_1.WorkspaceError(`Failed to resolve workspace name "${workspaceName}"`, constants_1.ERROR_CODES.MOTION_API_ERROR, { workspaceName });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Resolve default workspace - gets the first available workspace
|
|
175
|
+
*/
|
|
176
|
+
async resolveDefaultWorkspace() {
|
|
177
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Fetching default workspace', {
|
|
178
|
+
method: 'resolveDefaultWorkspace'
|
|
179
|
+
});
|
|
180
|
+
try {
|
|
181
|
+
const workspaces = await this.motionService.getWorkspaces();
|
|
182
|
+
if (!workspaces || workspaces.length === 0) {
|
|
183
|
+
throw new errorHandling_1.WorkspaceError('No workspaces available', constants_1.ERROR_CODES.NO_DEFAULT_WORKSPACE);
|
|
184
|
+
}
|
|
185
|
+
// Use the first workspace as default
|
|
186
|
+
const defaultWorkspace = workspaces[0];
|
|
187
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Using default workspace', {
|
|
188
|
+
method: 'resolveDefaultWorkspace',
|
|
189
|
+
workspaceId: defaultWorkspace.id,
|
|
190
|
+
workspaceName: defaultWorkspace.name
|
|
191
|
+
});
|
|
192
|
+
return defaultWorkspace;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (error instanceof errorHandling_1.WorkspaceError) {
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to fetch default workspace', {
|
|
199
|
+
method: 'resolveDefaultWorkspace',
|
|
200
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
201
|
+
});
|
|
202
|
+
throw new errorHandling_1.WorkspaceError('Failed to fetch default workspace', constants_1.ERROR_CODES.MOTION_API_ERROR);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
exports.WorkspaceResolver = WorkspaceResolver;
|
|
207
|
+
//# sourceMappingURL=workspaceResolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceResolver.js","sourceRoot":"","sources":["../../src/utils/workspaceResolver.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,29 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "motionmcp",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "MCP server for Motion API integration - manage projects, tasks, and workspaces from LLMs",
|
|
5
|
+
"main": "dist/mcp-server.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"motionmcp": "./
|
|
7
|
+
"motionmcp": "./dist/mcp-server.js"
|
|
8
8
|
},
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"keywords": ["mcp", "model-context-protocol", "motion", "api", "task-management", "project-management", "llm", "ai"],
|
|
15
|
+
"author": "Devon Hillard devon@digitalsanctuary.com",
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/devondragon/MotionMCP.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/devondragon/MotionMCP",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/devondragon/MotionMCP/issues"
|
|
16
24
|
},
|
|
17
|
-
"keywords": [],
|
|
18
|
-
"author": "",
|
|
19
|
-
"license": "ISC",
|
|
20
25
|
"type": "commonjs",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
21
29
|
"dependencies": {
|
|
22
30
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
31
|
+
"ajv": "^8.17.1",
|
|
23
32
|
"axios": "^1.9.0",
|
|
24
|
-
"cors": "^2.8.5",
|
|
25
33
|
"dotenv": "^16.5.0",
|
|
26
|
-
"
|
|
27
|
-
|
|
34
|
+
"zod": "^3.25.76"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^24.2.1",
|
|
38
|
+
"ts-node": "^10.9.2",
|
|
39
|
+
"typescript": "^5.9.2",
|
|
40
|
+
"vitest": "^3.2.4"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc",
|
|
44
|
+
"mcp": "node dist/mcp-server.js",
|
|
45
|
+
"mcp:dev": "ts-node src/mcp-server.ts",
|
|
46
|
+
"watch": "tsc --watch",
|
|
47
|
+
"type-check": "tsc --noEmit",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"worker:dev": "wrangler dev",
|
|
50
|
+
"worker:deploy": "wrangler deploy",
|
|
51
|
+
"prepublishOnly": "npm run type-check && npm run build"
|
|
28
52
|
}
|
|
29
53
|
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(npm init:*)",
|
|
5
|
-
"Bash(npm install:*)",
|
|
6
|
-
"Bash(mkdir:*)",
|
|
7
|
-
"Bash(git init:*)",
|
|
8
|
-
"Bash(git add:*)",
|
|
9
|
-
"WebFetch(domain:modelcontextprotocol.io)",
|
|
10
|
-
"WebFetch(domain:github.com)",
|
|
11
|
-
"Bash(chmod:*)"
|
|
12
|
-
],
|
|
13
|
-
"deny": []
|
|
14
|
-
}
|
|
15
|
-
}
|
package/.env.example
DELETED
package/sample.png
DELETED
|
Binary file
|
package/src/index.js
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const express = require('express');
|
|
4
|
-
const cors = require('cors');
|
|
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');
|
|
10
|
-
require('dotenv').config();
|
|
11
|
-
|
|
12
|
-
const motionRoutes = require('./routes/motion');
|
|
13
|
-
|
|
14
|
-
const logger = winston.createLogger({
|
|
15
|
-
level: 'info',
|
|
16
|
-
format: winston.format.combine(
|
|
17
|
-
winston.format.timestamp(),
|
|
18
|
-
winston.format.json()
|
|
19
|
-
),
|
|
20
|
-
transports: [
|
|
21
|
-
new winston.transports.Console({
|
|
22
|
-
format: winston.format.simple()
|
|
23
|
-
})
|
|
24
|
-
]
|
|
25
|
-
});
|
|
26
|
-
|
|
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
|
-
}
|
|
34
|
-
|
|
35
|
-
const portArg = args.find(arg => arg.startsWith('--port='));
|
|
36
|
-
if (portArg) {
|
|
37
|
-
process.env.PORT = portArg.split('=')[1];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
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
|
-
}
|
|
58
|
-
|
|
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
|
-
}
|
|
116
|
-
|
|
117
|
-
logger.info('Motion API key configured successfully');
|
|
118
|
-
|
|
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);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
process.on('SIGTERM', () => {
|
|
170
|
-
logger.info('Received SIGTERM, shutting down gracefully');
|
|
171
|
-
process.exit(0);
|
|
172
|
-
});
|
|
173
|
-
|
|
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 };
|