@telvok/librarian-mcp 2.3.1 → 2.3.2
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/dist/server.js
CHANGED
|
@@ -14,6 +14,7 @@ import { librarySearchTool } from './tools/library-search.js';
|
|
|
14
14
|
import { libraryBuyTool } from './tools/library-buy.js';
|
|
15
15
|
import { libraryDownloadTool } from './tools/library-download.js';
|
|
16
16
|
import { libraryPublishTool } from './tools/library-publish.js';
|
|
17
|
+
import { libraryUnpublishTool } from './tools/library-unpublish.js';
|
|
17
18
|
import { myBooksTool } from './tools/my-books.js';
|
|
18
19
|
import { syncTool } from './tools/sync.js';
|
|
19
20
|
import { sellerAnalyticsTool } from './tools/seller-analytics.js';
|
|
@@ -43,6 +44,7 @@ const allTools = [
|
|
|
43
44
|
{ tool: libraryBuyTool, group: 'marketplace' },
|
|
44
45
|
{ tool: libraryDownloadTool, group: 'marketplace' },
|
|
45
46
|
{ tool: libraryPublishTool, group: 'marketplace' },
|
|
47
|
+
{ tool: libraryUnpublishTool, group: 'marketplace' },
|
|
46
48
|
{ tool: myBooksTool, group: 'marketplace' },
|
|
47
49
|
{ tool: syncTool, group: 'marketplace' },
|
|
48
50
|
{ tool: sellerAnalyticsTool, group: 'marketplace' },
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export declare const libraryUnpublishTool: {
|
|
2
|
+
name: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {
|
|
8
|
+
slug: {
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
preview: {
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
unpublish_token: {
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
required: string[];
|
|
22
|
+
};
|
|
23
|
+
handler(args: unknown): Promise<{
|
|
24
|
+
success: boolean;
|
|
25
|
+
preview: boolean;
|
|
26
|
+
message: string;
|
|
27
|
+
unpublish_token: string;
|
|
28
|
+
book: {
|
|
29
|
+
slug: any;
|
|
30
|
+
name: any;
|
|
31
|
+
entries_count: any;
|
|
32
|
+
pricing: any;
|
|
33
|
+
url: any;
|
|
34
|
+
};
|
|
35
|
+
next_steps: string;
|
|
36
|
+
} | {
|
|
37
|
+
success: boolean;
|
|
38
|
+
message: any;
|
|
39
|
+
preview?: undefined;
|
|
40
|
+
unpublish_token?: undefined;
|
|
41
|
+
book?: undefined;
|
|
42
|
+
next_steps?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
success: boolean;
|
|
45
|
+
message: any;
|
|
46
|
+
book: any;
|
|
47
|
+
preview?: undefined;
|
|
48
|
+
unpublish_token?: undefined;
|
|
49
|
+
next_steps?: undefined;
|
|
50
|
+
}>;
|
|
51
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Marketplace Unpublish Tool
|
|
3
|
+
// Remove a published book from Telvok library
|
|
4
|
+
// ============================================================================
|
|
5
|
+
import * as crypto from 'crypto';
|
|
6
|
+
import { loadApiKey } from './auth.js';
|
|
7
|
+
const TELVOK_API_URL = process.env.TELVOK_API_URL || 'https://telvok.com';
|
|
8
|
+
const TOKEN_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
|
|
9
|
+
let pendingUnpublish = null;
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Tool Definition
|
|
12
|
+
// ============================================================================
|
|
13
|
+
export const libraryUnpublishTool = {
|
|
14
|
+
name: 'library_unpublish',
|
|
15
|
+
title: 'Unpublish Book',
|
|
16
|
+
description: `Remove a published book from Telvok marketplace.
|
|
17
|
+
|
|
18
|
+
⚠️ TWO-STEP UNPUBLISH FLOW (MANDATORY):
|
|
19
|
+
|
|
20
|
+
Step 1: ALWAYS call with preview: true first. Shows what will be deleted
|
|
21
|
+
and returns an unpublish_token. Show the preview to the user and ASK FOR CONFIRMATION.
|
|
22
|
+
|
|
23
|
+
Step 2: ONLY after the user explicitly confirms, call again with the unpublish_token
|
|
24
|
+
from the preview response. Unpublishing WITHOUT a valid token will be rejected.
|
|
25
|
+
|
|
26
|
+
RESTRICTIONS:
|
|
27
|
+
- Cannot unpublish books with active purchases
|
|
28
|
+
- Deletion is PERMANENT — all entries are removed from marketplace
|
|
29
|
+
|
|
30
|
+
TRIGGER PATTERNS:
|
|
31
|
+
- "Unpublish my book" → library_unpublish({ slug: "...", preview: true })
|
|
32
|
+
- "Remove from marketplace" → library_unpublish({ slug: "...", preview: true })
|
|
33
|
+
- User says "yes, delete it" → library_unpublish({ slug: "...", unpublish_token: "<token>" })
|
|
34
|
+
|
|
35
|
+
Use my_books() first to see your published books and their slugs.`,
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
slug: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Book slug (from my_books output)',
|
|
42
|
+
},
|
|
43
|
+
preview: {
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
description: 'If true, show what would be deleted without deleting. Returns an unpublish_token.',
|
|
46
|
+
},
|
|
47
|
+
unpublish_token: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Token from preview response. Required to actually unpublish. Single-use, expires in 5 minutes.',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ['slug'],
|
|
53
|
+
},
|
|
54
|
+
async handler(args) {
|
|
55
|
+
const { slug, preview, unpublish_token } = (args || {});
|
|
56
|
+
// Validate slug
|
|
57
|
+
if (!slug || typeof slug !== 'string') {
|
|
58
|
+
return { success: false, message: 'slug is required. Use my_books() to see your published books.' };
|
|
59
|
+
}
|
|
60
|
+
// Load API key
|
|
61
|
+
const apiKey = await loadApiKey();
|
|
62
|
+
if (!apiKey) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
message: 'Not authenticated. Run auth({ action: "login" }) first.',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// ========================================================================
|
|
69
|
+
// PREVIEW MODE — show what will be deleted, generate token
|
|
70
|
+
// ========================================================================
|
|
71
|
+
if (preview) {
|
|
72
|
+
// Fetch book details via my-books to confirm it exists and we own it
|
|
73
|
+
const res = await fetch(`${TELVOK_API_URL}/api/my-books`, {
|
|
74
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
return { success: false, message: `Failed to fetch books: ${res.status}` };
|
|
78
|
+
}
|
|
79
|
+
const data = await res.json();
|
|
80
|
+
const book = data.published?.find((b) => b.slug === slug);
|
|
81
|
+
if (!book) {
|
|
82
|
+
return {
|
|
83
|
+
success: false,
|
|
84
|
+
message: `No published book found with slug "${slug}". Use my_books() to see your books.`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Generate token
|
|
88
|
+
const token = crypto.randomBytes(16).toString('hex');
|
|
89
|
+
pendingUnpublish = { token, slug, created: Date.now() };
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
preview: true,
|
|
93
|
+
message: `Preview of unpublish — NOT deleted yet.\n\n⚠️ Show this to the user and ask for confirmation before unpublishing.`,
|
|
94
|
+
unpublish_token: token,
|
|
95
|
+
book: {
|
|
96
|
+
slug: book.slug,
|
|
97
|
+
name: book.name,
|
|
98
|
+
entries_count: book.entries_count,
|
|
99
|
+
pricing: book.pricing,
|
|
100
|
+
url: book.url,
|
|
101
|
+
},
|
|
102
|
+
next_steps: 'Show preview to user. After they confirm, call library_unpublish() again with the unpublish_token to delete.',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// ========================================================================
|
|
106
|
+
// EXECUTE MODE — validate token, call API to delete
|
|
107
|
+
// ========================================================================
|
|
108
|
+
// Token required
|
|
109
|
+
if (!unpublish_token) {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
message: '🚫 Unpublishing requires an unpublish_token. Call with preview: true first to get one.',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// Validate token
|
|
116
|
+
if (!pendingUnpublish || pendingUnpublish.token !== unpublish_token) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
message: '🚫 Invalid or expired unpublish_token. Run a new preview to get a fresh token.',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Check expiry
|
|
123
|
+
if (Date.now() - pendingUnpublish.created > TOKEN_EXPIRY_MS) {
|
|
124
|
+
pendingUnpublish = null;
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
message: '🚫 Unpublish token expired (5 minute limit). Run a new preview.',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// Check slug matches
|
|
131
|
+
if (pendingUnpublish.slug !== slug) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
message: `🚫 Token was generated for slug "${pendingUnpublish.slug}", not "${slug}". Run a new preview.`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
// Consume token (single use)
|
|
138
|
+
pendingUnpublish = null;
|
|
139
|
+
// Call API
|
|
140
|
+
const res = await fetch(`${TELVOK_API_URL}/api/publish`, {
|
|
141
|
+
method: 'DELETE',
|
|
142
|
+
headers: {
|
|
143
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
144
|
+
'Content-Type': 'application/json',
|
|
145
|
+
},
|
|
146
|
+
body: JSON.stringify({ slug }),
|
|
147
|
+
});
|
|
148
|
+
const data = await res.json();
|
|
149
|
+
if (!res.ok) {
|
|
150
|
+
return {
|
|
151
|
+
success: false,
|
|
152
|
+
message: data.message || data.error || `Unpublish failed: ${res.status}`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
message: data.message || `Unpublished "${slug}" from marketplace`,
|
|
158
|
+
book: data.book,
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
};
|