thread-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 +674 -0
- package/README.md +497 -0
- package/dist/formatters/index.d.ts +5 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +12 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/json.d.ts +3 -0
- package/dist/formatters/json.d.ts.map +1 -0
- package/dist/formatters/json.js +25 -0
- package/dist/formatters/json.js.map +1 -0
- package/dist/formatters/markdown.d.ts +3 -0
- package/dist/formatters/markdown.d.ts.map +1 -0
- package/dist/formatters/markdown.js +134 -0
- package/dist/formatters/markdown.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +175 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +3 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local.d.ts +23 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +126 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/remote.d.ts +15 -0
- package/dist/storage/remote.d.ts.map +1 -0
- package/dist/storage/remote.js +91 -0
- package/dist/storage/remote.js.map +1 -0
- package/dist/tools/delete-thread.d.ts +71 -0
- package/dist/tools/delete-thread.d.ts.map +1 -0
- package/dist/tools/delete-thread.js +74 -0
- package/dist/tools/delete-thread.js.map +1 -0
- package/dist/tools/find-threads.d.ts +175 -0
- package/dist/tools/find-threads.d.ts.map +1 -0
- package/dist/tools/find-threads.js +265 -0
- package/dist/tools/find-threads.js.map +1 -0
- package/dist/tools/index.d.ts +270 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +18 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/resume-thread.d.ts +138 -0
- package/dist/tools/resume-thread.d.ts.map +1 -0
- package/dist/tools/resume-thread.js +191 -0
- package/dist/tools/resume-thread.js.map +1 -0
- package/dist/tools/save-thread.d.ts +155 -0
- package/dist/tools/save-thread.d.ts.map +1 -0
- package/dist/tools/save-thread.js +116 -0
- package/dist/tools/save-thread.js.map +1 -0
- package/dist/tools/update-thread.d.ts +180 -0
- package/dist/tools/update-thread.d.ts.map +1 -0
- package/dist/tools/update-thread.js +159 -0
- package/dist/tools/update-thread.js.map +1 -0
- package/dist/types.d.ts +177 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +41 -0
- package/dist/types.js.map +1 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
# Thread MCP
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server for saving AI conversation threads to local files or remote servers. This tool enables you to preserve, update, search, and resume your conversations with AI applications like Claude, ChatGPT, and others.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Unified Saving**: Store conversations locally (Markdown/JSON) or remotely via REST API
|
|
8
|
+
- **Smart Search**: Find threads by ID, title, tags, or full-text search with relevance scoring
|
|
9
|
+
- **Easy Updates**: Append messages by ID or title - no need to track IDs manually
|
|
10
|
+
- **Resume Conversations**: Load previous threads with context optimized for AI continuation
|
|
11
|
+
- **Rich Metadata**: Include timestamps, tags, summaries, and source application info
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install thread-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install from source:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git clone https://github.com/your-username/thread-mcp.git
|
|
23
|
+
cd thread-mcp
|
|
24
|
+
npm install
|
|
25
|
+
npm run build
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Configure with Claude Desktop
|
|
31
|
+
|
|
32
|
+
Add to your Claude Desktop configuration file:
|
|
33
|
+
|
|
34
|
+
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
35
|
+
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"thread-mcp": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["thread-mcp"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or if installed from source:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"thread-mcp": {
|
|
54
|
+
"command": "node",
|
|
55
|
+
"args": ["/path/to/thread-mcp/dist/index.js"]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Available Tools
|
|
62
|
+
|
|
63
|
+
### `save_thread`
|
|
64
|
+
|
|
65
|
+
Save a new conversation thread to local storage or a remote server.
|
|
66
|
+
|
|
67
|
+
**Parameters:**
|
|
68
|
+
| Parameter | Type | Required | Default | Description |
|
|
69
|
+
|-----------|------|----------|---------|-------------|
|
|
70
|
+
| `title` | string | Yes | - | Title for the thread |
|
|
71
|
+
| `messages` | array | Yes | - | Array of messages with `role` and `content` |
|
|
72
|
+
| `destination` | string | No | `"local"` | Where to save: `"local"` or `"remote"` |
|
|
73
|
+
| `format` | string | No | `"markdown"` | Output format: `"markdown"` or `"json"` |
|
|
74
|
+
| `sourceApp` | string | No | - | Name of the AI application |
|
|
75
|
+
| `tags` | string[] | No | - | Tags for categorization |
|
|
76
|
+
| `summary` | string | No | - | Summary of the conversation |
|
|
77
|
+
| `outputDir` | string | No | `~/.thread-mcp` | Custom directory for local storage |
|
|
78
|
+
| `remoteUrl` | string | Conditional | - | Required when destination is `"remote"` |
|
|
79
|
+
| `apiKey` | string | No | - | API key for remote authentication |
|
|
80
|
+
| `headers` | object | No | - | Additional HTTP headers for remote |
|
|
81
|
+
|
|
82
|
+
**Example:**
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"title": "Code Review Discussion",
|
|
87
|
+
"messages": [
|
|
88
|
+
{ "role": "user", "content": "Can you review this Python function?" },
|
|
89
|
+
{ "role": "assistant", "content": "Sure! Here are my suggestions..." }
|
|
90
|
+
],
|
|
91
|
+
"sourceApp": "Claude",
|
|
92
|
+
"tags": ["code-review", "python"],
|
|
93
|
+
"format": "markdown"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### `find_threads`
|
|
100
|
+
|
|
101
|
+
Find saved threads by ID, title, search query, or list all threads. Supports filtering and relevance scoring.
|
|
102
|
+
|
|
103
|
+
**Parameters:**
|
|
104
|
+
| Parameter | Type | Required | Default | Description |
|
|
105
|
+
|-----------|------|----------|---------|-------------|
|
|
106
|
+
| `id` | string | No | - | Get a specific thread by ID (returns full details) |
|
|
107
|
+
| `title` | string | No | - | Find thread by exact title match |
|
|
108
|
+
| `query` | string | No | - | Search in titles, summaries, and content |
|
|
109
|
+
| `tags` | string[] | No | - | Filter by tags (must have ALL specified) |
|
|
110
|
+
| `sourceApp` | string | No | - | Filter by source application |
|
|
111
|
+
| `dateFrom` | string | No | - | Filter by creation date (ISO format) |
|
|
112
|
+
| `dateTo` | string | No | - | Filter by creation date (ISO format) |
|
|
113
|
+
| `includeMessages` | boolean | No | `false` | Include full message content |
|
|
114
|
+
| `limit` | number | No | `50` | Maximum results to return |
|
|
115
|
+
| `source` | string | No | `"local"` | Source: `"local"` or `"remote"` |
|
|
116
|
+
| `outputDir` | string | No | `~/.thread-mcp` | Directory for local storage |
|
|
117
|
+
| `remoteUrl` | string | Conditional | - | Required when source is `"remote"` |
|
|
118
|
+
|
|
119
|
+
**Examples:**
|
|
120
|
+
|
|
121
|
+
List all threads:
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"source": "local"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Find by ID:
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"id": "abc123-def456"
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Search with filters:
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"query": "Python debugging",
|
|
139
|
+
"tags": ["code"],
|
|
140
|
+
"includeMessages": true,
|
|
141
|
+
"limit": 5
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Response includes relevance metadata:**
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"totalResults": 3,
|
|
150
|
+
"threads": [
|
|
151
|
+
{
|
|
152
|
+
"id": "abc123",
|
|
153
|
+
"title": "Python Debugging Session",
|
|
154
|
+
"summary": "Discussion about debugging techniques",
|
|
155
|
+
"tags": ["python", "debugging"],
|
|
156
|
+
"messageCount": 12,
|
|
157
|
+
"createdAt": "2024-01-15T10:00:00.000Z",
|
|
158
|
+
"relevance": {
|
|
159
|
+
"score": 85,
|
|
160
|
+
"matchedFields": ["title", "content"],
|
|
161
|
+
"topicHints": ["python", "debugging", "error handling"]
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### `update_thread`
|
|
171
|
+
|
|
172
|
+
Update an existing thread by appending new messages. Find thread by ID or title.
|
|
173
|
+
|
|
174
|
+
**Parameters:**
|
|
175
|
+
| Parameter | Type | Required | Default | Description |
|
|
176
|
+
|-----------|------|----------|---------|-------------|
|
|
177
|
+
| `id` | string | Conditional | - | Thread ID (use this OR title) |
|
|
178
|
+
| `title` | string | Conditional | - | Find thread by exact title (use this OR id) |
|
|
179
|
+
| `messages` | array | Yes | - | New messages to add |
|
|
180
|
+
| `mode` | string | No | `"append"` | `"append"` to add messages, `"replace"` to overwrite |
|
|
181
|
+
| `deduplicateMessages` | boolean | No | `true` | Skip duplicate messages |
|
|
182
|
+
| `newTitle` | string | No | - | Update the thread title |
|
|
183
|
+
| `tags` | string[] | No | - | Update tags |
|
|
184
|
+
| `summary` | string | No | - | Update summary |
|
|
185
|
+
| `source` | string | No | `"local"` | Source: `"local"` or `"remote"` |
|
|
186
|
+
| `outputDir` | string | No | `~/.thread-mcp` | Directory for local storage |
|
|
187
|
+
| `remoteUrl` | string | Conditional | - | Required when source is `"remote"` |
|
|
188
|
+
|
|
189
|
+
**Example - Append by title:**
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"title": "Code Review Discussion",
|
|
194
|
+
"messages": [
|
|
195
|
+
{ "role": "user", "content": "What about error handling?" },
|
|
196
|
+
{ "role": "assistant", "content": "Good point! You should..." }
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Example - Update by ID with new metadata:**
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"id": "abc123-def456",
|
|
206
|
+
"messages": [
|
|
207
|
+
{ "role": "user", "content": "Follow-up question..." }
|
|
208
|
+
],
|
|
209
|
+
"tags": ["code-review", "python", "error-handling"],
|
|
210
|
+
"summary": "Extended discussion including error handling"
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### `delete_thread`
|
|
217
|
+
|
|
218
|
+
Delete a saved thread by ID or title.
|
|
219
|
+
|
|
220
|
+
**Parameters:**
|
|
221
|
+
| Parameter | Type | Required | Default | Description |
|
|
222
|
+
|-----------|------|----------|---------|-------------|
|
|
223
|
+
| `id` | string | Conditional | - | Thread ID (use this OR title) |
|
|
224
|
+
| `title` | string | Conditional | - | Find thread by exact title (use this OR id) |
|
|
225
|
+
| `source` | string | No | `"local"` | Source: `"local"` or `"remote"` |
|
|
226
|
+
| `outputDir` | string | No | `~/.thread-mcp` | Directory for local storage |
|
|
227
|
+
| `remoteUrl` | string | Conditional | - | Required when source is `"remote"` |
|
|
228
|
+
|
|
229
|
+
**Examples:**
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"id": "abc123-def456"
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"title": "Old Discussion to Remove"
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### `resume_thread`
|
|
246
|
+
|
|
247
|
+
Load a saved thread to continue the conversation. Returns context optimized for AI continuation.
|
|
248
|
+
|
|
249
|
+
**Parameters:**
|
|
250
|
+
| Parameter | Type | Required | Default | Description |
|
|
251
|
+
|-----------|------|----------|---------|-------------|
|
|
252
|
+
| `id` | string | Conditional | - | Thread ID |
|
|
253
|
+
| `title` | string | Conditional | - | Find by exact title match |
|
|
254
|
+
| `titleContains` | string | Conditional | - | Find most recent thread with title containing this |
|
|
255
|
+
| `format` | string | No | `"structured"` | Output format (see below) |
|
|
256
|
+
| `maxMessages` | number | No | all | Limit to last N messages |
|
|
257
|
+
| `includeSummary` | boolean | No | `true` | Include thread summary |
|
|
258
|
+
| `source` | string | No | `"local"` | Source: `"local"` or `"remote"` |
|
|
259
|
+
| `outputDir` | string | No | `~/.thread-mcp` | Directory for local storage |
|
|
260
|
+
| `remoteUrl` | string | Conditional | - | Required when source is `"remote"` |
|
|
261
|
+
|
|
262
|
+
**Output Formats:**
|
|
263
|
+
|
|
264
|
+
- `"structured"` - Organized context with metadata, messages, and continuation hints
|
|
265
|
+
- `"narrative"` - Human-readable summary suitable for context injection
|
|
266
|
+
- `"messages"` - Raw message array only
|
|
267
|
+
|
|
268
|
+
**Example - Resume by title:**
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"title": "Code Review Discussion",
|
|
273
|
+
"format": "structured",
|
|
274
|
+
"maxMessages": 10
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Structured Response:**
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"found": true,
|
|
283
|
+
"id": "abc123",
|
|
284
|
+
"format": "structured",
|
|
285
|
+
"context": {
|
|
286
|
+
"title": "Code Review Discussion",
|
|
287
|
+
"summary": "Discussion about Python best practices",
|
|
288
|
+
"tags": ["code-review", "python"],
|
|
289
|
+
"messageCount": 15,
|
|
290
|
+
"startedAt": "2024-01-15T10:00:00.000Z"
|
|
291
|
+
},
|
|
292
|
+
"messages": [...],
|
|
293
|
+
"continuationHint": "The assistant last responded. The user may have follow-up questions.",
|
|
294
|
+
"totalMessages": 15
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Typical Workflow
|
|
299
|
+
|
|
300
|
+
1. **Save a new thread** after an important conversation:
|
|
301
|
+
```json
|
|
302
|
+
{ "title": "Project Planning", "messages": [...], "tags": ["planning"] }
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
2. **Continue later** - find and resume:
|
|
306
|
+
```json
|
|
307
|
+
{ "title": "Project Planning" } // resume_thread
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
3. **Add new messages** as the conversation continues:
|
|
311
|
+
```json
|
|
312
|
+
{ "title": "Project Planning", "messages": [new messages...] } // update_thread
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
4. **Search across threads** to find relevant context:
|
|
316
|
+
```json
|
|
317
|
+
{ "query": "database schema", "tags": ["planning"] } // find_threads
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Output Formats
|
|
321
|
+
|
|
322
|
+
### Markdown
|
|
323
|
+
|
|
324
|
+
Produces a human-readable Markdown file with YAML frontmatter:
|
|
325
|
+
|
|
326
|
+
```markdown
|
|
327
|
+
---
|
|
328
|
+
id: abc123-def456
|
|
329
|
+
title: "Code Review Discussion"
|
|
330
|
+
created_at: 2024-01-15T10:00:00.000Z
|
|
331
|
+
source_app: Claude
|
|
332
|
+
tags: ["code-review", "python"]
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
# Code Review Discussion
|
|
336
|
+
|
|
337
|
+
> A discussion about Python best practices
|
|
338
|
+
|
|
339
|
+
## Conversation
|
|
340
|
+
|
|
341
|
+
### User _(1/15/2024, 10:00:00 AM)_
|
|
342
|
+
|
|
343
|
+
Can you review this Python function?
|
|
344
|
+
|
|
345
|
+
### Assistant _(1/15/2024, 10:00:05 AM)_
|
|
346
|
+
|
|
347
|
+
Sure! Here are my suggestions...
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### JSON
|
|
351
|
+
|
|
352
|
+
Produces a structured JSON file:
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{
|
|
356
|
+
"id": "abc123-def456",
|
|
357
|
+
"metadata": {
|
|
358
|
+
"title": "Code Review Discussion",
|
|
359
|
+
"sourceApp": "Claude",
|
|
360
|
+
"createdAt": "2024-01-15T10:00:00.000Z",
|
|
361
|
+
"tags": ["code-review", "python"]
|
|
362
|
+
},
|
|
363
|
+
"messages": [
|
|
364
|
+
{
|
|
365
|
+
"role": "user",
|
|
366
|
+
"content": "Can you review this Python function?",
|
|
367
|
+
"timestamp": "2024-01-15T10:00:00.000Z"
|
|
368
|
+
}
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Remote Server API
|
|
374
|
+
|
|
375
|
+
When using remote storage, your server should implement these endpoints:
|
|
376
|
+
|
|
377
|
+
### `POST /conversations`
|
|
378
|
+
|
|
379
|
+
Create a new conversation.
|
|
380
|
+
|
|
381
|
+
**Request Body:**
|
|
382
|
+
|
|
383
|
+
```json
|
|
384
|
+
{
|
|
385
|
+
"id": "string",
|
|
386
|
+
"title": "string",
|
|
387
|
+
"content": "string (formatted content)",
|
|
388
|
+
"format": "markdown | json",
|
|
389
|
+
"metadata": { ... }
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Response:**
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"url": "https://your-server.com/conversations/id"
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### `GET /conversations`
|
|
402
|
+
|
|
403
|
+
List all conversations.
|
|
404
|
+
|
|
405
|
+
### `GET /conversations/:id`
|
|
406
|
+
|
|
407
|
+
Get a specific conversation.
|
|
408
|
+
|
|
409
|
+
### `PUT /conversations/:id`
|
|
410
|
+
|
|
411
|
+
Update a conversation (for update_thread).
|
|
412
|
+
|
|
413
|
+
### `DELETE /conversations/:id`
|
|
414
|
+
|
|
415
|
+
Delete a conversation.
|
|
416
|
+
|
|
417
|
+
## Development
|
|
418
|
+
|
|
419
|
+
### Prerequisites
|
|
420
|
+
|
|
421
|
+
- Node.js 22+
|
|
422
|
+
- npm
|
|
423
|
+
|
|
424
|
+
### Setup
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
npm install
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Build
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
npm run build
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Run Tests
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
npm test # Run all tests
|
|
440
|
+
npm run test:watch # Watch mode
|
|
441
|
+
npm run test:coverage # With coverage report
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Development Server
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
npm run dev
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Linting & Formatting
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
npm run lint # ESLint
|
|
454
|
+
npm run format # Prettier format
|
|
455
|
+
npm run format:check # Check formatting
|
|
456
|
+
npm run typecheck # TypeScript type checking
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Project Structure
|
|
460
|
+
|
|
461
|
+
```
|
|
462
|
+
thread-mcp/
|
|
463
|
+
├── src/
|
|
464
|
+
│ ├── index.ts # Entry point
|
|
465
|
+
│ ├── server.ts # MCP server setup
|
|
466
|
+
│ ├── types.ts # TypeScript types & Zod schemas
|
|
467
|
+
│ ├── tools/ # MCP tool implementations
|
|
468
|
+
│ │ ├── save-thread.ts # Save new threads
|
|
469
|
+
│ │ ├── find-threads.ts # Search/list/get threads
|
|
470
|
+
│ │ ├── update-thread.ts # Update existing threads
|
|
471
|
+
│ │ ├── delete-thread.ts # Delete threads
|
|
472
|
+
│ │ └── resume-thread.ts # Load threads for continuation
|
|
473
|
+
│ ├── formatters/ # Output formatters
|
|
474
|
+
│ │ ├── markdown.ts
|
|
475
|
+
│ │ └── json.ts
|
|
476
|
+
│ └── storage/ # Storage providers
|
|
477
|
+
│ ├── local.ts
|
|
478
|
+
│ └── remote.ts
|
|
479
|
+
├── tests/
|
|
480
|
+
│ ├── unit/ # Unit tests
|
|
481
|
+
│ └── e2e/ # End-to-end tests
|
|
482
|
+
├── package.json
|
|
483
|
+
├── tsconfig.json
|
|
484
|
+
└── vitest.config.ts
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## License
|
|
488
|
+
|
|
489
|
+
MIT
|
|
490
|
+
|
|
491
|
+
## Contributing
|
|
492
|
+
|
|
493
|
+
1. Fork the repository
|
|
494
|
+
2. Create a feature branch
|
|
495
|
+
3. Make your changes
|
|
496
|
+
4. Run tests: `npm test`
|
|
497
|
+
5. Submit a pull request
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/formatters/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAO1C,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAE5D"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { markdownFormatter } from "./markdown.js";
|
|
2
|
+
import { jsonFormatter } from "./json.js";
|
|
3
|
+
export { markdownFormatter } from "./markdown.js";
|
|
4
|
+
export { jsonFormatter } from "./json.js";
|
|
5
|
+
const formatters = {
|
|
6
|
+
markdown: markdownFormatter,
|
|
7
|
+
json: jsonFormatter,
|
|
8
|
+
};
|
|
9
|
+
export function getFormatter(format) {
|
|
10
|
+
return formatters[format];
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/formatters/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,UAAU,GAAoC;IAClD,QAAQ,EAAE,iBAAiB;IAC3B,IAAI,EAAE,aAAa;CACpB,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/formatters/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAGf,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,aAAa,EAAE,SA2B3B,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ConversationSchema, } from "../types.js";
|
|
2
|
+
export const jsonFormatter = {
|
|
3
|
+
extension: ".json",
|
|
4
|
+
format(conversation, options) {
|
|
5
|
+
const output = {
|
|
6
|
+
id: conversation.id,
|
|
7
|
+
};
|
|
8
|
+
if (options.includeMetadata) {
|
|
9
|
+
output.metadata = conversation.metadata;
|
|
10
|
+
}
|
|
11
|
+
output.messages = conversation.messages.map((msg) => {
|
|
12
|
+
if (!options.includeTimestamps) {
|
|
13
|
+
const { timestamp, ...rest } = msg;
|
|
14
|
+
return rest;
|
|
15
|
+
}
|
|
16
|
+
return msg;
|
|
17
|
+
});
|
|
18
|
+
return JSON.stringify(output, null, 2);
|
|
19
|
+
},
|
|
20
|
+
parse(content) {
|
|
21
|
+
const data = JSON.parse(content);
|
|
22
|
+
return ConversationSchema.parse(data);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/formatters/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,SAAS,EAAE,OAAO;IAElB,MAAM,CAAC,YAA0B,EAAE,OAAoB;QACrD,MAAM,MAAM,GAA4B;YACtC,EAAE,EAAE,YAAY,CAAC,EAAE;SACpB,CAAC;QAEF,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,MAAM,CAAC,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;QAC1C,CAAC;QAED,MAAM,CAAC,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YAClD,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;gBAC/B,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC;gBACnC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,OAAe;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/formatters/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,SAAS,EAAwB,MAAM,aAAa,CAAC;AAmHjF,eAAO,MAAM,iBAAiB,EAAE,SAwD/B,CAAC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
function formatMessage(message, includeTimestamps) {
|
|
2
|
+
const roleLabel = message.role.charAt(0).toUpperCase() + message.role.slice(1);
|
|
3
|
+
const timestamp = includeTimestamps && message.timestamp
|
|
4
|
+
? ` _(${new Date(message.timestamp).toLocaleString()})_`
|
|
5
|
+
: "";
|
|
6
|
+
return `### ${roleLabel}${timestamp}\n\n${message.content}\n`;
|
|
7
|
+
}
|
|
8
|
+
function formatMetadata(conversation) {
|
|
9
|
+
const { metadata } = conversation;
|
|
10
|
+
const lines = [
|
|
11
|
+
"---",
|
|
12
|
+
`id: ${conversation.id}`,
|
|
13
|
+
`title: "${metadata.title}"`,
|
|
14
|
+
`created_at: ${metadata.createdAt}`,
|
|
15
|
+
];
|
|
16
|
+
if (metadata.updatedAt) {
|
|
17
|
+
lines.push(`updated_at: ${metadata.updatedAt}`);
|
|
18
|
+
}
|
|
19
|
+
if (metadata.sourceApp) {
|
|
20
|
+
lines.push(`source_app: ${metadata.sourceApp}`);
|
|
21
|
+
}
|
|
22
|
+
if (metadata.tags && metadata.tags.length > 0) {
|
|
23
|
+
lines.push(`tags: [${metadata.tags.map((t) => `"${t}"`).join(", ")}]`);
|
|
24
|
+
}
|
|
25
|
+
lines.push("---\n");
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
function parseMetadata(frontmatter) {
|
|
29
|
+
const lines = frontmatter.split("\n").filter((l) => l.trim());
|
|
30
|
+
const data = {};
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
const colonIndex = line.indexOf(":");
|
|
33
|
+
if (colonIndex === -1)
|
|
34
|
+
continue;
|
|
35
|
+
const key = line.slice(0, colonIndex).trim();
|
|
36
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
37
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
38
|
+
value = value.slice(1, -1);
|
|
39
|
+
}
|
|
40
|
+
data[key] = value;
|
|
41
|
+
}
|
|
42
|
+
if (!data.id || !data.title || !data.created_at) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
let tags;
|
|
46
|
+
if (data.tags) {
|
|
47
|
+
const tagsMatch = data.tags.match(/\[(.+)\]/);
|
|
48
|
+
if (tagsMatch) {
|
|
49
|
+
tags = tagsMatch[1].split(",").map((t) => t.trim().replace(/^"|"$/g, ""));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
id: data.id,
|
|
54
|
+
metadata: {
|
|
55
|
+
title: data.title,
|
|
56
|
+
createdAt: data.created_at,
|
|
57
|
+
updatedAt: data.updated_at,
|
|
58
|
+
sourceApp: data.source_app,
|
|
59
|
+
tags,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function parseMessages(content) {
|
|
64
|
+
const messages = [];
|
|
65
|
+
const messageRegex = /### (User|Assistant|System)(?:\s+_\((.+?)\)_)?\s*\n\n([\s\S]*?)(?=\n### |$)/gi;
|
|
66
|
+
let match;
|
|
67
|
+
while ((match = messageRegex.exec(content)) !== null) {
|
|
68
|
+
const role = match[1].toLowerCase();
|
|
69
|
+
const timestampStr = match[2];
|
|
70
|
+
const messageContent = match[3].trim();
|
|
71
|
+
const message = {
|
|
72
|
+
role,
|
|
73
|
+
content: messageContent,
|
|
74
|
+
};
|
|
75
|
+
if (timestampStr) {
|
|
76
|
+
try {
|
|
77
|
+
const date = new Date(timestampStr);
|
|
78
|
+
if (!isNaN(date.getTime())) {
|
|
79
|
+
message.timestamp = date.toISOString();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore invalid timestamps
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
messages.push(message);
|
|
87
|
+
}
|
|
88
|
+
return messages;
|
|
89
|
+
}
|
|
90
|
+
export const markdownFormatter = {
|
|
91
|
+
extension: ".md",
|
|
92
|
+
format(conversation, options) {
|
|
93
|
+
const parts = [];
|
|
94
|
+
if (options.includeMetadata) {
|
|
95
|
+
parts.push(formatMetadata(conversation));
|
|
96
|
+
}
|
|
97
|
+
parts.push(`# ${conversation.metadata.title}\n`);
|
|
98
|
+
if (conversation.metadata.summary) {
|
|
99
|
+
parts.push(`> ${conversation.metadata.summary}\n`);
|
|
100
|
+
}
|
|
101
|
+
parts.push("## Conversation\n");
|
|
102
|
+
for (const message of conversation.messages) {
|
|
103
|
+
parts.push(formatMessage(message, options.includeTimestamps));
|
|
104
|
+
}
|
|
105
|
+
return parts.join("\n");
|
|
106
|
+
},
|
|
107
|
+
parse(content) {
|
|
108
|
+
let id = crypto.randomUUID();
|
|
109
|
+
let metadata = {
|
|
110
|
+
title: "Untitled Conversation",
|
|
111
|
+
createdAt: new Date().toISOString(),
|
|
112
|
+
};
|
|
113
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
114
|
+
if (frontmatterMatch) {
|
|
115
|
+
const parsed = parseMetadata(frontmatterMatch[1]);
|
|
116
|
+
if (parsed) {
|
|
117
|
+
id = parsed.id;
|
|
118
|
+
metadata = parsed.metadata;
|
|
119
|
+
}
|
|
120
|
+
content = content.slice(frontmatterMatch[0].length);
|
|
121
|
+
}
|
|
122
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
123
|
+
if (titleMatch && metadata.title === "Untitled Conversation") {
|
|
124
|
+
metadata.title = titleMatch[1].trim();
|
|
125
|
+
}
|
|
126
|
+
const summaryMatch = content.match(/^>\s+(.+)$/m);
|
|
127
|
+
if (summaryMatch) {
|
|
128
|
+
metadata.summary = summaryMatch[1].trim();
|
|
129
|
+
}
|
|
130
|
+
const messages = parseMessages(content);
|
|
131
|
+
return { id, metadata, messages };
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=markdown.js.map
|