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.
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/index.js +674 -0
- 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
|
+
}
|