glintlint-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/README.md +95 -0
- package/dist/cli.js +272 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +268 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Glintlint MCP Server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [Glintlint](https://glintlint.com) — manage your tasks with AI assistants like Claude Desktop, Cursor, and more.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **8 tools** for task management: list, create, update, complete, delete tasks + groups + stats
|
|
8
|
+
- **Today summary** — get a quick overview of today's tasks grouped by time block
|
|
9
|
+
- Works with **Claude Desktop**, **Cursor**, and any MCP-compatible AI tool
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- Node.js 20+
|
|
14
|
+
- A Glintlint account with an API key (get one from **Settings > API Keys**)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### Claude Desktop
|
|
19
|
+
|
|
20
|
+
Add to your `claude_desktop_config.json`:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"mcpServers": {
|
|
25
|
+
"glintlint": {
|
|
26
|
+
"command": "npx",
|
|
27
|
+
"args": ["-y", "glintlint-mcp"],
|
|
28
|
+
"env": {
|
|
29
|
+
"GLINTLINT_API_KEY": "glint_your_api_key_here"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Cursor
|
|
37
|
+
|
|
38
|
+
Add to your MCP settings:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"glintlint": {
|
|
44
|
+
"command": "npx",
|
|
45
|
+
"args": ["-y", "glintlint-mcp"],
|
|
46
|
+
"env": {
|
|
47
|
+
"GLINTLINT_API_KEY": "glint_your_api_key_here"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Available Tools
|
|
55
|
+
|
|
56
|
+
| Tool | Description |
|
|
57
|
+
|------|-------------|
|
|
58
|
+
| `list_tasks` | List tasks with filters (date, status, priority, group, time block) |
|
|
59
|
+
| `create_task` | Create a new task with title, date, priority, time block, etc. |
|
|
60
|
+
| `update_task` | Update any field of an existing task |
|
|
61
|
+
| `complete_task` | Mark a task as completed |
|
|
62
|
+
| `delete_task` | Permanently delete a task |
|
|
63
|
+
| `get_today_summary` | Get today's tasks grouped by time block + statistics |
|
|
64
|
+
| `list_groups` | List all task groups |
|
|
65
|
+
| `get_stats` | Get completion rate, priority distribution, overdue count |
|
|
66
|
+
|
|
67
|
+
## Usage Examples
|
|
68
|
+
|
|
69
|
+
Once configured, you can ask your AI assistant:
|
|
70
|
+
|
|
71
|
+
- "What are my tasks for today?"
|
|
72
|
+
- "Create a task: review PR tomorrow morning, high priority"
|
|
73
|
+
- "Mark the task about writing docs as complete"
|
|
74
|
+
- "Show me my task statistics for this week"
|
|
75
|
+
- "What groups do I have?"
|
|
76
|
+
|
|
77
|
+
## Environment Variables
|
|
78
|
+
|
|
79
|
+
| Variable | Required | Default | Description |
|
|
80
|
+
|----------|----------|---------|-------------|
|
|
81
|
+
| `GLINTLINT_API_KEY` | Yes | — | Your API key (starts with `glint_`) |
|
|
82
|
+
| `GLINTLINT_BASE_URL` | No | `https://glintlint.com` | API base URL |
|
|
83
|
+
|
|
84
|
+
## API Key Scopes
|
|
85
|
+
|
|
86
|
+
Your API key needs the following scopes for full functionality:
|
|
87
|
+
|
|
88
|
+
- `tasks:read` — list and view tasks
|
|
89
|
+
- `tasks:write` — create, update, delete tasks
|
|
90
|
+
- `groups:read` — list groups
|
|
91
|
+
- `stats:read` — view statistics
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
|
|
7
|
+
// src/client.ts
|
|
8
|
+
var DEFAULT_BASE_URL = "https://www.glintlint.com";
|
|
9
|
+
var GlintlintClient = class {
|
|
10
|
+
baseUrl;
|
|
11
|
+
apiKey;
|
|
12
|
+
constructor() {
|
|
13
|
+
const apiKey = process.env.GLINTLINT_API_KEY;
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"GLINTLINT_API_KEY environment variable is required. Get your API key from Glintlint Settings > API Keys."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
this.apiKey = apiKey;
|
|
20
|
+
this.baseUrl = (process.env.GLINTLINT_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
21
|
+
}
|
|
22
|
+
async request(method, path, options) {
|
|
23
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
24
|
+
if (options?.params) {
|
|
25
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
26
|
+
if (value !== void 0 && value !== "") {
|
|
27
|
+
url.searchParams.set(key, value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const headers = {
|
|
32
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
33
|
+
"Content-Type": "application/json"
|
|
34
|
+
};
|
|
35
|
+
const response = await fetch(url.toString(), {
|
|
36
|
+
method,
|
|
37
|
+
headers,
|
|
38
|
+
body: options?.body ? JSON.stringify(options.body) : void 0
|
|
39
|
+
});
|
|
40
|
+
const json = await response.json();
|
|
41
|
+
if (!response.ok || json.error) {
|
|
42
|
+
const code = json.error?.code || `HTTP_${response.status}`;
|
|
43
|
+
const message = json.error?.message || `Request failed with status ${response.status}`;
|
|
44
|
+
throw new ApiError(code, message, response.status);
|
|
45
|
+
}
|
|
46
|
+
return json.data;
|
|
47
|
+
}
|
|
48
|
+
// ── Tasks ──────────────────────────────────────────────────────────
|
|
49
|
+
async listTasks(params) {
|
|
50
|
+
return this.request("GET", "/api/v1/tasks", {
|
|
51
|
+
params
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async getTask(id) {
|
|
55
|
+
return this.request("GET", `/api/v1/tasks/${id}`);
|
|
56
|
+
}
|
|
57
|
+
async createTask(data) {
|
|
58
|
+
return this.request("POST", "/api/v1/tasks", { body: data });
|
|
59
|
+
}
|
|
60
|
+
async updateTask(id, data) {
|
|
61
|
+
return this.request("PATCH", `/api/v1/tasks/${id}`, { body: data });
|
|
62
|
+
}
|
|
63
|
+
async deleteTask(id) {
|
|
64
|
+
return this.request("DELETE", `/api/v1/tasks/${id}`);
|
|
65
|
+
}
|
|
66
|
+
// ── Groups ─────────────────────────────────────────────────────────
|
|
67
|
+
async listGroups() {
|
|
68
|
+
return this.request("GET", "/api/v1/groups");
|
|
69
|
+
}
|
|
70
|
+
// ── Stats ──────────────────────────────────────────────────────────
|
|
71
|
+
async getStats(params) {
|
|
72
|
+
return this.request("GET", "/api/v1/stats", {
|
|
73
|
+
params
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var ApiError = class extends Error {
|
|
78
|
+
constructor(code, message, status) {
|
|
79
|
+
super(message);
|
|
80
|
+
this.code = code;
|
|
81
|
+
this.status = status;
|
|
82
|
+
this.name = "ApiError";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// src/tools/tasks.ts
|
|
87
|
+
import { z } from "zod";
|
|
88
|
+
function registerTaskTools(server, client) {
|
|
89
|
+
server.tool(
|
|
90
|
+
"list_tasks",
|
|
91
|
+
"List tasks with optional filtering by date, status, priority, time block, or group",
|
|
92
|
+
{
|
|
93
|
+
date: z.string().optional().describe("Exact date filter (YYYY-MM-DD)"),
|
|
94
|
+
date_from: z.string().optional().describe("Start date filter (YYYY-MM-DD)"),
|
|
95
|
+
date_to: z.string().optional().describe("End date filter (YYYY-MM-DD)"),
|
|
96
|
+
group_id: z.string().optional().describe("Filter by group ID"),
|
|
97
|
+
is_completed: z.enum(["true", "false"]).optional().describe("Filter by completion status"),
|
|
98
|
+
priority: z.enum(["low", "medium", "high"]).optional().describe("Filter by priority"),
|
|
99
|
+
time_block: z.enum(["none", "morning", "afternoon", "evening"]).optional().describe("Filter by time block"),
|
|
100
|
+
limit: z.number().int().min(1).max(100).optional().describe("Max results (default 50, max 100)")
|
|
101
|
+
},
|
|
102
|
+
async (args) => {
|
|
103
|
+
const params = {};
|
|
104
|
+
if (args.date) params.date = args.date;
|
|
105
|
+
if (args.date_from) params.date_from = args.date_from;
|
|
106
|
+
if (args.date_to) params.date_to = args.date_to;
|
|
107
|
+
if (args.group_id) params.group_id = args.group_id;
|
|
108
|
+
if (args.is_completed) params.is_completed = args.is_completed;
|
|
109
|
+
if (args.priority) params.priority = args.priority;
|
|
110
|
+
if (args.time_block) params.time_block = args.time_block;
|
|
111
|
+
if (args.limit) params.limit = String(args.limit);
|
|
112
|
+
const data = await client.listTasks(params);
|
|
113
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
server.tool(
|
|
117
|
+
"create_task",
|
|
118
|
+
"Create a new task. Requires title and date.",
|
|
119
|
+
{
|
|
120
|
+
title: z.string().describe("Task title (max 500 chars)"),
|
|
121
|
+
date: z.string().describe("Task date (YYYY-MM-DD)"),
|
|
122
|
+
notes: z.string().optional().describe("Additional notes (max 5000 chars)"),
|
|
123
|
+
time_block: z.enum(["none", "morning", "afternoon", "evening"]).optional().describe("Time of day"),
|
|
124
|
+
priority: z.enum(["low", "medium", "high"]).optional().describe("Priority level (default: medium)"),
|
|
125
|
+
group_id: z.string().optional().describe("Group ID to assign the task to")
|
|
126
|
+
},
|
|
127
|
+
async (args) => {
|
|
128
|
+
const data = await client.createTask(args);
|
|
129
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
server.tool(
|
|
133
|
+
"update_task",
|
|
134
|
+
"Update an existing task. Only provided fields will be changed.",
|
|
135
|
+
{
|
|
136
|
+
id: z.string().describe("Task ID (UUID)"),
|
|
137
|
+
title: z.string().optional().describe("New title"),
|
|
138
|
+
date: z.string().optional().describe("New date (YYYY-MM-DD)"),
|
|
139
|
+
notes: z.string().optional().describe("New notes"),
|
|
140
|
+
time_block: z.enum(["none", "morning", "afternoon", "evening"]).optional().describe("New time block"),
|
|
141
|
+
priority: z.enum(["low", "medium", "high"]).optional().describe("New priority"),
|
|
142
|
+
group_id: z.string().nullable().optional().describe("New group ID (null to remove)"),
|
|
143
|
+
is_completed: z.boolean().optional().describe("Set completion status")
|
|
144
|
+
},
|
|
145
|
+
async (args) => {
|
|
146
|
+
const { id, ...updateData } = args;
|
|
147
|
+
const data = await client.updateTask(id, updateData);
|
|
148
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
server.tool(
|
|
152
|
+
"complete_task",
|
|
153
|
+
"Mark a task as completed (shortcut for update_task with is_completed=true)",
|
|
154
|
+
{
|
|
155
|
+
id: z.string().describe("Task ID (UUID)")
|
|
156
|
+
},
|
|
157
|
+
async (args) => {
|
|
158
|
+
const data = await client.updateTask(args.id, { is_completed: true });
|
|
159
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
server.tool(
|
|
163
|
+
"delete_task",
|
|
164
|
+
"Permanently delete a task. This cannot be undone.",
|
|
165
|
+
{
|
|
166
|
+
id: z.string().describe("Task ID (UUID)")
|
|
167
|
+
},
|
|
168
|
+
async (args) => {
|
|
169
|
+
await client.deleteTask(args.id);
|
|
170
|
+
return { content: [{ type: "text", text: "Task deleted successfully." }] };
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
server.tool(
|
|
174
|
+
"get_today_summary",
|
|
175
|
+
"Get a summary of today's tasks grouped by time block, plus overall statistics",
|
|
176
|
+
{},
|
|
177
|
+
async () => {
|
|
178
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
179
|
+
const [tasks, stats] = await Promise.all([
|
|
180
|
+
client.listTasks({ date: today, limit: "100" }),
|
|
181
|
+
client.getStats({ date_from: today, date_to: today })
|
|
182
|
+
]);
|
|
183
|
+
const taskList = Array.isArray(tasks) ? tasks : [];
|
|
184
|
+
const byTimeBlock = {
|
|
185
|
+
morning: [],
|
|
186
|
+
afternoon: [],
|
|
187
|
+
evening: [],
|
|
188
|
+
none: []
|
|
189
|
+
};
|
|
190
|
+
for (const task of taskList) {
|
|
191
|
+
const block = task.time_block || "none";
|
|
192
|
+
if (block in byTimeBlock) {
|
|
193
|
+
byTimeBlock[block].push({
|
|
194
|
+
id: task.id,
|
|
195
|
+
title: task.title,
|
|
196
|
+
priority: task.priority,
|
|
197
|
+
is_completed: task.is_completed,
|
|
198
|
+
group: task.group ? task.group.name : null,
|
|
199
|
+
notes: task.notes ? String(task.notes).substring(0, 100) : null
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const summary = {
|
|
204
|
+
date: today,
|
|
205
|
+
stats,
|
|
206
|
+
tasks_by_time_block: byTimeBlock,
|
|
207
|
+
total_today: taskList.length,
|
|
208
|
+
completed_today: taskList.filter((t) => t.is_completed).length,
|
|
209
|
+
pending_today: taskList.filter((t) => !t.is_completed).length
|
|
210
|
+
};
|
|
211
|
+
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/tools/groups.ts
|
|
217
|
+
function registerGroupTools(server, client) {
|
|
218
|
+
server.tool(
|
|
219
|
+
"list_groups",
|
|
220
|
+
"List all task groups (categories) for the user",
|
|
221
|
+
{},
|
|
222
|
+
async () => {
|
|
223
|
+
const data = await client.listGroups();
|
|
224
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/tools/stats.ts
|
|
230
|
+
import { z as z2 } from "zod";
|
|
231
|
+
function registerStatsTools(server, client) {
|
|
232
|
+
server.tool(
|
|
233
|
+
"get_stats",
|
|
234
|
+
"Get task statistics including completion rate, priority distribution, and overdue count",
|
|
235
|
+
{
|
|
236
|
+
date_from: z2.string().optional().describe("Start date (YYYY-MM-DD). Default: 30 days ago"),
|
|
237
|
+
date_to: z2.string().optional().describe("End date (YYYY-MM-DD). Default: today")
|
|
238
|
+
},
|
|
239
|
+
async (args) => {
|
|
240
|
+
const params = {};
|
|
241
|
+
if (args.date_from) params.date_from = args.date_from;
|
|
242
|
+
if (args.date_to) params.date_to = args.date_to;
|
|
243
|
+
const data = await client.getStats(params);
|
|
244
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/index.ts
|
|
250
|
+
function createServer() {
|
|
251
|
+
const server = new McpServer({
|
|
252
|
+
name: "glintlint",
|
|
253
|
+
version: "1.0.0"
|
|
254
|
+
});
|
|
255
|
+
const client = new GlintlintClient();
|
|
256
|
+
registerTaskTools(server, client);
|
|
257
|
+
registerGroupTools(server, client);
|
|
258
|
+
registerStatsTools(server, client);
|
|
259
|
+
return server;
|
|
260
|
+
}
|
|
261
|
+
async function startStdio() {
|
|
262
|
+
const server = createServer();
|
|
263
|
+
const transport = new StdioServerTransport();
|
|
264
|
+
await server.connect(transport);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/cli.ts
|
|
268
|
+
startStdio().catch((error) => {
|
|
269
|
+
console.error("Failed to start Glintlint MCP server:", error);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
});
|
|
272
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/tools/tasks.ts","../src/tools/groups.ts","../src/tools/stats.ts","../src/cli.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\r\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\"\r\nimport { GlintlintClient } from \"./client.js\"\r\nimport { registerTaskTools } from \"./tools/tasks.js\"\r\nimport { registerGroupTools } from \"./tools/groups.js\"\r\nimport { registerStatsTools } from \"./tools/stats.js\"\r\n\r\nexport function createServer(): McpServer {\r\n const server = new McpServer({\r\n name: \"glintlint\",\r\n version: \"1.0.0\",\r\n })\r\n\r\n const client = new GlintlintClient()\r\n\r\n registerTaskTools(server, client)\r\n registerGroupTools(server, client)\r\n registerStatsTools(server, client)\r\n\r\n return server\r\n}\r\n\r\nexport async function startStdio() {\r\n const server = createServer()\r\n const transport = new StdioServerTransport()\r\n await server.connect(transport)\r\n}\r\n","/**\r\n * HTTP client for Glintlint REST API.\r\n * All MCP tools delegate to this layer.\r\n */\r\n\r\nconst DEFAULT_BASE_URL = \"https://www.glintlint.com\"\r\n\r\ninterface ApiResponse<T = unknown> {\r\n data?: T\r\n meta?: { total: number; limit: number; offset: number }\r\n error?: { code: string; message: string }\r\n}\r\n\r\nexport class GlintlintClient {\r\n private baseUrl: string\r\n private apiKey: string\r\n\r\n constructor() {\r\n const apiKey = process.env.GLINTLINT_API_KEY\r\n if (!apiKey) {\r\n throw new Error(\r\n \"GLINTLINT_API_KEY environment variable is required. \" +\r\n \"Get your API key from Glintlint Settings > API Keys.\"\r\n )\r\n }\r\n this.apiKey = apiKey\r\n this.baseUrl = (process.env.GLINTLINT_BASE_URL || DEFAULT_BASE_URL).replace(/\\/$/, \"\")\r\n }\r\n\r\n private async request<T = unknown>(\r\n method: string,\r\n path: string,\r\n options?: { params?: Record<string, string>; body?: unknown }\r\n ): Promise<T> {\r\n const url = new URL(`${this.baseUrl}${path}`)\r\n if (options?.params) {\r\n for (const [key, value] of Object.entries(options.params)) {\r\n if (value !== undefined && value !== \"\") {\r\n url.searchParams.set(key, value)\r\n }\r\n }\r\n }\r\n\r\n const headers: Record<string, string> = {\r\n Authorization: `Bearer ${this.apiKey}`,\r\n \"Content-Type\": \"application/json\",\r\n }\r\n\r\n const response = await fetch(url.toString(), {\r\n method,\r\n headers,\r\n body: options?.body ? JSON.stringify(options.body) : undefined,\r\n })\r\n\r\n const json = (await response.json()) as ApiResponse<T>\r\n\r\n if (!response.ok || json.error) {\r\n const code = json.error?.code || `HTTP_${response.status}`\r\n const message = json.error?.message || `Request failed with status ${response.status}`\r\n throw new ApiError(code, message, response.status)\r\n }\r\n\r\n return json.data as T\r\n }\r\n\r\n // ── Tasks ──────────────────────────────────────────────────────────\r\n\r\n async listTasks(params?: {\r\n date?: string\r\n date_from?: string\r\n date_to?: string\r\n group_id?: string\r\n is_completed?: string\r\n priority?: string\r\n time_block?: string\r\n limit?: string\r\n offset?: string\r\n }): Promise<unknown> {\r\n return this.request(\"GET\", \"/api/v1/tasks\", {\r\n params: params as Record<string, string>,\r\n })\r\n }\r\n\r\n async getTask(id: string): Promise<unknown> {\r\n return this.request(\"GET\", `/api/v1/tasks/${id}`)\r\n }\r\n\r\n async createTask(data: {\r\n title: string\r\n date: string\r\n notes?: string\r\n time_block?: string\r\n priority?: string\r\n group_id?: string\r\n }): Promise<unknown> {\r\n return this.request(\"POST\", \"/api/v1/tasks\", { body: data })\r\n }\r\n\r\n async updateTask(\r\n id: string,\r\n data: {\r\n title?: string\r\n date?: string\r\n notes?: string\r\n time_block?: string\r\n priority?: string\r\n group_id?: string\r\n is_completed?: boolean\r\n }\r\n ): Promise<unknown> {\r\n return this.request(\"PATCH\", `/api/v1/tasks/${id}`, { body: data })\r\n }\r\n\r\n async deleteTask(id: string): Promise<unknown> {\r\n return this.request(\"DELETE\", `/api/v1/tasks/${id}`)\r\n }\r\n\r\n // ── Groups ─────────────────────────────────────────────────────────\r\n\r\n async listGroups(): Promise<unknown> {\r\n return this.request(\"GET\", \"/api/v1/groups\")\r\n }\r\n\r\n // ── Stats ──────────────────────────────────────────────────────────\r\n\r\n async getStats(params?: {\r\n date_from?: string\r\n date_to?: string\r\n }): Promise<unknown> {\r\n return this.request(\"GET\", \"/api/v1/stats\", {\r\n params: params as Record<string, string>,\r\n })\r\n }\r\n}\r\n\r\nexport class ApiError extends Error {\r\n constructor(\r\n public code: string,\r\n message: string,\r\n public status: number\r\n ) {\r\n super(message)\r\n this.name = \"ApiError\"\r\n }\r\n}\r\n","import { z } from \"zod\"\r\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\r\nimport { GlintlintClient } from \"../client.js\"\r\n\r\nexport function registerTaskTools(server: McpServer, client: GlintlintClient) {\r\n // ── list_tasks ─────────────────────────────────────────────────────\r\n server.tool(\r\n \"list_tasks\",\r\n \"List tasks with optional filtering by date, status, priority, time block, or group\",\r\n {\r\n date: z.string().optional().describe(\"Exact date filter (YYYY-MM-DD)\"),\r\n date_from: z.string().optional().describe(\"Start date filter (YYYY-MM-DD)\"),\r\n date_to: z.string().optional().describe(\"End date filter (YYYY-MM-DD)\"),\r\n group_id: z.string().optional().describe(\"Filter by group ID\"),\r\n is_completed: z.enum([\"true\", \"false\"]).optional().describe(\"Filter by completion status\"),\r\n priority: z.enum([\"low\", \"medium\", \"high\"]).optional().describe(\"Filter by priority\"),\r\n time_block: z.enum([\"none\", \"morning\", \"afternoon\", \"evening\"]).optional().describe(\"Filter by time block\"),\r\n limit: z.number().int().min(1).max(100).optional().describe(\"Max results (default 50, max 100)\"),\r\n },\r\n async (args) => {\r\n const params: Record<string, string> = {}\r\n if (args.date) params.date = args.date\r\n if (args.date_from) params.date_from = args.date_from\r\n if (args.date_to) params.date_to = args.date_to\r\n if (args.group_id) params.group_id = args.group_id\r\n if (args.is_completed) params.is_completed = args.is_completed\r\n if (args.priority) params.priority = args.priority\r\n if (args.time_block) params.time_block = args.time_block\r\n if (args.limit) params.limit = String(args.limit)\r\n\r\n const data = await client.listTasks(params)\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n\r\n // ── create_task ────────────────────────────────────────────────────\r\n server.tool(\r\n \"create_task\",\r\n \"Create a new task. Requires title and date.\",\r\n {\r\n title: z.string().describe(\"Task title (max 500 chars)\"),\r\n date: z.string().describe(\"Task date (YYYY-MM-DD)\"),\r\n notes: z.string().optional().describe(\"Additional notes (max 5000 chars)\"),\r\n time_block: z.enum([\"none\", \"morning\", \"afternoon\", \"evening\"]).optional().describe(\"Time of day\"),\r\n priority: z.enum([\"low\", \"medium\", \"high\"]).optional().describe(\"Priority level (default: medium)\"),\r\n group_id: z.string().optional().describe(\"Group ID to assign the task to\"),\r\n },\r\n async (args) => {\r\n const data = await client.createTask(args)\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n\r\n // ── update_task ────────────────────────────────────────────────────\r\n server.tool(\r\n \"update_task\",\r\n \"Update an existing task. Only provided fields will be changed.\",\r\n {\r\n id: z.string().describe(\"Task ID (UUID)\"),\r\n title: z.string().optional().describe(\"New title\"),\r\n date: z.string().optional().describe(\"New date (YYYY-MM-DD)\"),\r\n notes: z.string().optional().describe(\"New notes\"),\r\n time_block: z.enum([\"none\", \"morning\", \"afternoon\", \"evening\"]).optional().describe(\"New time block\"),\r\n priority: z.enum([\"low\", \"medium\", \"high\"]).optional().describe(\"New priority\"),\r\n group_id: z.string().nullable().optional().describe(\"New group ID (null to remove)\"),\r\n is_completed: z.boolean().optional().describe(\"Set completion status\"),\r\n },\r\n async (args) => {\r\n const { id, ...updateData } = args\r\n const data = await client.updateTask(id, updateData)\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n\r\n // ── complete_task ──────────────────────────────────────────────────\r\n server.tool(\r\n \"complete_task\",\r\n \"Mark a task as completed (shortcut for update_task with is_completed=true)\",\r\n {\r\n id: z.string().describe(\"Task ID (UUID)\"),\r\n },\r\n async (args) => {\r\n const data = await client.updateTask(args.id, { is_completed: true })\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n\r\n // ── delete_task ────────────────────────────────────────────────────\r\n server.tool(\r\n \"delete_task\",\r\n \"Permanently delete a task. This cannot be undone.\",\r\n {\r\n id: z.string().describe(\"Task ID (UUID)\"),\r\n },\r\n async (args) => {\r\n await client.deleteTask(args.id)\r\n return { content: [{ type: \"text\" as const, text: \"Task deleted successfully.\" }] }\r\n }\r\n )\r\n\r\n // ── get_today_summary ──────────────────────────────────────────────\r\n server.tool(\r\n \"get_today_summary\",\r\n \"Get a summary of today's tasks grouped by time block, plus overall statistics\",\r\n {},\r\n async () => {\r\n const today = new Date().toISOString().split(\"T\")[0]\r\n\r\n const [tasks, stats] = await Promise.all([\r\n client.listTasks({ date: today, limit: \"100\" }) as Promise<Array<Record<string, unknown>>>,\r\n client.getStats({ date_from: today, date_to: today }) as Promise<Record<string, unknown>>,\r\n ])\r\n\r\n const taskList = Array.isArray(tasks) ? tasks : []\r\n\r\n const byTimeBlock: Record<string, Array<Record<string, unknown>>> = {\r\n morning: [],\r\n afternoon: [],\r\n evening: [],\r\n none: [],\r\n }\r\n\r\n for (const task of taskList) {\r\n const block = (task.time_block as string) || \"none\"\r\n if (block in byTimeBlock) {\r\n byTimeBlock[block].push({\r\n id: task.id,\r\n title: task.title,\r\n priority: task.priority,\r\n is_completed: task.is_completed,\r\n group: task.group ? (task.group as Record<string, unknown>).name : null,\r\n notes: task.notes ? String(task.notes).substring(0, 100) : null,\r\n })\r\n }\r\n }\r\n\r\n const summary = {\r\n date: today,\r\n stats,\r\n tasks_by_time_block: byTimeBlock,\r\n total_today: taskList.length,\r\n completed_today: taskList.filter((t) => t.is_completed).length,\r\n pending_today: taskList.filter((t) => !t.is_completed).length,\r\n }\r\n\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(summary, null, 2) }] }\r\n }\r\n )\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\r\nimport { GlintlintClient } from \"../client.js\"\r\n\r\nexport function registerGroupTools(server: McpServer, client: GlintlintClient) {\r\n server.tool(\r\n \"list_groups\",\r\n \"List all task groups (categories) for the user\",\r\n {},\r\n async () => {\r\n const data = await client.listGroups()\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n}\r\n","import { z } from \"zod\"\r\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\r\nimport { GlintlintClient } from \"../client.js\"\r\n\r\nexport function registerStatsTools(server: McpServer, client: GlintlintClient) {\r\n server.tool(\r\n \"get_stats\",\r\n \"Get task statistics including completion rate, priority distribution, and overdue count\",\r\n {\r\n date_from: z.string().optional().describe(\"Start date (YYYY-MM-DD). Default: 30 days ago\"),\r\n date_to: z.string().optional().describe(\"End date (YYYY-MM-DD). Default: today\"),\r\n },\r\n async (args) => {\r\n const params: Record<string, string> = {}\r\n if (args.date_from) params.date_from = args.date_from\r\n if (args.date_to) params.date_to = args.date_to\r\n\r\n const data = await client.getStats(params)\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n}\r\n","import { startStdio } from \"./index.js\"\r\n\r\nstartStdio().catch((error) => {\r\n console.error(\"Failed to start Glintlint MCP server:\", error)\r\n process.exit(1)\r\n})\r\n"],"mappings":"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","names":["z"]}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
|
|
5
|
+
// src/client.ts
|
|
6
|
+
var DEFAULT_BASE_URL = "https://www.glintlint.com";
|
|
7
|
+
var GlintlintClient = class {
|
|
8
|
+
baseUrl;
|
|
9
|
+
apiKey;
|
|
10
|
+
constructor() {
|
|
11
|
+
const apiKey = process.env.GLINTLINT_API_KEY;
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"GLINTLINT_API_KEY environment variable is required. Get your API key from Glintlint Settings > API Keys."
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
this.apiKey = apiKey;
|
|
18
|
+
this.baseUrl = (process.env.GLINTLINT_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
19
|
+
}
|
|
20
|
+
async request(method, path, options) {
|
|
21
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
22
|
+
if (options?.params) {
|
|
23
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
24
|
+
if (value !== void 0 && value !== "") {
|
|
25
|
+
url.searchParams.set(key, value);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const headers = {
|
|
30
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
31
|
+
"Content-Type": "application/json"
|
|
32
|
+
};
|
|
33
|
+
const response = await fetch(url.toString(), {
|
|
34
|
+
method,
|
|
35
|
+
headers,
|
|
36
|
+
body: options?.body ? JSON.stringify(options.body) : void 0
|
|
37
|
+
});
|
|
38
|
+
const json = await response.json();
|
|
39
|
+
if (!response.ok || json.error) {
|
|
40
|
+
const code = json.error?.code || `HTTP_${response.status}`;
|
|
41
|
+
const message = json.error?.message || `Request failed with status ${response.status}`;
|
|
42
|
+
throw new ApiError(code, message, response.status);
|
|
43
|
+
}
|
|
44
|
+
return json.data;
|
|
45
|
+
}
|
|
46
|
+
// ── Tasks ──────────────────────────────────────────────────────────
|
|
47
|
+
async listTasks(params) {
|
|
48
|
+
return this.request("GET", "/api/v1/tasks", {
|
|
49
|
+
params
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async getTask(id) {
|
|
53
|
+
return this.request("GET", `/api/v1/tasks/${id}`);
|
|
54
|
+
}
|
|
55
|
+
async createTask(data) {
|
|
56
|
+
return this.request("POST", "/api/v1/tasks", { body: data });
|
|
57
|
+
}
|
|
58
|
+
async updateTask(id, data) {
|
|
59
|
+
return this.request("PATCH", `/api/v1/tasks/${id}`, { body: data });
|
|
60
|
+
}
|
|
61
|
+
async deleteTask(id) {
|
|
62
|
+
return this.request("DELETE", `/api/v1/tasks/${id}`);
|
|
63
|
+
}
|
|
64
|
+
// ── Groups ─────────────────────────────────────────────────────────
|
|
65
|
+
async listGroups() {
|
|
66
|
+
return this.request("GET", "/api/v1/groups");
|
|
67
|
+
}
|
|
68
|
+
// ── Stats ──────────────────────────────────────────────────────────
|
|
69
|
+
async getStats(params) {
|
|
70
|
+
return this.request("GET", "/api/v1/stats", {
|
|
71
|
+
params
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var ApiError = class extends Error {
|
|
76
|
+
constructor(code, message, status) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.code = code;
|
|
79
|
+
this.status = status;
|
|
80
|
+
this.name = "ApiError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/tools/tasks.ts
|
|
85
|
+
import { z } from "zod";
|
|
86
|
+
function registerTaskTools(server, client) {
|
|
87
|
+
server.tool(
|
|
88
|
+
"list_tasks",
|
|
89
|
+
"List tasks with optional filtering by date, status, priority, time block, or group",
|
|
90
|
+
{
|
|
91
|
+
date: z.string().optional().describe("Exact date filter (YYYY-MM-DD)"),
|
|
92
|
+
date_from: z.string().optional().describe("Start date filter (YYYY-MM-DD)"),
|
|
93
|
+
date_to: z.string().optional().describe("End date filter (YYYY-MM-DD)"),
|
|
94
|
+
group_id: z.string().optional().describe("Filter by group ID"),
|
|
95
|
+
is_completed: z.enum(["true", "false"]).optional().describe("Filter by completion status"),
|
|
96
|
+
priority: z.enum(["low", "medium", "high"]).optional().describe("Filter by priority"),
|
|
97
|
+
time_block: z.enum(["none", "morning", "afternoon", "evening"]).optional().describe("Filter by time block"),
|
|
98
|
+
limit: z.number().int().min(1).max(100).optional().describe("Max results (default 50, max 100)")
|
|
99
|
+
},
|
|
100
|
+
async (args) => {
|
|
101
|
+
const params = {};
|
|
102
|
+
if (args.date) params.date = args.date;
|
|
103
|
+
if (args.date_from) params.date_from = args.date_from;
|
|
104
|
+
if (args.date_to) params.date_to = args.date_to;
|
|
105
|
+
if (args.group_id) params.group_id = args.group_id;
|
|
106
|
+
if (args.is_completed) params.is_completed = args.is_completed;
|
|
107
|
+
if (args.priority) params.priority = args.priority;
|
|
108
|
+
if (args.time_block) params.time_block = args.time_block;
|
|
109
|
+
if (args.limit) params.limit = String(args.limit);
|
|
110
|
+
const data = await client.listTasks(params);
|
|
111
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
server.tool(
|
|
115
|
+
"create_task",
|
|
116
|
+
"Create a new task. Requires title and date.",
|
|
117
|
+
{
|
|
118
|
+
title: z.string().describe("Task title (max 500 chars)"),
|
|
119
|
+
date: z.string().describe("Task date (YYYY-MM-DD)"),
|
|
120
|
+
notes: z.string().optional().describe("Additional notes (max 5000 chars)"),
|
|
121
|
+
time_block: z.enum(["none", "morning", "afternoon", "evening"]).optional().describe("Time of day"),
|
|
122
|
+
priority: z.enum(["low", "medium", "high"]).optional().describe("Priority level (default: medium)"),
|
|
123
|
+
group_id: z.string().optional().describe("Group ID to assign the task to")
|
|
124
|
+
},
|
|
125
|
+
async (args) => {
|
|
126
|
+
const data = await client.createTask(args);
|
|
127
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
server.tool(
|
|
131
|
+
"update_task",
|
|
132
|
+
"Update an existing task. Only provided fields will be changed.",
|
|
133
|
+
{
|
|
134
|
+
id: z.string().describe("Task ID (UUID)"),
|
|
135
|
+
title: z.string().optional().describe("New title"),
|
|
136
|
+
date: z.string().optional().describe("New date (YYYY-MM-DD)"),
|
|
137
|
+
notes: z.string().optional().describe("New notes"),
|
|
138
|
+
time_block: z.enum(["none", "morning", "afternoon", "evening"]).optional().describe("New time block"),
|
|
139
|
+
priority: z.enum(["low", "medium", "high"]).optional().describe("New priority"),
|
|
140
|
+
group_id: z.string().nullable().optional().describe("New group ID (null to remove)"),
|
|
141
|
+
is_completed: z.boolean().optional().describe("Set completion status")
|
|
142
|
+
},
|
|
143
|
+
async (args) => {
|
|
144
|
+
const { id, ...updateData } = args;
|
|
145
|
+
const data = await client.updateTask(id, updateData);
|
|
146
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
server.tool(
|
|
150
|
+
"complete_task",
|
|
151
|
+
"Mark a task as completed (shortcut for update_task with is_completed=true)",
|
|
152
|
+
{
|
|
153
|
+
id: z.string().describe("Task ID (UUID)")
|
|
154
|
+
},
|
|
155
|
+
async (args) => {
|
|
156
|
+
const data = await client.updateTask(args.id, { is_completed: true });
|
|
157
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
server.tool(
|
|
161
|
+
"delete_task",
|
|
162
|
+
"Permanently delete a task. This cannot be undone.",
|
|
163
|
+
{
|
|
164
|
+
id: z.string().describe("Task ID (UUID)")
|
|
165
|
+
},
|
|
166
|
+
async (args) => {
|
|
167
|
+
await client.deleteTask(args.id);
|
|
168
|
+
return { content: [{ type: "text", text: "Task deleted successfully." }] };
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
server.tool(
|
|
172
|
+
"get_today_summary",
|
|
173
|
+
"Get a summary of today's tasks grouped by time block, plus overall statistics",
|
|
174
|
+
{},
|
|
175
|
+
async () => {
|
|
176
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
177
|
+
const [tasks, stats] = await Promise.all([
|
|
178
|
+
client.listTasks({ date: today, limit: "100" }),
|
|
179
|
+
client.getStats({ date_from: today, date_to: today })
|
|
180
|
+
]);
|
|
181
|
+
const taskList = Array.isArray(tasks) ? tasks : [];
|
|
182
|
+
const byTimeBlock = {
|
|
183
|
+
morning: [],
|
|
184
|
+
afternoon: [],
|
|
185
|
+
evening: [],
|
|
186
|
+
none: []
|
|
187
|
+
};
|
|
188
|
+
for (const task of taskList) {
|
|
189
|
+
const block = task.time_block || "none";
|
|
190
|
+
if (block in byTimeBlock) {
|
|
191
|
+
byTimeBlock[block].push({
|
|
192
|
+
id: task.id,
|
|
193
|
+
title: task.title,
|
|
194
|
+
priority: task.priority,
|
|
195
|
+
is_completed: task.is_completed,
|
|
196
|
+
group: task.group ? task.group.name : null,
|
|
197
|
+
notes: task.notes ? String(task.notes).substring(0, 100) : null
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const summary = {
|
|
202
|
+
date: today,
|
|
203
|
+
stats,
|
|
204
|
+
tasks_by_time_block: byTimeBlock,
|
|
205
|
+
total_today: taskList.length,
|
|
206
|
+
completed_today: taskList.filter((t) => t.is_completed).length,
|
|
207
|
+
pending_today: taskList.filter((t) => !t.is_completed).length
|
|
208
|
+
};
|
|
209
|
+
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/tools/groups.ts
|
|
215
|
+
function registerGroupTools(server, client) {
|
|
216
|
+
server.tool(
|
|
217
|
+
"list_groups",
|
|
218
|
+
"List all task groups (categories) for the user",
|
|
219
|
+
{},
|
|
220
|
+
async () => {
|
|
221
|
+
const data = await client.listGroups();
|
|
222
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/tools/stats.ts
|
|
228
|
+
import { z as z2 } from "zod";
|
|
229
|
+
function registerStatsTools(server, client) {
|
|
230
|
+
server.tool(
|
|
231
|
+
"get_stats",
|
|
232
|
+
"Get task statistics including completion rate, priority distribution, and overdue count",
|
|
233
|
+
{
|
|
234
|
+
date_from: z2.string().optional().describe("Start date (YYYY-MM-DD). Default: 30 days ago"),
|
|
235
|
+
date_to: z2.string().optional().describe("End date (YYYY-MM-DD). Default: today")
|
|
236
|
+
},
|
|
237
|
+
async (args) => {
|
|
238
|
+
const params = {};
|
|
239
|
+
if (args.date_from) params.date_from = args.date_from;
|
|
240
|
+
if (args.date_to) params.date_to = args.date_to;
|
|
241
|
+
const data = await client.getStats(params);
|
|
242
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/index.ts
|
|
248
|
+
function createServer() {
|
|
249
|
+
const server = new McpServer({
|
|
250
|
+
name: "glintlint",
|
|
251
|
+
version: "1.0.0"
|
|
252
|
+
});
|
|
253
|
+
const client = new GlintlintClient();
|
|
254
|
+
registerTaskTools(server, client);
|
|
255
|
+
registerGroupTools(server, client);
|
|
256
|
+
registerStatsTools(server, client);
|
|
257
|
+
return server;
|
|
258
|
+
}
|
|
259
|
+
async function startStdio() {
|
|
260
|
+
const server = createServer();
|
|
261
|
+
const transport = new StdioServerTransport();
|
|
262
|
+
await server.connect(transport);
|
|
263
|
+
}
|
|
264
|
+
export {
|
|
265
|
+
createServer,
|
|
266
|
+
startStdio
|
|
267
|
+
};
|
|
268
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/tools/tasks.ts","../src/tools/groups.ts","../src/tools/stats.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\r\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\"\r\nimport { GlintlintClient } from \"./client.js\"\r\nimport { registerTaskTools } from \"./tools/tasks.js\"\r\nimport { registerGroupTools } from \"./tools/groups.js\"\r\nimport { registerStatsTools } from \"./tools/stats.js\"\r\n\r\nexport function createServer(): McpServer {\r\n const server = new McpServer({\r\n name: \"glintlint\",\r\n version: \"1.0.0\",\r\n })\r\n\r\n const client = new GlintlintClient()\r\n\r\n registerTaskTools(server, client)\r\n registerGroupTools(server, client)\r\n registerStatsTools(server, client)\r\n\r\n return server\r\n}\r\n\r\nexport async function startStdio() {\r\n const server = createServer()\r\n const transport = new StdioServerTransport()\r\n await server.connect(transport)\r\n}\r\n","/**\r\n * HTTP client for Glintlint REST API.\r\n * All MCP tools delegate to this layer.\r\n */\r\n\r\nconst DEFAULT_BASE_URL = \"https://www.glintlint.com\"\r\n\r\ninterface ApiResponse<T = unknown> {\r\n data?: T\r\n meta?: { total: number; limit: number; offset: number }\r\n error?: { code: string; message: string }\r\n}\r\n\r\nexport class GlintlintClient {\r\n private baseUrl: string\r\n private apiKey: string\r\n\r\n constructor() {\r\n const apiKey = process.env.GLINTLINT_API_KEY\r\n if (!apiKey) {\r\n throw new Error(\r\n \"GLINTLINT_API_KEY environment variable is required. \" +\r\n \"Get your API key from Glintlint Settings > API Keys.\"\r\n )\r\n }\r\n this.apiKey = apiKey\r\n this.baseUrl = (process.env.GLINTLINT_BASE_URL || DEFAULT_BASE_URL).replace(/\\/$/, \"\")\r\n }\r\n\r\n private async request<T = unknown>(\r\n method: string,\r\n path: string,\r\n options?: { params?: Record<string, string>; body?: unknown }\r\n ): Promise<T> {\r\n const url = new URL(`${this.baseUrl}${path}`)\r\n if (options?.params) {\r\n for (const [key, value] of Object.entries(options.params)) {\r\n if (value !== undefined && value !== \"\") {\r\n url.searchParams.set(key, value)\r\n }\r\n }\r\n }\r\n\r\n const headers: Record<string, string> = {\r\n Authorization: `Bearer ${this.apiKey}`,\r\n \"Content-Type\": \"application/json\",\r\n }\r\n\r\n const response = await fetch(url.toString(), {\r\n method,\r\n headers,\r\n body: options?.body ? JSON.stringify(options.body) : undefined,\r\n })\r\n\r\n const json = (await response.json()) as ApiResponse<T>\r\n\r\n if (!response.ok || json.error) {\r\n const code = json.error?.code || `HTTP_${response.status}`\r\n const message = json.error?.message || `Request failed with status ${response.status}`\r\n throw new ApiError(code, message, response.status)\r\n }\r\n\r\n return json.data as T\r\n }\r\n\r\n // ── Tasks ──────────────────────────────────────────────────────────\r\n\r\n async listTasks(params?: {\r\n date?: string\r\n date_from?: string\r\n date_to?: string\r\n group_id?: string\r\n is_completed?: string\r\n priority?: string\r\n time_block?: string\r\n limit?: string\r\n offset?: string\r\n }): Promise<unknown> {\r\n return this.request(\"GET\", \"/api/v1/tasks\", {\r\n params: params as Record<string, string>,\r\n })\r\n }\r\n\r\n async getTask(id: string): Promise<unknown> {\r\n return this.request(\"GET\", `/api/v1/tasks/${id}`)\r\n }\r\n\r\n async createTask(data: {\r\n title: string\r\n date: string\r\n notes?: string\r\n time_block?: string\r\n priority?: string\r\n group_id?: string\r\n }): Promise<unknown> {\r\n return this.request(\"POST\", \"/api/v1/tasks\", { body: data })\r\n }\r\n\r\n async updateTask(\r\n id: string,\r\n data: {\r\n title?: string\r\n date?: string\r\n notes?: string\r\n time_block?: string\r\n priority?: string\r\n group_id?: string\r\n is_completed?: boolean\r\n }\r\n ): Promise<unknown> {\r\n return this.request(\"PATCH\", `/api/v1/tasks/${id}`, { body: data })\r\n }\r\n\r\n async deleteTask(id: string): Promise<unknown> {\r\n return this.request(\"DELETE\", `/api/v1/tasks/${id}`)\r\n }\r\n\r\n // ── Groups ─────────────────────────────────────────────────────────\r\n\r\n async listGroups(): Promise<unknown> {\r\n return this.request(\"GET\", \"/api/v1/groups\")\r\n }\r\n\r\n // ── Stats ──────────────────────────────────────────────────────────\r\n\r\n async getStats(params?: {\r\n date_from?: string\r\n date_to?: string\r\n }): Promise<unknown> {\r\n return this.request(\"GET\", \"/api/v1/stats\", {\r\n params: params as Record<string, string>,\r\n })\r\n }\r\n}\r\n\r\nexport class ApiError extends Error {\r\n constructor(\r\n public code: string,\r\n message: string,\r\n public status: number\r\n ) {\r\n super(message)\r\n this.name = \"ApiError\"\r\n }\r\n}\r\n","import { z } from \"zod\"\r\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\r\nimport { GlintlintClient } from \"../client.js\"\r\n\r\nexport function registerTaskTools(server: McpServer, client: GlintlintClient) {\r\n // ── list_tasks ─────────────────────────────────────────────────────\r\n server.tool(\r\n \"list_tasks\",\r\n \"List tasks with optional filtering by date, status, priority, time block, or group\",\r\n {\r\n date: z.string().optional().describe(\"Exact date filter (YYYY-MM-DD)\"),\r\n date_from: z.string().optional().describe(\"Start date filter (YYYY-MM-DD)\"),\r\n date_to: z.string().optional().describe(\"End date filter (YYYY-MM-DD)\"),\r\n group_id: z.string().optional().describe(\"Filter by group ID\"),\r\n is_completed: z.enum([\"true\", \"false\"]).optional().describe(\"Filter by completion status\"),\r\n priority: z.enum([\"low\", \"medium\", \"high\"]).optional().describe(\"Filter by priority\"),\r\n time_block: z.enum([\"none\", \"morning\", \"afternoon\", \"evening\"]).optional().describe(\"Filter by time block\"),\r\n limit: z.number().int().min(1).max(100).optional().describe(\"Max results (default 50, max 100)\"),\r\n },\r\n async (args) => {\r\n const params: Record<string, string> = {}\r\n if (args.date) params.date = args.date\r\n if (args.date_from) params.date_from = args.date_from\r\n if (args.date_to) params.date_to = args.date_to\r\n if (args.group_id) params.group_id = args.group_id\r\n if (args.is_completed) params.is_completed = args.is_completed\r\n if (args.priority) params.priority = args.priority\r\n if (args.time_block) params.time_block = args.time_block\r\n if (args.limit) params.limit = String(args.limit)\r\n\r\n const data = await client.listTasks(params)\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n\r\n // ── create_task ────────────────────────────────────────────────────\r\n server.tool(\r\n \"create_task\",\r\n \"Create a new task. Requires title and date.\",\r\n {\r\n title: z.string().describe(\"Task title (max 500 chars)\"),\r\n date: z.string().describe(\"Task date (YYYY-MM-DD)\"),\r\n notes: z.string().optional().describe(\"Additional notes (max 5000 chars)\"),\r\n time_block: z.enum([\"none\", \"morning\", \"afternoon\", \"evening\"]).optional().describe(\"Time of day\"),\r\n priority: z.enum([\"low\", \"medium\", \"high\"]).optional().describe(\"Priority level (default: medium)\"),\r\n group_id: z.string().optional().describe(\"Group ID to assign the task to\"),\r\n },\r\n async (args) => {\r\n const data = await client.createTask(args)\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n\r\n // ── update_task ────────────────────────────────────────────────────\r\n server.tool(\r\n \"update_task\",\r\n \"Update an existing task. Only provided fields will be changed.\",\r\n {\r\n id: z.string().describe(\"Task ID (UUID)\"),\r\n title: z.string().optional().describe(\"New title\"),\r\n date: z.string().optional().describe(\"New date (YYYY-MM-DD)\"),\r\n notes: z.string().optional().describe(\"New notes\"),\r\n time_block: z.enum([\"none\", \"morning\", \"afternoon\", \"evening\"]).optional().describe(\"New time block\"),\r\n priority: z.enum([\"low\", \"medium\", \"high\"]).optional().describe(\"New priority\"),\r\n group_id: z.string().nullable().optional().describe(\"New group ID (null to remove)\"),\r\n is_completed: z.boolean().optional().describe(\"Set completion status\"),\r\n },\r\n async (args) => {\r\n const { id, ...updateData } = args\r\n const data = await client.updateTask(id, updateData)\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n\r\n // ── complete_task ──────────────────────────────────────────────────\r\n server.tool(\r\n \"complete_task\",\r\n \"Mark a task as completed (shortcut for update_task with is_completed=true)\",\r\n {\r\n id: z.string().describe(\"Task ID (UUID)\"),\r\n },\r\n async (args) => {\r\n const data = await client.updateTask(args.id, { is_completed: true })\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n\r\n // ── delete_task ────────────────────────────────────────────────────\r\n server.tool(\r\n \"delete_task\",\r\n \"Permanently delete a task. This cannot be undone.\",\r\n {\r\n id: z.string().describe(\"Task ID (UUID)\"),\r\n },\r\n async (args) => {\r\n await client.deleteTask(args.id)\r\n return { content: [{ type: \"text\" as const, text: \"Task deleted successfully.\" }] }\r\n }\r\n )\r\n\r\n // ── get_today_summary ──────────────────────────────────────────────\r\n server.tool(\r\n \"get_today_summary\",\r\n \"Get a summary of today's tasks grouped by time block, plus overall statistics\",\r\n {},\r\n async () => {\r\n const today = new Date().toISOString().split(\"T\")[0]\r\n\r\n const [tasks, stats] = await Promise.all([\r\n client.listTasks({ date: today, limit: \"100\" }) as Promise<Array<Record<string, unknown>>>,\r\n client.getStats({ date_from: today, date_to: today }) as Promise<Record<string, unknown>>,\r\n ])\r\n\r\n const taskList = Array.isArray(tasks) ? tasks : []\r\n\r\n const byTimeBlock: Record<string, Array<Record<string, unknown>>> = {\r\n morning: [],\r\n afternoon: [],\r\n evening: [],\r\n none: [],\r\n }\r\n\r\n for (const task of taskList) {\r\n const block = (task.time_block as string) || \"none\"\r\n if (block in byTimeBlock) {\r\n byTimeBlock[block].push({\r\n id: task.id,\r\n title: task.title,\r\n priority: task.priority,\r\n is_completed: task.is_completed,\r\n group: task.group ? (task.group as Record<string, unknown>).name : null,\r\n notes: task.notes ? String(task.notes).substring(0, 100) : null,\r\n })\r\n }\r\n }\r\n\r\n const summary = {\r\n date: today,\r\n stats,\r\n tasks_by_time_block: byTimeBlock,\r\n total_today: taskList.length,\r\n completed_today: taskList.filter((t) => t.is_completed).length,\r\n pending_today: taskList.filter((t) => !t.is_completed).length,\r\n }\r\n\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(summary, null, 2) }] }\r\n }\r\n )\r\n}\r\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\r\nimport { GlintlintClient } from \"../client.js\"\r\n\r\nexport function registerGroupTools(server: McpServer, client: GlintlintClient) {\r\n server.tool(\r\n \"list_groups\",\r\n \"List all task groups (categories) for the user\",\r\n {},\r\n async () => {\r\n const data = await client.listGroups()\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n}\r\n","import { z } from \"zod\"\r\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\r\nimport { GlintlintClient } from \"../client.js\"\r\n\r\nexport function registerStatsTools(server: McpServer, client: GlintlintClient) {\r\n server.tool(\r\n \"get_stats\",\r\n \"Get task statistics including completion rate, priority distribution, and overdue count\",\r\n {\r\n date_from: z.string().optional().describe(\"Start date (YYYY-MM-DD). Default: 30 days ago\"),\r\n date_to: z.string().optional().describe(\"End date (YYYY-MM-DD). Default: today\"),\r\n },\r\n async (args) => {\r\n const params: Record<string, string> = {}\r\n if (args.date_from) params.date_from = args.date_from\r\n if (args.date_to) params.date_to = args.date_to\r\n\r\n const data = await client.getStats(params)\r\n return { content: [{ type: \"text\" as const, text: JSON.stringify(data, null, 2) }] }\r\n }\r\n )\r\n}\r\n"],"mappings":"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","names":["z"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "glintlint-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP Server for Glintlint - manage your tasks with AI assistants",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"glintlint-mcp": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"start": "node dist/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"glintlint",
|
|
21
|
+
"task-management",
|
|
22
|
+
"ai",
|
|
23
|
+
"claude"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
28
|
+
"zod": "^3.25.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.0.0",
|
|
32
|
+
"tsup": "^8.0.0",
|
|
33
|
+
"typescript": "^5.7.0"
|
|
34
|
+
}
|
|
35
|
+
}
|