tascan-mcp 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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/index.js +674 -0
  4. package/package.json +52 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Edward Love II / Love Productions LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # TaScan MCP Server
2
+
3
+ AI agent integration for [TaScan](https://tascan.io) — the universal task protocol platform. Manage projects, events, tasks, workers, QR codes, templates, and completion reports through Claude, GitHub Copilot, or any MCP-compatible AI client.
4
+
5
+ **Scan. Task. Done.**
6
+
7
+ ## What is TaScan?
8
+
9
+ TaScan is a zero-download task assignment and verification platform for physical-world work. Workers scan a QR code, complete tasks with photo verification, and managers get real-time completion reports — no app download, no login, no training required.
10
+
11
+ Industries: live events, construction, hospitality, warehousing, property management, healthcare, aviation, FEMA disaster response, and more.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install tascan-mcp
17
+ ```
18
+
19
+ Or run directly with npx:
20
+
21
+ ```bash
22
+ npx tascan-mcp
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ ### Environment Variables
28
+
29
+ | Variable | Required | Description |
30
+ |----------|----------|-------------|
31
+ | `TASCAN_API_KEY` | Yes | Your TaScan API key (generate in Admin Portal > Team > API Keys) |
32
+ | `TASCAN_API_URL` | No | API base URL (default: `https://tascan-live-event.netlify.app/api/v1`) |
33
+
34
+ ### Claude Desktop
35
+
36
+ Add to your Claude Desktop config file (`claude_desktop_config.json`):
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "tascan": {
42
+ "command": "npx",
43
+ "args": ["-y", "tascan-mcp"],
44
+ "env": {
45
+ "TASCAN_API_KEY": "your-api-key-here"
46
+ }
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### Claude Code
53
+
54
+ ```bash
55
+ claude mcp add tascan -- npx -y tascan-mcp
56
+ ```
57
+
58
+ Then set your API key in the environment.
59
+
60
+ ## Tools (22)
61
+
62
+ ### Projects
63
+ | Tool | Description | Type |
64
+ |------|-------------|------|
65
+ | `tascan_list_projects` | List all projects in the organization | Read |
66
+ | `tascan_get_project` | Get details of a specific project | Read |
67
+ | `tascan_create_project` | Create a new project | Create |
68
+ | `tascan_update_project` | Update project name, location, status, dates | Update |
69
+ | `tascan_delete_project` | Delete a project and all its contents | Delete |
70
+
71
+ ### Events (Task Lists)
72
+ | Tool | Description | Type |
73
+ |------|-------------|------|
74
+ | `tascan_list_events` | List all events within a project | Read |
75
+ | `tascan_get_event` | Get event details including tasks | Read |
76
+ | `tascan_create_event` | Create a new event in a project | Create |
77
+ | `tascan_update_event` | Update event name, description, modes | Update |
78
+ | `tascan_delete_event` | Delete an event and all its tasks | Delete |
79
+
80
+ ### Tasks
81
+ | Tool | Description | Type |
82
+ |------|-------------|------|
83
+ | `tascan_list_tasks` | List all tasks in an event | Read |
84
+ | `tascan_get_task` | Get task details including completions | Read |
85
+ | `tascan_add_tasks` | Bulk-create tasks in an event | Create |
86
+ | `tascan_update_task` | Update task title, type, flags, order | Update |
87
+ | `tascan_delete_task` | Delete a task and its completions | Delete |
88
+
89
+ ### Workers
90
+ | Tool | Description | Type |
91
+ |------|-------------|------|
92
+ | `tascan_list_workers` | List all workers in the organization | Read |
93
+ | `tascan_create_worker` | Create a new worker profile | Create |
94
+ | `tascan_update_worker` | Update worker name, phone, email | Update |
95
+
96
+ ### Operations
97
+ | Tool | Description | Type |
98
+ |------|-------------|------|
99
+ | `tascan_generate_qr` | Generate a QR code for an event | Create |
100
+ | `tascan_apply_template` | Apply a pre-built template to an event | Create |
101
+ | `tascan_list_templates` | List available task templates | Read |
102
+ | `tascan_get_report` | Get completion report for an event | Read |
103
+
104
+ ## Usage Examples
105
+
106
+ ### Example 1: Set up a construction site inspection
107
+
108
+ ```
109
+ User: Create a construction project for the Downtown Tower site and set up a daily safety inspection with tasks for PPE check, fall protection, scaffolding inspection, and fire extinguisher check. Make all tasks safety checkpoints that require photos.
110
+
111
+ Claude will:
112
+ 1. Call tascan_create_project with name "Downtown Tower" and location "123 Main St"
113
+ 2. Call tascan_create_event with name "Daily Safety Inspection"
114
+ 3. Call tascan_add_tasks with 4 safety checkpoint tasks requiring photos
115
+ 4. Call tascan_generate_qr to create a scannable QR code for the foreman
116
+ ```
117
+
118
+ ### Example 2: Check event completion status
119
+
120
+ ```
121
+ User: How's the hotel room turnover going for the Marriott project? Which rooms still need to be finished?
122
+
123
+ Claude will:
124
+ 1. Call tascan_list_projects to find the Marriott project
125
+ 2. Call tascan_list_events to find active room turnovers
126
+ 3. Call tascan_get_report for each event to show completion rates
127
+ 4. Summarize which tasks are incomplete and which workers are assigned
128
+ ```
129
+
130
+ ### Example 3: Use templates to quickly deploy an event
131
+
132
+ ```
133
+ User: We have a warehouse receiving shipment coming in tomorrow. Set up the standard receiving checklist.
134
+
135
+ Claude will:
136
+ 1. Call tascan_list_templates with category "logistics" to find available templates
137
+ 2. Call tascan_create_project for the warehouse
138
+ 3. Call tascan_create_event for the receiving session
139
+ 4. Call tascan_apply_template with the "warehouse-receiving" template slug
140
+ 5. Call tascan_generate_qr for the receiving dock crew
141
+ ```
142
+
143
+ ### Example 4: Manage workers across multiple events
144
+
145
+ ```
146
+ User: Show me all our workers and which events they've completed this week. Flag anyone with a reliability score below 80%.
147
+
148
+ Claude will:
149
+ 1. Call tascan_list_workers to get all worker profiles
150
+ 2. Call tascan_list_projects and tascan_list_events to enumerate active events
151
+ 3. Call tascan_get_report for each event to check worker completions
152
+ 4. Analyze completion data and flag underperforming workers
153
+ ```
154
+
155
+ ## Task Types
156
+
157
+ Tasks support multiple response types:
158
+
159
+ | Type | Description |
160
+ |------|-------------|
161
+ | `checkbox` | Simple done/not-done (default) |
162
+ | `photo` | Requires photo upload to complete |
163
+ | `text` | Free-text response |
164
+ | `number` | Numeric response |
165
+ | `date` | Date selection |
166
+ | `choice` | Multiple choice selection |
167
+
168
+ Tasks can also be flagged as:
169
+ - **Safety checkpoints** (`is_safety_checkpoint: true`) — highlighted in red, cannot be skipped
170
+ - **Photo required** (`requires_photo: true`) — worker must attach a photo to complete
171
+
172
+ ## Getting an API Key
173
+
174
+ 1. Log in to the [TaScan Admin Portal](https://tascan-live-event.netlify.app)
175
+ 2. Navigate to **Team** in the sidebar
176
+ 3. Scroll to **API Keys**
177
+ 4. Click **Generate API Key**
178
+ 5. Copy the key (it's only shown once)
179
+ 6. Set it as `TASCAN_API_KEY` in your environment
180
+
181
+ API keys are scoped to your organization and support rate limiting (60 requests/minute).
182
+
183
+ ## Privacy Policy
184
+
185
+ TaScan collects and processes task completion data, worker information (name, phone, email), GPS coordinates (with consent), and photos uploaded during task completion. Data is stored securely in Supabase with row-level security policies. API access is authenticated and rate-limited.
186
+
187
+ For the full privacy policy, visit: https://tascan.io/privacy
188
+
189
+ For data deletion requests or privacy inquiries, contact: Michael@TaScan.io
190
+
191
+ ## Support
192
+
193
+ - **Email:** Michael@TaScan.io
194
+ - **Website:** https://tascan.io
195
+ - **Issues:** https://github.com/snowbikemike/tascan-mcp/issues
196
+
197
+ ## License
198
+
199
+ MIT License - Copyright (c) 2026 Michael Edward Love II / Love Productions LLC
package/index.js ADDED
@@ -0,0 +1,674 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ============================================================
4
+ // TaScan MCP Server
5
+ // Wraps TaScan REST API v1 for Claude and other AI agents
6
+ // Transport: stdio (for Claude Desktop / Claude Code)
7
+ // ============================================================
8
+
9
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ import { z } from 'zod';
12
+
13
+ const API_BASE = process.env.TASCAN_API_URL || 'https://tascan-live-event.netlify.app/api/v1';
14
+ const API_KEY = process.env.TASCAN_API_KEY || '';
15
+
16
+ if (!API_KEY) {
17
+ console.error('TASCAN_API_KEY environment variable is required');
18
+ process.exit(1);
19
+ }
20
+
21
+ // ─── API Helper ────────────────────────────────────────────
22
+ async function apiCall(method, path, body) {
23
+ const url = API_BASE + path;
24
+ const opts = {
25
+ method,
26
+ headers: {
27
+ 'Authorization': 'Bearer ' + API_KEY,
28
+ 'Content-Type': 'application/json'
29
+ }
30
+ };
31
+
32
+ if (body && (method === 'POST' || method === 'PUT')) {
33
+ opts.body = JSON.stringify(body);
34
+ }
35
+
36
+ const resp = await fetch(url, opts);
37
+ const data = await resp.json();
38
+
39
+ if (!resp.ok) {
40
+ throw new Error(data.error || `API error ${resp.status}`);
41
+ }
42
+
43
+ return data;
44
+ }
45
+
46
+ // ─── Server Setup ──────────────────────────────────────────
47
+ const server = new McpServer({
48
+ name: 'tascan',
49
+ version: '1.0.0'
50
+ });
51
+
52
+
53
+ // ─── Tool: List Projects ───────────────────────────────────
54
+ server.registerTool(
55
+ 'tascan_list_projects',
56
+ {
57
+ title: 'List Projects',
58
+ description: 'List all TaScan projects in the organization',
59
+ inputSchema: {},
60
+ annotations: {
61
+ readOnlyHint: true,
62
+ destructiveHint: false
63
+ }
64
+ },
65
+ async () => {
66
+ const result = await apiCall('GET', '/projects');
67
+ return {
68
+ content: [{
69
+ type: 'text',
70
+ text: JSON.stringify(result.data, null, 2)
71
+ }]
72
+ };
73
+ }
74
+ );
75
+
76
+
77
+ // ─── Tool: Create Project ──────────────────────────────────
78
+ server.registerTool(
79
+ 'tascan_create_project',
80
+ {
81
+ title: 'Create Project',
82
+ description: 'Create a new TaScan project (top-level container for events)',
83
+ inputSchema: {
84
+ name: z.string().describe('Project name'),
85
+ location: z.string().optional().describe('Project location / venue')
86
+ },
87
+ annotations: {
88
+ readOnlyHint: false,
89
+ destructiveHint: false
90
+ }
91
+ },
92
+ async ({ name, location }) => {
93
+ const result = await apiCall('POST', '/projects', { name, location });
94
+ return {
95
+ content: [{
96
+ type: 'text',
97
+ text: `Project created: ${result.data.name} (ID: ${result.data.id})\n\n${JSON.stringify(result.data, null, 2)}`
98
+ }]
99
+ };
100
+ }
101
+ );
102
+
103
+
104
+ // ─── Tool: Create Event (Task List) ────────────────────────
105
+ server.registerTool(
106
+ 'tascan_create_event',
107
+ {
108
+ title: 'Create Event',
109
+ description: 'Create a new event (task list) within a project',
110
+ inputSchema: {
111
+ project_id: z.string().describe('Project ID to add the event to'),
112
+ name: z.string().describe('Event name'),
113
+ description: z.string().optional().describe('Event description')
114
+ },
115
+ annotations: {
116
+ readOnlyHint: false,
117
+ destructiveHint: false
118
+ }
119
+ },
120
+ async ({ project_id, name, description }) => {
121
+ const result = await apiCall('POST', `/projects/${project_id}/lists`, { name, description });
122
+ return {
123
+ content: [{
124
+ type: 'text',
125
+ text: `Event created: ${result.data.name} (ID: ${result.data.id})\n\n${JSON.stringify(result.data, null, 2)}`
126
+ }]
127
+ };
128
+ }
129
+ );
130
+
131
+
132
+ // ─── Tool: Add Tasks (Bulk) ────────────────────────────────
133
+ server.registerTool(
134
+ 'tascan_add_tasks',
135
+ {
136
+ title: 'Add Tasks',
137
+ description: 'Add one or more tasks to an event (task list). Supports bulk creation.',
138
+ inputSchema: {
139
+ list_id: z.string().describe('Task list (event) ID'),
140
+ tasks: z.array(z.object({
141
+ title: z.string().describe('Task title'),
142
+ description: z.string().optional().describe('Task description'),
143
+ response_type: z.enum(['checkbox', 'photo', 'text', 'number', 'date', 'choice']).optional().describe('Task type (default: checkbox)'),
144
+ is_safety_checkpoint: z.boolean().optional().describe('Safety-critical task flag'),
145
+ requires_photo: z.boolean().optional().describe('Require photo on completion')
146
+ })).describe('Array of tasks to create')
147
+ },
148
+ annotations: {
149
+ readOnlyHint: false,
150
+ destructiveHint: false
151
+ }
152
+ },
153
+ async ({ list_id, tasks }) => {
154
+ const result = await apiCall('POST', `/lists/${list_id}/tasks`, tasks);
155
+ return {
156
+ content: [{
157
+ type: 'text',
158
+ text: `${result.count} task(s) created in list ${list_id}\n\n${JSON.stringify(result.data, null, 2)}`
159
+ }]
160
+ };
161
+ }
162
+ );
163
+
164
+
165
+ // ─── Tool: List Workers ────────────────────────────────────
166
+ server.registerTool(
167
+ 'tascan_list_workers',
168
+ {
169
+ title: 'List Workers',
170
+ description: 'List all workers (taskees) in the organization',
171
+ inputSchema: {},
172
+ annotations: {
173
+ readOnlyHint: true,
174
+ destructiveHint: false
175
+ }
176
+ },
177
+ async () => {
178
+ const result = await apiCall('GET', '/workers');
179
+ return {
180
+ content: [{
181
+ type: 'text',
182
+ text: JSON.stringify(result.data, null, 2)
183
+ }]
184
+ };
185
+ }
186
+ );
187
+
188
+
189
+ // ─── Tool: Create Worker ───────────────────────────────────
190
+ server.registerTool(
191
+ 'tascan_create_worker',
192
+ {
193
+ title: 'Create Worker',
194
+ description: 'Create a new worker (taskee) in the organization',
195
+ inputSchema: {
196
+ name: z.string().describe('Worker name'),
197
+ phone: z.string().optional().describe('Worker phone number'),
198
+ email: z.string().optional().describe('Worker email')
199
+ },
200
+ annotations: {
201
+ readOnlyHint: false,
202
+ destructiveHint: false
203
+ }
204
+ },
205
+ async ({ name, phone, email }) => {
206
+ const result = await apiCall('POST', '/workers', { name, phone, email });
207
+ return {
208
+ content: [{
209
+ type: 'text',
210
+ text: `Worker created: ${result.data.name} (ID: ${result.data.id})\n\n${JSON.stringify(result.data, null, 2)}`
211
+ }]
212
+ };
213
+ }
214
+ );
215
+
216
+
217
+ // ─── Tool: Generate QR Code ────────────────────────────────
218
+ server.registerTool(
219
+ 'tascan_generate_qr',
220
+ {
221
+ title: 'Generate QR Code',
222
+ description: 'Generate a QR code for a task list (event) that workers can scan to access tasks',
223
+ inputSchema: {
224
+ list_id: z.string().describe('Task list (event) ID')
225
+ },
226
+ annotations: {
227
+ readOnlyHint: false,
228
+ destructiveHint: false
229
+ }
230
+ },
231
+ async ({ list_id }) => {
232
+ const result = await apiCall('POST', `/lists/${list_id}/qr`);
233
+ return {
234
+ content: [{
235
+ type: 'text',
236
+ text: `QR Code generated for "${result.data.list_name}":\n- Scan URL: ${result.data.scan_url}\n- QR Image: ${result.data.qr_image_url}`
237
+ }]
238
+ };
239
+ }
240
+ );
241
+
242
+
243
+ // ─── Tool: Apply Template ──────────────────────────────────
244
+ server.registerTool(
245
+ 'tascan_apply_template',
246
+ {
247
+ title: 'Apply Template',
248
+ description: 'Apply a pre-built template to a task list, adding all template tasks',
249
+ inputSchema: {
250
+ list_id: z.string().describe('Task list (event) ID to apply template to'),
251
+ template_slug: z.string().describe('Template slug (e.g. "conference-load-in", "warehouse-receiving", "restaurant-opening", "hotel-room-turnover")')
252
+ },
253
+ annotations: {
254
+ readOnlyHint: false,
255
+ destructiveHint: false
256
+ }
257
+ },
258
+ async ({ list_id, template_slug }) => {
259
+ const result = await apiCall('POST', `/lists/${list_id}/apply-template`, { template_slug });
260
+ return {
261
+ content: [{
262
+ type: 'text',
263
+ text: `Template applied! ${result.count} tasks added to list ${list_id}\n\n${JSON.stringify(result.data, null, 2)}`
264
+ }]
265
+ };
266
+ }
267
+ );
268
+
269
+
270
+ // ─── Tool: Get Report ──────────────────────────────────────
271
+ server.registerTool(
272
+ 'tascan_get_report',
273
+ {
274
+ title: 'Get Completion Report',
275
+ description: 'Get completion report for a task list (event) including task status, completions, workers, and photos',
276
+ inputSchema: {
277
+ list_id: z.string().describe('Task list (event) ID')
278
+ },
279
+ annotations: {
280
+ readOnlyHint: true,
281
+ destructiveHint: false
282
+ }
283
+ },
284
+ async ({ list_id }) => {
285
+ const result = await apiCall('GET', `/lists/${list_id}/report`);
286
+ const r = result.data;
287
+ let text = `Report for "${r.list_name}":\n`;
288
+ text += `Completion: ${r.completed_tasks}/${r.total_tasks} (${r.completion_rate}%)\n\n`;
289
+
290
+ for (const task of r.tasks) {
291
+ const status = task.completed ? '[x]' : '[ ]';
292
+ text += `${status} ${task.title}`;
293
+ if (task.completions && task.completions.length > 0) {
294
+ text += ` (by ${task.completions.map(c => c.worker_name).join(', ')})`;
295
+ }
296
+ text += '\n';
297
+ }
298
+
299
+ return { content: [{ type: 'text', text }] };
300
+ }
301
+ );
302
+
303
+
304
+ // ─── Tool: List Templates ──────────────────────────────────
305
+ server.registerTool(
306
+ 'tascan_list_templates',
307
+ {
308
+ title: 'List Templates',
309
+ description: 'List available task templates (built-in and saved)',
310
+ inputSchema: {
311
+ category: z.string().optional().describe('Filter by category (e.g. "live-events", "hospitality", "logistics", "construction", "facilities", "general", "field-service", "education")')
312
+ },
313
+ annotations: {
314
+ readOnlyHint: true,
315
+ destructiveHint: false
316
+ }
317
+ },
318
+ async ({ category }) => {
319
+ const path = category ? `/templates?category=${encodeURIComponent(category)}` : '/templates';
320
+ const result = await apiCall('GET', path);
321
+ return {
322
+ content: [{
323
+ type: 'text',
324
+ text: JSON.stringify(result.data, null, 2)
325
+ }]
326
+ };
327
+ }
328
+ );
329
+
330
+
331
+ // ─── Tool: List Events (Task Lists) ──────────────────────
332
+ server.registerTool(
333
+ 'tascan_list_events',
334
+ {
335
+ title: 'List Events',
336
+ description: 'List all events (task lists) within a project',
337
+ inputSchema: {
338
+ project_id: z.string().describe('Project ID')
339
+ },
340
+ annotations: {
341
+ readOnlyHint: true,
342
+ destructiveHint: false
343
+ }
344
+ },
345
+ async ({ project_id }) => {
346
+ const result = await apiCall('GET', `/projects/${project_id}/lists`);
347
+ return {
348
+ content: [{
349
+ type: 'text',
350
+ text: JSON.stringify(result.data, null, 2)
351
+ }]
352
+ };
353
+ }
354
+ );
355
+
356
+
357
+ // ─── Tool: Get Event ─────────────────────────────────────
358
+ server.registerTool(
359
+ 'tascan_get_event',
360
+ {
361
+ title: 'Get Event',
362
+ description: 'Get details of a specific event (task list) including its tasks',
363
+ inputSchema: {
364
+ list_id: z.string().describe('Task list (event) ID')
365
+ },
366
+ annotations: {
367
+ readOnlyHint: true,
368
+ destructiveHint: false
369
+ }
370
+ },
371
+ async ({ list_id }) => {
372
+ const result = await apiCall('GET', `/lists/${list_id}`);
373
+ return {
374
+ content: [{
375
+ type: 'text',
376
+ text: JSON.stringify(result.data, null, 2)
377
+ }]
378
+ };
379
+ }
380
+ );
381
+
382
+
383
+ // ─── Tool: List Tasks ────────────────────────────────────
384
+ server.registerTool(
385
+ 'tascan_list_tasks',
386
+ {
387
+ title: 'List Tasks',
388
+ description: 'List all tasks in an event (task list)',
389
+ inputSchema: {
390
+ list_id: z.string().describe('Task list (event) ID')
391
+ },
392
+ annotations: {
393
+ readOnlyHint: true,
394
+ destructiveHint: false
395
+ }
396
+ },
397
+ async ({ list_id }) => {
398
+ const result = await apiCall('GET', `/lists/${list_id}/tasks`);
399
+ return {
400
+ content: [{
401
+ type: 'text',
402
+ text: JSON.stringify(result.data, null, 2)
403
+ }]
404
+ };
405
+ }
406
+ );
407
+
408
+
409
+ // ─── Tool: Get Project ───────────────────────────────────
410
+ server.registerTool(
411
+ 'tascan_get_project',
412
+ {
413
+ title: 'Get Project',
414
+ description: 'Get details of a specific project',
415
+ inputSchema: {
416
+ project_id: z.string().describe('Project ID')
417
+ },
418
+ annotations: {
419
+ readOnlyHint: true,
420
+ destructiveHint: false
421
+ }
422
+ },
423
+ async ({ project_id }) => {
424
+ const result = await apiCall('GET', `/projects/${project_id}`);
425
+ return {
426
+ content: [{
427
+ type: 'text',
428
+ text: JSON.stringify(result.data, null, 2)
429
+ }]
430
+ };
431
+ }
432
+ );
433
+
434
+
435
+ // ─── Tool: Get Task ──────────────────────────────────────
436
+ server.registerTool(
437
+ 'tascan_get_task',
438
+ {
439
+ title: 'Get Task',
440
+ description: 'Get details of a specific task including completions',
441
+ inputSchema: {
442
+ task_id: z.string().describe('Task ID')
443
+ },
444
+ annotations: {
445
+ readOnlyHint: true,
446
+ destructiveHint: false
447
+ }
448
+ },
449
+ async ({ task_id }) => {
450
+ const result = await apiCall('GET', `/tasks/${task_id}`);
451
+ return {
452
+ content: [{
453
+ type: 'text',
454
+ text: JSON.stringify(result.data, null, 2)
455
+ }]
456
+ };
457
+ }
458
+ );
459
+
460
+
461
+ // ─── Tool: Update Project ────────────────────────────────
462
+ server.registerTool(
463
+ 'tascan_update_project',
464
+ {
465
+ title: 'Update Project',
466
+ description: 'Update a project (name, location, status, dates)',
467
+ inputSchema: {
468
+ project_id: z.string().describe('Project ID'),
469
+ name: z.string().optional().describe('New project name'),
470
+ location: z.string().optional().describe('New location'),
471
+ status: z.enum(['active', 'archived']).optional().describe('Project status'),
472
+ start_date: z.string().optional().describe('Start date (ISO format)'),
473
+ end_date: z.string().optional().describe('End date (ISO format)')
474
+ },
475
+ annotations: {
476
+ readOnlyHint: false,
477
+ destructiveHint: false
478
+ }
479
+ },
480
+ async ({ project_id, ...updates }) => {
481
+ const body = Object.fromEntries(Object.entries(updates).filter(([, v]) => v !== undefined));
482
+ const result = await apiCall('PUT', `/projects/${project_id}`, body);
483
+ return {
484
+ content: [{
485
+ type: 'text',
486
+ text: `Project updated: ${result.data.name}\n\n${JSON.stringify(result.data, null, 2)}`
487
+ }]
488
+ };
489
+ }
490
+ );
491
+
492
+
493
+ // ─── Tool: Update Event ──────────────────────────────────
494
+ server.registerTool(
495
+ 'tascan_update_event',
496
+ {
497
+ title: 'Update Event',
498
+ description: 'Update an event / task list (name, description, team_mode, timer_mode)',
499
+ inputSchema: {
500
+ list_id: z.string().describe('Task list (event) ID'),
501
+ name: z.string().optional().describe('New event name'),
502
+ description: z.string().optional().describe('New description'),
503
+ team_mode: z.boolean().optional().describe('Team mode on/off'),
504
+ timer_mode: z.boolean().optional().describe('Timer mode on/off')
505
+ },
506
+ annotations: {
507
+ readOnlyHint: false,
508
+ destructiveHint: false
509
+ }
510
+ },
511
+ async ({ list_id, ...updates }) => {
512
+ const body = Object.fromEntries(Object.entries(updates).filter(([, v]) => v !== undefined));
513
+ const result = await apiCall('PUT', `/lists/${list_id}`, body);
514
+ return {
515
+ content: [{
516
+ type: 'text',
517
+ text: `Event updated: ${result.data.name}\n\n${JSON.stringify(result.data, null, 2)}`
518
+ }]
519
+ };
520
+ }
521
+ );
522
+
523
+
524
+ // ─── Tool: Update Task ───────────────────────────────────
525
+ server.registerTool(
526
+ 'tascan_update_task',
527
+ {
528
+ title: 'Update Task',
529
+ description: 'Update a task (title, description, response_type, flags, sort_order)',
530
+ inputSchema: {
531
+ task_id: z.string().describe('Task ID'),
532
+ title: z.string().optional().describe('New title'),
533
+ description: z.string().optional().describe('New description'),
534
+ response_type: z.enum(['checkbox', 'photo', 'text', 'number', 'date', 'choice']).optional().describe('Task type'),
535
+ is_safety_checkpoint: z.boolean().optional().describe('Safety-critical flag'),
536
+ requires_photo: z.boolean().optional().describe('Require photo'),
537
+ sort_order: z.number().optional().describe('Sort order position')
538
+ },
539
+ annotations: {
540
+ readOnlyHint: false,
541
+ destructiveHint: false
542
+ }
543
+ },
544
+ async ({ task_id, ...updates }) => {
545
+ const body = Object.fromEntries(Object.entries(updates).filter(([, v]) => v !== undefined));
546
+ const result = await apiCall('PUT', `/tasks/${task_id}`, body);
547
+ return {
548
+ content: [{
549
+ type: 'text',
550
+ text: `Task updated: ${result.data.title}\n\n${JSON.stringify(result.data, null, 2)}`
551
+ }]
552
+ };
553
+ }
554
+ );
555
+
556
+
557
+ // ─── Tool: Delete Project ────────────────────────────────
558
+ server.registerTool(
559
+ 'tascan_delete_project',
560
+ {
561
+ title: 'Delete Project',
562
+ description: 'Delete a project and all its events, tasks, and completions',
563
+ inputSchema: {
564
+ project_id: z.string().describe('Project ID to delete')
565
+ },
566
+ annotations: {
567
+ readOnlyHint: false,
568
+ destructiveHint: true
569
+ }
570
+ },
571
+ async ({ project_id }) => {
572
+ await apiCall('DELETE', `/projects/${project_id}`);
573
+ return {
574
+ content: [{
575
+ type: 'text',
576
+ text: `Project ${project_id} deleted successfully.`
577
+ }]
578
+ };
579
+ }
580
+ );
581
+
582
+
583
+ // ─── Tool: Delete Event ──────────────────────────────────
584
+ server.registerTool(
585
+ 'tascan_delete_event',
586
+ {
587
+ title: 'Delete Event',
588
+ description: 'Delete an event (task list) and all its tasks and completions',
589
+ inputSchema: {
590
+ list_id: z.string().describe('Task list (event) ID to delete')
591
+ },
592
+ annotations: {
593
+ readOnlyHint: false,
594
+ destructiveHint: true
595
+ }
596
+ },
597
+ async ({ list_id }) => {
598
+ await apiCall('DELETE', `/lists/${list_id}`);
599
+ return {
600
+ content: [{
601
+ type: 'text',
602
+ text: `Event ${list_id} deleted successfully.`
603
+ }]
604
+ };
605
+ }
606
+ );
607
+
608
+
609
+ // ─── Tool: Delete Task ───────────────────────────────────
610
+ server.registerTool(
611
+ 'tascan_delete_task',
612
+ {
613
+ title: 'Delete Task',
614
+ description: 'Delete a specific task and its completions',
615
+ inputSchema: {
616
+ task_id: z.string().describe('Task ID to delete')
617
+ },
618
+ annotations: {
619
+ readOnlyHint: false,
620
+ destructiveHint: true
621
+ }
622
+ },
623
+ async ({ task_id }) => {
624
+ await apiCall('DELETE', `/tasks/${task_id}`);
625
+ return {
626
+ content: [{
627
+ type: 'text',
628
+ text: `Task ${task_id} deleted successfully.`
629
+ }]
630
+ };
631
+ }
632
+ );
633
+
634
+
635
+ // ─── Tool: Update Worker ─────────────────────────────────
636
+ server.registerTool(
637
+ 'tascan_update_worker',
638
+ {
639
+ title: 'Update Worker',
640
+ description: 'Update a worker profile (name, phone, email)',
641
+ inputSchema: {
642
+ worker_id: z.string().describe('Worker ID'),
643
+ name: z.string().optional().describe('New name'),
644
+ phone: z.string().optional().describe('New phone number'),
645
+ email: z.string().optional().describe('New email')
646
+ },
647
+ annotations: {
648
+ readOnlyHint: false,
649
+ destructiveHint: false
650
+ }
651
+ },
652
+ async ({ worker_id, ...updates }) => {
653
+ const body = Object.fromEntries(Object.entries(updates).filter(([, v]) => v !== undefined));
654
+ const result = await apiCall('PUT', `/workers/${worker_id}`, body);
655
+ return {
656
+ content: [{
657
+ type: 'text',
658
+ text: `Worker updated: ${result.data.name}\n\n${JSON.stringify(result.data, null, 2)}`
659
+ }]
660
+ };
661
+ }
662
+ );
663
+
664
+
665
+ // ─── Start Server ──────────────────────────────────────────
666
+ async function main() {
667
+ const transport = new StdioServerTransport();
668
+ await server.connect(transport);
669
+ }
670
+
671
+ main().catch(err => {
672
+ console.error('TaScan MCP server error:', err);
673
+ process.exit(1);
674
+ });
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "tascan-mcp",
3
+ "version": "1.0.0",
4
+ "description": "TaScan MCP Server — AI agent integration for the universal task protocol platform. Manage projects, events, tasks, workers, QR codes, templates, and reports. Scan. Task. Done.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "tascan-mcp": "./index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node index.js"
12
+ },
13
+ "files": [
14
+ "index.js",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.27.0",
20
+ "zod": "^4.3.6"
21
+ },
22
+ "keywords": [
23
+ "tascan",
24
+ "mcp",
25
+ "model-context-protocol",
26
+ "ai",
27
+ "task-management",
28
+ "qr-code",
29
+ "field-operations",
30
+ "workforce",
31
+ "pwa",
32
+ "claude"
33
+ ],
34
+ "author": {
35
+ "name": "Michael Edward Love II",
36
+ "email": "Michael@TaScan.io",
37
+ "url": "https://tascan.io"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/snowbikemike/tascan-mcp.git"
42
+ },
43
+ "homepage": "https://tascan.io",
44
+ "bugs": {
45
+ "url": "https://github.com/snowbikemike/tascan-mcp/issues",
46
+ "email": "Michael@TaScan.io"
47
+ },
48
+ "license": "MIT",
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ }
52
+ }