mcp-server-moscow 0.0.1
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.dev.md +81 -0
- package/README.md +78 -0
- package/dist/index.js +264 -0
- package/package.json +26 -0
package/README.dev.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
- `MCP_TOKEN` (Optional): A custom token for authorization if your Edge Functions require it.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Using with Claude Desktop or Antigravity
|
|
22
|
+
|
|
23
|
+
Add the following to your MCP configuration file (e.g., `claude_desktop_config.json` or `mcp_config.json`):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"moscow": {
|
|
29
|
+
"command": "node",
|
|
30
|
+
"args": [
|
|
31
|
+
"/Users/guiller/WebstormProjects/moscow-task/mcp-server/dist/index.js",
|
|
32
|
+
"--stdio"
|
|
33
|
+
],
|
|
34
|
+
"env": {
|
|
35
|
+
"SUPABASE_URL": "https://your-project.supabase.co",
|
|
36
|
+
"MCP_TOKEN": "mcp_123.....",
|
|
37
|
+
"TOPIC": "mcp"
|
|
38
|
+
},
|
|
39
|
+
"disabled": false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Running Locally (Development)
|
|
46
|
+
|
|
47
|
+
1. **Install dependencies**:
|
|
48
|
+
```bash
|
|
49
|
+
npm install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
2. **Build**:
|
|
53
|
+
```bash
|
|
54
|
+
npm run build
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Deploy to NPM
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
1. **Login to NPM**:
|
|
61
|
+
```bash
|
|
62
|
+
npm login
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
2. **Publish**:
|
|
66
|
+
```bash
|
|
67
|
+
npm publish --access public
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
After publishing, anyone can use your server with:
|
|
71
|
+
```bash
|
|
72
|
+
npx -y moscow-mcp-server
|
|
73
|
+
```
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Tools Provided
|
|
77
|
+
|
|
78
|
+
- `list_tasks`: List tasks from the Moscow project. Can be filtered by `topic`.
|
|
79
|
+
- `create_task`: Create a new task.
|
|
80
|
+
- `update_task`: Update an existing task.
|
|
81
|
+
- `delete_task`: Delete a task.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Moscow MCP Server
|
|
2
|
+
|
|
3
|
+
This is a **Model Context Protocol (MCP)** server for the **Moscow Task Management** system. It allows AI assistants like Claude Desktop or Antigravity to interact with your personal task list directly.
|
|
4
|
+
|
|
5
|
+
## What is this for?
|
|
6
|
+
|
|
7
|
+
By connecting this MCP server to your AI assistant, you can:
|
|
8
|
+
- **List your tasks**: Ask "What tasks do I have?"
|
|
9
|
+
- **Create tasks**: Say "Add a task to check email in the morning."
|
|
10
|
+
- **Update tasks**: "Mark the email task as done."
|
|
11
|
+
- **Manage priorities**: "Review my MUST priority tasks."
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
1. **Sign Up**: You must have an account on the Moscow Task Manager app at [https://moscow-task.pages.dev/](https://moscow-task.pages.dev/).
|
|
16
|
+
|
|
17
|
+
## How to Get Your Token
|
|
18
|
+
|
|
19
|
+
To secure your task data, this MCP server requires a personal access token.
|
|
20
|
+
|
|
21
|
+
1. **Log in** to [https://moscow-task.pages.dev/](https://moscow-task.pages.dev/).
|
|
22
|
+
2. Open the **Settings** menu (click the gear icon or your profile).
|
|
23
|
+
3. Scroll down to the **Moscow MCP Tokens** section.
|
|
24
|
+
4. Enter a name for your token (e.g., "Claude Desktop") in the input field.
|
|
25
|
+
5. Click the **+ (Plus)** button to generate the token.
|
|
26
|
+
6. Click the **Copy** icon next to your new token to copy it to your clipboard.
|
|
27
|
+
|
|
28
|
+
> **Keep this token secret!** It gives full access to your tasks.
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
### 1. Using with Claude Desktop
|
|
33
|
+
|
|
34
|
+
Add the following to your Claude Desktop configuration file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"moscow": {
|
|
40
|
+
"command": "npx",
|
|
41
|
+
"args": [
|
|
42
|
+
"-y",
|
|
43
|
+
"mcp-server-moscow"
|
|
44
|
+
],
|
|
45
|
+
"env": {
|
|
46
|
+
"MCP_TOKEN": "paste-your-token-here"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
*Replace `paste-your-token-here` with the token you copied from the website.*
|
|
54
|
+
|
|
55
|
+
### 2. Manual / Local Development
|
|
56
|
+
|
|
57
|
+
If you are running the server from source:
|
|
58
|
+
|
|
59
|
+
1. Clone the repository.
|
|
60
|
+
2. Install dependencies: `npm install`
|
|
61
|
+
3. Build the project: `npm run build`
|
|
62
|
+
4. Add to your MCP config using the full path to the built file:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"moscow-local": {
|
|
68
|
+
"command": "node",
|
|
69
|
+
"args": [
|
|
70
|
+
"/absolute/path/to/moscow-task/mcp-server/dist/index.js"
|
|
71
|
+
],
|
|
72
|
+
"env": {
|
|
73
|
+
"MCP_TOKEN": "paste-your-token-here"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
// Load .env from the project root relative to this file's location
|
|
12
|
+
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
|
13
|
+
const SUPABASE_URL = process.env.SUPABASE_URL || 'https://jnxbtfiwzaxwheifuhuf.supabase.co';
|
|
14
|
+
const DEFAULT_TOPIC = process.env.TOPIC || 'default';
|
|
15
|
+
if (!SUPABASE_URL) {
|
|
16
|
+
console.error('Missing SUPABASE_URL environment variable');
|
|
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;
|
|
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
|
+
},
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const errorMessage = axios.isAxiosError(error)
|
|
149
|
+
? error.response?.data?.error || error.message
|
|
150
|
+
: error instanceof Error
|
|
151
|
+
? error.message
|
|
152
|
+
: String(error);
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: 'text', text: `Error listing tasks: ${errorMessage}` }],
|
|
155
|
+
isError: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
case 'create_task': {
|
|
160
|
+
const { title, description, topic, priority, status } = request.params.arguments || {};
|
|
161
|
+
try {
|
|
162
|
+
const response = await axios.post(getEdgeFunctionUrl(), {
|
|
163
|
+
title,
|
|
164
|
+
description,
|
|
165
|
+
topic: topic || topicScope,
|
|
166
|
+
priority,
|
|
167
|
+
status,
|
|
168
|
+
}, {
|
|
169
|
+
headers: {
|
|
170
|
+
Authorization: `Bearer ${authValue}`,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: 'text',
|
|
177
|
+
text: `Task created successfully: ${JSON.stringify(response.data, null, 2)}`,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
const errorMessage = axios.isAxiosError(error)
|
|
184
|
+
? error.response?.data?.error || error.message
|
|
185
|
+
: error instanceof Error
|
|
186
|
+
? error.message
|
|
187
|
+
: String(error);
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: 'text', text: `Error creating task: ${errorMessage}` }],
|
|
190
|
+
isError: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
case 'update_task': {
|
|
195
|
+
const { id, ...updates } = request.params.arguments || {};
|
|
196
|
+
try {
|
|
197
|
+
const response = await axios.patch(getEdgeFunctionUrl(), {
|
|
198
|
+
id,
|
|
199
|
+
...updates,
|
|
200
|
+
}, {
|
|
201
|
+
headers: {
|
|
202
|
+
Authorization: `Bearer ${authValue}`,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
content: [
|
|
207
|
+
{
|
|
208
|
+
type: 'text',
|
|
209
|
+
text: `Task updated successfully: ${JSON.stringify(response.data, null, 2)}`,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
const errorMessage = axios.isAxiosError(error)
|
|
216
|
+
? error.response?.data?.error || error.message
|
|
217
|
+
: error instanceof Error
|
|
218
|
+
? error.message
|
|
219
|
+
: String(error);
|
|
220
|
+
return {
|
|
221
|
+
content: [{ type: 'text', text: `Error updating task: ${errorMessage}` }],
|
|
222
|
+
isError: true,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
case 'delete_task': {
|
|
227
|
+
const { id } = request.params.arguments || {};
|
|
228
|
+
try {
|
|
229
|
+
await axios.delete(getEdgeFunctionUrl(), {
|
|
230
|
+
params: { id },
|
|
231
|
+
headers: {
|
|
232
|
+
Authorization: `Bearer ${authValue}`,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: 'text', text: `Task ${id} deleted successfully.` }],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const errorMessage = axios.isAxiosError(error)
|
|
241
|
+
? error.response?.data?.error || error.message
|
|
242
|
+
: error instanceof Error
|
|
243
|
+
? error.message
|
|
244
|
+
: String(error);
|
|
245
|
+
return {
|
|
246
|
+
content: [{ type: 'text', text: `Error deleting task: ${errorMessage}` }],
|
|
247
|
+
isError: true,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
default:
|
|
252
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
// STARTUP LOGIC: Always use Stdio
|
|
256
|
+
async function main() {
|
|
257
|
+
const transport = new StdioServerTransport();
|
|
258
|
+
await server.connect(transport);
|
|
259
|
+
console.error('Moscow MCP Server running on Stdio');
|
|
260
|
+
}
|
|
261
|
+
main().catch((error) => {
|
|
262
|
+
console.error('Server error:', error);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-server-moscow",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "MCP 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
|
+
}
|