stuf-mcp 1.0.0 → 2.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 +1 -1
- package/crypto.js +4 -3
- package/index.js +1 -1
- package/package.json +3 -3
- package/sync.js +5 -6
package/README.md
CHANGED
package/crypto.js
CHANGED
|
@@ -12,8 +12,9 @@ export async function setEncryptionKey(base64Key) {
|
|
|
12
12
|
export async function encrypt(data) {
|
|
13
13
|
if (!encryptionKey) throw new Error('Encryption key not set');
|
|
14
14
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
15
|
-
|
|
16
|
-
const
|
|
15
|
+
// data is Uint8Array or Array — encrypt raw bytes
|
|
16
|
+
const input = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
17
|
+
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, encryptionKey, input);
|
|
17
18
|
const combined = new Uint8Array(iv.length + ciphertext.byteLength);
|
|
18
19
|
combined.set(iv);
|
|
19
20
|
combined.set(new Uint8Array(ciphertext), iv.length);
|
|
@@ -26,5 +27,5 @@ export async function decrypt(base64Data) {
|
|
|
26
27
|
const iv = combined.slice(0, 12);
|
|
27
28
|
const ciphertext = combined.slice(12);
|
|
28
29
|
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, encryptionKey, ciphertext);
|
|
29
|
-
return
|
|
30
|
+
return new Uint8Array(decrypted);
|
|
30
31
|
}
|
package/index.js
CHANGED
|
@@ -165,7 +165,7 @@ server.tool('delete_task', 'Delete a task', {
|
|
|
165
165
|
|
|
166
166
|
const taskName = doc.todos[idx].name;
|
|
167
167
|
await applyAndPush(d => {
|
|
168
|
-
d.todos.
|
|
168
|
+
d.todos.splice(idx, 1);
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
return { content: [{ type: 'text', text: `Deleted: ${taskName}` }] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stuf-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for stuf — AI-powered task management",
|
|
6
6
|
"main": "index.js",
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"claude"
|
|
27
27
|
],
|
|
28
28
|
"author": "asbjornenge",
|
|
29
|
-
"license": "
|
|
29
|
+
"license": "AGPL-3.0",
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
32
|
"url": "https://github.com/asbjornenge/stuf"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
-
"automerge": "^0.
|
|
36
|
+
"@automerge/automerge": "^3.0.0",
|
|
37
37
|
"ws": "^8.18.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/sync.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Automerge from 'automerge';
|
|
1
|
+
import * as Automerge from '@automerge/automerge';
|
|
2
2
|
import WebSocket from 'ws';
|
|
3
3
|
import { encrypt, decrypt } from './crypto.js';
|
|
4
4
|
|
|
@@ -35,7 +35,7 @@ export async function pullChanges() {
|
|
|
35
35
|
for (const { data } of changes) {
|
|
36
36
|
try {
|
|
37
37
|
const change = await decrypt(data);
|
|
38
|
-
doc = Automerge.applyChanges(doc, [change]);
|
|
38
|
+
[doc] = Automerge.applyChanges(doc, [change]);
|
|
39
39
|
} catch (err) {
|
|
40
40
|
console.warn(`Failed to apply change: ${err.message}`);
|
|
41
41
|
}
|
|
@@ -53,8 +53,7 @@ export async function pullSnapshot() {
|
|
|
53
53
|
const { data, seq } = await res.json();
|
|
54
54
|
if (data) {
|
|
55
55
|
const decrypted = await decrypt(data);
|
|
56
|
-
|
|
57
|
-
doc = Automerge.load(bytes);
|
|
56
|
+
doc = Automerge.load(decrypted);
|
|
58
57
|
lastSeq = seq || 0;
|
|
59
58
|
}
|
|
60
59
|
return doc;
|
|
@@ -63,11 +62,11 @@ export async function pullSnapshot() {
|
|
|
63
62
|
export async function pushChanges(oldDoc, newDoc) {
|
|
64
63
|
const changes = Automerge.getChanges(oldDoc, newDoc);
|
|
65
64
|
if (changes.length === 0) return;
|
|
66
|
-
const encrypted = await Promise.all(changes.map(c => encrypt(c)));
|
|
65
|
+
const encrypted = await Promise.all(changes.map(c => encrypt(Array.from(c))));
|
|
67
66
|
const res = await fetch(`${serverUrl}/api/changes`, {
|
|
68
67
|
method: 'POST',
|
|
69
68
|
headers: headers(),
|
|
70
|
-
body: JSON.stringify({ changes: encrypted })
|
|
69
|
+
body: JSON.stringify({ changes: encrypted, formatVersion: 3 })
|
|
71
70
|
});
|
|
72
71
|
if (!res.ok) throw new Error(`Push failed: ${res.status} ${await res.text()}`);
|
|
73
72
|
const { lastSeq: newSeq } = await res.json();
|