moscow-mcp-server 1.0.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/README.md +64 -0
- package/dist/index.js +268 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Moscow MCP Server (Local Adapter)
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that provides tools to interact with the Moscow task management system via Supabase Edge Functions.
|
|
4
|
+
|
|
5
|
+
This server is designed to run locally (as a "Local Adapter") and communicate with your remote Supabase instance via standard HTTP requests.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- Node.js (v18 or higher)
|
|
10
|
+
- A Supabase project with the Moscow task management Edge Functions deployed.
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
The server requires the following environment variables. You can set them in a `.env` file in the root directory or pass them via your MCP client configuration.
|
|
15
|
+
|
|
16
|
+
- `SUPABASE_URL`: The URL of your Supabase project.
|
|
17
|
+
- `SUPABASE_ANON_KEY`: The anonymous API key for your Supabase project.
|
|
18
|
+
- `MCP_TOKEN` (Optional): A custom token for authorization if your Edge Functions require it.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Using with Claude Desktop or Antigravity
|
|
23
|
+
|
|
24
|
+
Add the following to your MCP configuration file (e.g., `claude_desktop_config.json` or `mcp_config.json`):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"moscow": {
|
|
30
|
+
"command": "node",
|
|
31
|
+
"args": [
|
|
32
|
+
"/Users/guiller/WebstormProjects/moscow-task/mcp-server/dist/index.js",
|
|
33
|
+
"--stdio"
|
|
34
|
+
],
|
|
35
|
+
"env": {
|
|
36
|
+
"SUPABASE_URL": "https://your-project.supabase.co",
|
|
37
|
+
"SUPABASE_ANON_KEY": "your-anon-key",
|
|
38
|
+
"MCP_TOKEN": "mcp_123.....",
|
|
39
|
+
"TOPIC": "mcp"
|
|
40
|
+
},
|
|
41
|
+
"disabled": false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Running Locally (Development)
|
|
48
|
+
|
|
49
|
+
1. **Install dependencies**:
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
2. **Build**:
|
|
55
|
+
```bash
|
|
56
|
+
npm run build
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Tools Provided
|
|
60
|
+
|
|
61
|
+
- `list_tasks`: List tasks from the Moscow project. Can be filtered by `topic`.
|
|
62
|
+
- `create_task`: Create a new task.
|
|
63
|
+
- `update_task`: Update an existing task.
|
|
64
|
+
- `delete_task`: Delete a task.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
// Load .env from the project root relative to this file's location
|
|
11
|
+
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
|
12
|
+
const SUPABASE_URL = process.env.SUPABASE_URL;
|
|
13
|
+
const SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY;
|
|
14
|
+
const DEFAULT_TOPIC = process.env.TOPIC || 'default';
|
|
15
|
+
if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
|
|
16
|
+
console.error('Missing SUPABASE_URL or SUPABASE_ANON_KEY environment variables');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const server = new Server({
|
|
20
|
+
name: 'moscow-mcp-server',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
}, {
|
|
23
|
+
capabilities: {
|
|
24
|
+
tools: {},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const getEdgeFunctionUrl = () => `${SUPABASE_URL}/functions/v1/tasks`;
|
|
28
|
+
// Tool Handlers
|
|
29
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
30
|
+
tools: [
|
|
31
|
+
{
|
|
32
|
+
name: 'list_tasks',
|
|
33
|
+
description: 'List tasks for the current user and optionally filter by topic.',
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
topic: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Optional topic to filter tasks by.',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'create_task',
|
|
46
|
+
description: 'Create a new task.',
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
title: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'The title of the task.',
|
|
53
|
+
},
|
|
54
|
+
description: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'A detailed description of the task.',
|
|
57
|
+
},
|
|
58
|
+
topic: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'The topic/category of the task.',
|
|
61
|
+
},
|
|
62
|
+
priority: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
enum: ['MUST', 'SHOULD', 'COULD', 'WONT'],
|
|
65
|
+
description: 'The priority of the task.',
|
|
66
|
+
},
|
|
67
|
+
status: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
enum: ['TODO', 'IN_PROGRESS', 'DONE'],
|
|
70
|
+
description: 'The status of the task.',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ['title'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'update_task',
|
|
78
|
+
description: 'Update an existing task.',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
id: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'The ID of the task to update.',
|
|
85
|
+
},
|
|
86
|
+
title: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: 'The new title of the task.',
|
|
89
|
+
},
|
|
90
|
+
description: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
description: 'The new description of the task.',
|
|
93
|
+
},
|
|
94
|
+
topic: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'The new topic/category of the task.',
|
|
97
|
+
},
|
|
98
|
+
priority: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
enum: ['MUST', 'SHOULD', 'COULD', 'WONT'],
|
|
101
|
+
description: 'The new priority.',
|
|
102
|
+
},
|
|
103
|
+
status: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
enum: ['TODO', 'IN_PROGRESS', 'DONE'],
|
|
106
|
+
description: 'The new status.',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
required: ['id'],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'delete_task',
|
|
114
|
+
description: 'Delete a task.',
|
|
115
|
+
inputSchema: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
properties: {
|
|
118
|
+
id: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
description: 'The ID of the task to delete.',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
required: ['id'],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
}));
|
|
128
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
129
|
+
// Extract dynamic auth from the transport metadata if available
|
|
130
|
+
const env = extra?.env || {};
|
|
131
|
+
const mcpToken = env.MCP_TOKEN || process.env.MCP_TOKEN;
|
|
132
|
+
const topicScope = request.params.arguments?.topic || env.TOPIC || DEFAULT_TOPIC;
|
|
133
|
+
const authValue = mcpToken || SUPABASE_ANON_KEY;
|
|
134
|
+
switch (request.params.name) {
|
|
135
|
+
case 'list_tasks': {
|
|
136
|
+
try {
|
|
137
|
+
const response = await axios.get(getEdgeFunctionUrl(), {
|
|
138
|
+
params: { topic: topicScope },
|
|
139
|
+
headers: {
|
|
140
|
+
Authorization: `Bearer ${authValue}`,
|
|
141
|
+
apikey: SUPABASE_ANON_KEY,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
return {
|
|
145
|
+
content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
const errorMessage = axios.isAxiosError(error)
|
|
150
|
+
? error.response?.data?.error || error.message
|
|
151
|
+
: error instanceof Error
|
|
152
|
+
? error.message
|
|
153
|
+
: String(error);
|
|
154
|
+
return {
|
|
155
|
+
content: [{ type: 'text', text: `Error listing tasks: ${errorMessage}` }],
|
|
156
|
+
isError: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
case 'create_task': {
|
|
161
|
+
const { title, description, topic, priority, status } = request.params.arguments || {};
|
|
162
|
+
try {
|
|
163
|
+
const response = await axios.post(getEdgeFunctionUrl(), {
|
|
164
|
+
title,
|
|
165
|
+
description,
|
|
166
|
+
topic: topic || topicScope,
|
|
167
|
+
priority,
|
|
168
|
+
status,
|
|
169
|
+
}, {
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `Bearer ${authValue}`,
|
|
172
|
+
apikey: SUPABASE_ANON_KEY,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
content: [
|
|
177
|
+
{
|
|
178
|
+
type: 'text',
|
|
179
|
+
text: `Task created successfully: ${JSON.stringify(response.data, null, 2)}`,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
const errorMessage = axios.isAxiosError(error)
|
|
186
|
+
? error.response?.data?.error || error.message
|
|
187
|
+
: error instanceof Error
|
|
188
|
+
? error.message
|
|
189
|
+
: String(error);
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: 'text', text: `Error creating task: ${errorMessage}` }],
|
|
192
|
+
isError: true,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
case 'update_task': {
|
|
197
|
+
const { id, ...updates } = request.params.arguments || {};
|
|
198
|
+
try {
|
|
199
|
+
const response = await axios.patch(getEdgeFunctionUrl(), {
|
|
200
|
+
id,
|
|
201
|
+
...updates,
|
|
202
|
+
}, {
|
|
203
|
+
headers: {
|
|
204
|
+
Authorization: `Bearer ${authValue}`,
|
|
205
|
+
apikey: SUPABASE_ANON_KEY,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: 'text',
|
|
212
|
+
text: `Task updated successfully: ${JSON.stringify(response.data, null, 2)}`,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
const errorMessage = axios.isAxiosError(error)
|
|
219
|
+
? error.response?.data?.error || error.message
|
|
220
|
+
: error instanceof Error
|
|
221
|
+
? error.message
|
|
222
|
+
: String(error);
|
|
223
|
+
return {
|
|
224
|
+
content: [{ type: 'text', text: `Error updating task: ${errorMessage}` }],
|
|
225
|
+
isError: true,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
case 'delete_task': {
|
|
230
|
+
const { id } = request.params.arguments || {};
|
|
231
|
+
try {
|
|
232
|
+
await axios.delete(getEdgeFunctionUrl(), {
|
|
233
|
+
params: { id },
|
|
234
|
+
headers: {
|
|
235
|
+
Authorization: `Bearer ${authValue}`,
|
|
236
|
+
apikey: SUPABASE_ANON_KEY,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
content: [{ type: 'text', text: `Task ${id} deleted successfully.` }],
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
const errorMessage = axios.isAxiosError(error)
|
|
245
|
+
? error.response?.data?.error || error.message
|
|
246
|
+
: error instanceof Error
|
|
247
|
+
? error.message
|
|
248
|
+
: String(error);
|
|
249
|
+
return {
|
|
250
|
+
content: [{ type: 'text', text: `Error deleting task: ${errorMessage}` }],
|
|
251
|
+
isError: true,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
default:
|
|
256
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
// STARTUP LOGIC: Always use Stdio
|
|
260
|
+
async function main() {
|
|
261
|
+
const transport = new StdioServerTransport();
|
|
262
|
+
await server.connect(transport);
|
|
263
|
+
console.error('Moscow MCP Server running on Stdio');
|
|
264
|
+
}
|
|
265
|
+
main().catch((error) => {
|
|
266
|
+
console.error('Server error:', error);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "moscow-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "MCP server for Moscow task management",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": "dist/index.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"dev": "ts-node src/index.ts"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^0.6.0",
|
|
18
|
+
"axios": "^1.6.0",
|
|
19
|
+
"dotenv": "^16.3.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.8.0",
|
|
23
|
+
"ts-node": "^10.9.1",
|
|
24
|
+
"typescript": "^5.2.2"
|
|
25
|
+
}
|
|
26
|
+
}
|